C语言程序设计易混、易错知识点(中篇)

  1. 注:个别题目未给ABCD,只需要了解知识点即可;另外排版可能有点乱,望知悉

  2. 在printf中的%作为转义符,两个%才相当于1个%

  3. free掉一个指针后,指针的值是不会自动置为NULL的,当然其指向的内存已经被释放,可以重新分配给其他进行使用,此时该指针被称为野指针

    • 对野指针进行操作,可能会破坏内存结构,因为并不知道当前指针指向的内容是什么,所以一般在free操作结束后,由程序猿将指针置为NULL。
  4. C语言的指针的数据类型声明的是指针实际指向内容的数据类型

  5. c = c^32 大小写互换

  6. 当顺利执行了文件关闭操作时,fclose函数的返回值是:如果正常关闭了文件,则函数返回值为0;否则,返回值为非0

  7. 以下函数用法正确的个数是:

    
      void test1(){
          unsigned char array[MAX_CHAR+1],i;
          for(i = 0;i <= MAX_CHAR;i++){
              array[i] = i;
          }
      }
      char*test2(){
          char p[] = "hello world";
          return p;
      }
      char *p = test2();
      void test3(){
          char str[10];
          str++;
          *str = '0';
      }
    
    
    

    重点不在于CHAR_MAX的取值是多少,而是在于i的取值范围是多少。

    一般char的取值范围是-128到127,而u char 则是0 ~ 255,所以i的取值范围是0~255.

    所以当CHAR_MAX常量大于255时,执行i++后,i不能表示256以上的数字,所以导致无限循环。

    第二个问题:

    重点在于函数中p的身份,他是一个指针,还是数组名;

    如果是指针p,则p指向存放字符串常量的地址,返回p则是返回字符串常量地址值,调用函数结束字符串常量不会消失(是常量)。所以返回常量的地址不会出错。

    如果是数组p,则函数会将字符串常量的字符逐个复制到p数组里面,返回p则是返回数组p,但是调用函数结束后p被销毁,里面的元素不存在了。

    例子中p是数组名,所以会出错,p所指的地址是随机值。

    若是把char p[]=“hello”;改成char *p=“hello”;就可以了。

    第三个问题:

    重点在于str++;这实际的语句就是str=str+1;而str是数组名,数组名是常量,所以不能给常量赋值。(可以执行str+1,但是不能str=.)

  8. feesk中seek_end 的文件末尾指针末尾最后一个字符后面,而非最后一个字符

  9. 使用"w"写文件也可以使用fwrite

  10. 对于32位系统,定义 int **a[3][4], 则变量a占有的内存空间为

    • 此处定义的是指向指针的指针数组,对于32位系统,指针占内存空间4字节,因此总空间为3×4×4=48。

​ 下面是几种网上解答:

用右左法则来看,首先往变量a的右边看,是【3】,再往左看,是*,所以它首先是一个指针数组,数组里存放3个指针,然后再往右看是【4】,再往左看,是int ,说明前面3个指针每个指针都指向一个数组,每个数组里存放4个int 类型的指针,所以34有12个二级指针,每个指针在32位系统占4个字节,所以48个字节

​ 可以从简单到难进行理解,int *a[3]是一个指针数组数组中的每个元素就是一个指针,a的大小是3*4 = 12;int *a[3][4]是一个二维的指针数组,数组中的每一个元素是一个指针,a的大小是3*4*4 = 48int **a[3]也是一个指针数组,只不过该数组的元素是一个二级指针,但是二级指针的本质还是指针,所以a的大小是3*4 = 12int **a[3][4]是一个二维数组,数组中的每一个元素是一个二级指针,所以a的大小是3*4*4 = 48,就这么简单,不需要去分析什么从左到右从右到左看,反而容易搞混;
​ 因为[]的优先级是大于
号的优先级的,所以先从变量名a向右看是a[3][4]是一个二维数组,然后再将该二维数组看作一个整体向左看,int **是一个二级指针,说明该二维数组是一个数据类型为二级指针的指针数组,这种理解方法就跟普通的指针数组理解方式一样,比如:int *a[3]
​ 我们理解是先向右看是a[3]拥有三个元素的数组,再向左看,int *说明数组的元素类型是整形的指针类型,所以是一个指针数组

  1. %3o表示以八进制数形式输出,占3个空格的位置,右对齐,左边多余的位置补

    空格,但实际数据的宽度为4大于规定的宽度,所以此时按实际宽度输出,故第一个y的 输出为│4630│。%8o与%3o的差别就在于输出占8个空格的位置,所以左边要补4个空格, 故第二个y的输出也为│□□□□4630│.%#8o与%8o的差别只是输出时必须输出八进制前导0,所以第三个y的输出为│□□□04630│.%08o与%8o的差别只是输出时左边多余的位置补0, 所以第四个y的输出为│00004630│

  2. 2014对应的二进制为:0000 0000 000 0000 0000 0111 1101 1110

    • x|(x+1)的作用是对一个数中二进制0的个数进行统计
  3. 1、【编译】是把c源程序翻译成汇编代码:.s

    2、【汇编】是把汇编代码翻译成二进制目标代码:.obj

    3、【链接】是把多个二进制目标代码文件链接成一个可执行程序;因为一个工程某个文件调用了其他c文件的函数或变量 一个程序需要上面三个步骤才能由源程序变成可执行程序。

  4.       union s{
          	int i;
          	char c;
          	float a;
          }temp;
          temp.i = 266;
          printf("%d", temp.c);
          
          //输出是10
          因为266>256.也因为temp一共占有四个字节,i和ch共用内存空间,但是ch 只占据最低的一个字节,即最低的8位,所以输出c的值也是只能输出这一字节内存中的二进制数表示的数,如果赋值的i是100,将会正常输出100
              
    
  5.       int x = 10;
          printf("%d, %d\n", --x, --x);//8,8
          x = 10;
          printf("%d, %d\n", --x, x--);//8,10
          x = 10;
          printf("%d, %d\n", x--, --x);//9,8
          x = 10;
          printf("%d, %d\n", x--, x--);//9,10
    
    
    
    
    int main() {
    	int x[5] = {0,1,2,3,4}, *p = x;
    	p += 2;
    	int a =5;
    	printf("%d  %d  %d\n", *p, *p++, *(++p));
    	printf("%d  %d  %d", a, a++, ++a);
        //4  3  3(区别指针变量与变量的输出情况是不同的)
    	//7  6  7
    }
    
  6.       int fseek(FILE *stream, long offset, int fromwhere); 
          
          函数fseek将文件位置指针重定位到fromwhere(SEEK_SET文件头0,SEEK_CUR文件当前位置1,SEEK_END文件末尾2)开始偏移offset个字节的位置;返回成功0,失败-1long ftell(FILE *stream);
          
          返回文件位置指针当前位置相对于文件首的偏移字节数;
    
  7. 每个C语言中的字要么归为关键字,要么归为标识符。而标识符分为预定义标识符和用户标识符

  8. 变量可以定义在函数体外也可以定义在函数体内 , 所以 A 错误。常量的类型可以从字面形式上区分 , 比如 1 为整型常量 ,1.0 为实型常量 ,a 为字符型常量 , 所以 B 错误。预定义的标识符不是 C 语言的关键字 , 所以 C 错误。

  9. 整型常量和实型常量都是数值型常量

  10. 两个指针变量之间的运算

  • 只有指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义。
1) 两指针变量相减

两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。 实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数)。例如pf1和pf2是指向同一浮点数组的两个指针变量,设pf1的值为2010H,pf2的值为2000H,而浮点数组每个元素占4个字节,所以pf1-pf2的结果为(2000H-2010H)/4=4,表示pf1和 pf2之间相差4个元素。

注意:两个指针变量不能进行加法运算。例如,pf1+pf2是什么意思呢?毫无实际意义。

2) 两指针变量进行关系运算

指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。例如:

  • pf1==pf2 表示pf1和pf2指向同一数组元素;
  • pf1>pf2 表示pf1处于高地址位置;
  • pf1<pf2 表示pf2处于低地址位置。

指针自增、自减每次移动的偏移量是指针所指向对象的字节大小,所以p++与q的偏移量是2个字节。

指针相减的值是指针地址的偏移除以指针每次移位的大小;

1)p-q=1;偏移量为2个字节,每次移动2个字节,所以为1

2)(char *)p-(char *)q,指针的偏移没变,但是每次指针移位是按照(char*)类型移动,即每次移动1个字节,所以是2

3)数字每次元素2个字节,所以sizeof(arr)为30,sizeof(*arr)为2。

​ 指针变量还可以与0比较。设p为指针变量,则p==0表明p是空指针,它不指向任何变量;p!=0表示p不是空指针。
空指针是由对指针变量赋予0值而得到的。例如:

    #define NULL 0
    int *p = NULL;

对指针变量赋0值和不赋值是不同的。 指针变量未赋值时,值是随机的,是垃圾值,不能使用的,否则将造成意外错误。而指针变量赋0值后,则可以使用,只是它不指向具体的变量而已。

  1. 面试问题

    无法高效获取长度,无法根据偏移快速访问元素,是链表的两个劣势。然而面试的时候经常碰见诸如获取倒数第k个元素获取中间位置的元素判断链表是否存在环判断环的长度等和长度与位置有关的问题。这些问题都可以通过灵活运用双指针来解决。

    Tips:双指针并不是固定的公式,而是一种思维方式~

    先来看"倒数第k个元素的问题"。设有两个指针 p 和 q,初始时均指向头结点。首先,先让 p 沿着 next 移动 k 次。此时,p 指向第 k+1个结点,q 指向头节点,两个指针的距离为 k 。然后,同时移动 p 和 q,直到 p 指向空,此时 q 即指向倒数第 k 个结点。

      class Solution {
      public:
          ListNode* getKthFromEnd(ListNode* head, int k) {
              ListNode *p = head, *q = head; //初始化
              while(k--) {   //将 p指针移动 k 次
                  p = p->next;
              }
              while(p != nullptr) {//同时移动,直到 p == nullptr
                  p = p->next;
                  q = q->next;
              }
              return q;
          }
      };
      
    

    获取中间元素的问题。设有两个指针 fast 和 slow,初始时指向头节点。每次移动时,fast向后走两次,slow向后走一次,直到 fast 无法向后走两次。这使得在每轮移动之后。fast 和 slow 的距离就会增加一。设链表有 n 个元素,那么最多移动 n/2 轮。当 n 为奇数时,slow 恰好指向中间结点,当 n 为 偶数时,slow 恰好指向中间两个结点的靠前一个(可以考虑下如何使其指向后一个结点呢?)。

      下述代码实现了 n 为偶数时慢指针指向靠后结点。
      class Solution {
      public:
          ListNode* middleNode(ListNode* head) {
              ListNode *p = head, *q = head;
              while(q != nullptr && q->next != nullptr) {
                  p = p->next;
                  q = q->next->next;
              }
              return p;
          } 
      };
      
    

    是否存在环的问题。如果将尾结点的 next 指针指向其他任意一个结点,那么链表就存在了一个环。

    上一部分中,总结快慢指针的特性 —— 每轮移动之后两者的距离会加一。下面会继续用该特性解决环的问题。
    当一个链表有环时,快慢指针都会陷入环中进行无限次移动,然后变成了追及问题。想象一下在操场跑步的场景,只要一直跑下去,快的总会追上慢的。当两个指针都进入环后,每轮移动使得慢指针到快指针的距离增加一,同时快指针到慢指针的距离也减少一,只要一直移动下去,快指针总会追上慢指针。

    据上述表述得出,如果一个链表存在环,那么快慢指针必然会相遇。实现代码如下:

      class Solution {
      public:
          bool hasCycle(ListNode *head) {
              ListNode *slow = head;
              ListNode *fast = head;
              while(fast != nullptr) {
                  fast = fast->next;
                  if(fast != nullptr) {
                      fast = fast->next;
                  }
                  if(fast == slow) {
                      return true;
                  }
                  slow = slow->next;
              }
              return nullptr;
          }
      };
      
    

    最后一个问题,如果存在环,如何判断环的长度呢?方法是,快慢指针相遇后继续移动,直到第二次相遇。两次相遇间的移动次数即为环的长度

  2. 由于链表中从高位到低位存放了数字的二进制表示,因此我们可以使用二进制转十进制的方法,在遍历一遍链表的同时得到数字的十进制值。

    以示例 1 中给出的二进制链表为例:

    在这里插入图片描述

    ​ 表示 n 是二进制整数。
    在这里插入图片描述

    链表的第 1 个节点的值是 1,这个 1 是二进制的最高位,在十进制分解中,1 作为系数对应的 22
    的指数是 2,这是因为链表的长度为 3。我们是不是有必要一定要先知道链表的长度,才可以确定指数 2 呢?答案是不必要的。

    • 每读取链表的一个节点值,可以认为读到的节点值是当前二进制数的最低位;
    • 当读到下一个节点值的时候,需要将已经读到的结果乘以 2,再将新读到的节点值当作当前二进制数的最低位;
    • 如此进行下去,直到读到了链表的末尾。
    class Solution {
    public:
        int getDecimalValue(ListNode* head) {
            ListNode* cur = head;
            int ans = 0;
            while (cur != nullptr) {
                ans = ans * 2 + cur->val;
                cur = cur->next;
            }
            return ans;
        }
    };
    
    
         5÷2=2余1 
         2÷2=1余0 
         1÷2=0余1  
          ===> 得出二进制 101 .
         反推回去 商 x 除数 + 余数 
         => 0 x 2 + 1 = 1 
         -> 1 x 2 + 0 = 2
         -> 2 x 2 +1 = 5
    
  3. C语言语法规定,字母e或E之前必须要有数字,且e或E后面的指数必须为整数。如e3、5e3.6、.e、e等都是非法的指数形式。

  4. 在这里插入图片描述

  5. 在方法体中定义的局部变量在该方法被执行时创建:错

    • 不是局部变量再该方法被执行/调用时创建,而是应该为在该变量被声明并赋值时创建,可以理解为当代码执行到该 变量被赋值的代码是才被创建
  6. C语言的源程序加工包括三步:预处理、编译和链接。

    其中源程序加工的编译阶段又可细分为:预处理,编译,汇编三个阶段。

    即C语言由源代码生成可执行程序的过程为:C源程序→编译预处理→编译→汇编程序→链接程序→可执行文件

    ①预处理:
    
    1、头文件的包含,#include预处理指令。
    
    2、define定义符号的替换
    
    #define  预处理指令
    
    注释删除
    
    ②编译:
    
    1、把C语言代码翻译成汇编代码。
    
    2、语法分析
         
    3、词法分析
    
    4、语义分析
    
    5、符号汇总
    
    ③汇编
    
    1、把汇编指令翻译成二进制指令
    
    2、形成符号表
    
    ④链接
    
    1、合并段表
    
    2、符号表的合并和符号表的重定位
    
  7. 函数fscanf不能从标准输入流读取数据 --错

  8. 程序员必须明确地用函数fopen打开标准输入流、标准输出流和标准错误流 --错

  9. 程序必须明确地调用函数fclose关闭文件 – 错

  10.  int _tmain(int argc, _TCHAR* argv[])
     {
     	char *p1 = "h
            ello";
     	char *p2 = "world";
     	char *p3 = "a piece of cake";
     	char *str[] = { p1, p2, p3 };
     	cout << sizeof(*str[0]) << " " << typeid(str[0]).name() << " " << *(str[0] + 1) << endl;//typeid是类型
     	cout << sizeof(*&str[0]) << " " << typeid(&str[0]).name() << " " << *(&str[0] + 1) << endl;
     	cout << sizeof(*str) << " " << typeid(str).name() << " " << *(str + 1) << endl;
     	cout << sizeof(*&str) << " " << typeid(&str).name() << " " << *(&str + 1) << endl;
     	return 0;
     }
     运行结果:
     1 char * e
     4 char * * world
     4 char * [3] world
     12 char * (*)[3] 00F7F734
     能看懂这个你就知道了,这个地方+1的时候都是说步长,步长就是说+1前面的这个对象 所指向的 数据类型的长度,比如 &str[0]类型是char * * 所指向的是char * 长度是指针的长度(不同机器不同)
    
  11. math.h的abs返回值()

    不可能是负数

    不可能是正数

    都有可能

    不可能是0

    因为负数的范围比正数大一个,比如8位的二进制,可以表示范围为-128~127所以abs(-128)可能并不能表示为128
    所以只能返回原值

    img

  12. int *p[4]; //表示指针数组,有四个元素,每个元素都是整型指针。

    int (*p)[4]; //表示行指针,所指对象一行有四个元素。
    int *p(void); //表示函数,此函数无参,返回整型指针。
    int( *P)(void) ;//表示函数指针,可以指向无参,且返回值为整型指针的函数。

  13. 静态成员函数只能访问静态成员变量和静态成员函数。而不是只有静态成员函数可以操作静态数据成员

  14. 看不懂char data[0];请去百度 柔性数组,它只能放在结构体末尾,是

    申明一个长度为0的数组,就可以使得这个结构体是可变长的。对于编译器来说,此时长度为0的数组并不占用空间,因为数组名本身不占空间,它只是一个偏移量, 数组名这个符号本身代 表了一个不可修改的地址常量 (注意:数组名永远都不会是指针!,只有在函数传递中数组名称就是一个指针。 ),但对于这个数组的大小,我们可以进行动态分配 请仔细理解后半部分,对于编译器而言,数组名仅仅是一个符号,它不会占用任何空间,它在结构体中,只是代表了一个偏移量,代表一个不可修改的地址常量!

    对于0长数组的这个特点,很容易构造出变成结构体,如缓冲区,数据包等等:

    注意:构造缓冲区就是方便管理内存缓冲区,减少内存碎片化,它的作用不是标志结构体结束,而是扩展

    柔性数组是C99的扩展,简而言之就是一个在struct结构里的标识占位符(不占结构struct的空间)

  15. 负数取模运算:将负数看成正数进行取模 ,余数的符号与被除数即第一个操作符相同(C和java)

  16. C语言中的一个变量可以被定义为两个或多个不同的类型。——正确

    • 如果同一个变量,分别作为全局变量和局部变量,根据局部优先原则,是可以定义为不同类型的
  17. 1、任何指针都可以直接赋值给void*型指针

    2、void*型指针赋给确定类型的指针时需要进行格式转换

    3、指针大小和系统位数有关,16位系统是2个字节,32位系统是个4字节,64位系统是8个字节

    4、指针有安全隐患

  18. 函数在执行return语句时会将其右边语句的值保存在eax寄存器中,然后整个函数语句被调用时的值就是eax寄存器里面的值。

    如果没有写return,那么返回的也就自然是上一次变量对应eax寄存器里面的值。形参以及内部变量的值都是临时存放在eax寄存器里面的,所以它的值是时刻更新的,也就是最后一次被更新的变量的值.

    这也很好地解释了图4为什么不是最后一条语句的值,而是最后一次被更新的变量m的值.

    这里要提到另一个规则了:函数在调用时,根据实参顺序从右到左依次存入eax寄存器里,再放到形参里面,也就造成了图6中的结果:输入顺序不同,返回的值不同,这里按照从右到左的选择返回的就是a的值。

  19. 我们先来看一下指针常量是什么:指针是形容词,常量是名词。这回是以常量为中心的一个偏正结构短语。那么,指针常量的本质是一个常量,而用指针修饰它,那么说明这个常量的值是一个指针指针常量的值是指针,这个值因为是常量,所以不能被赋值。
    回过来看本题:
    A选项:NULL 是一个标准规定的宏定义,用来表示空指针常量
    B选项:C语言规定函数名就是函数的入口地址。符合指针常量的性质:值是一个指针,且不能修改。
    C选项:数组的名字本质上与函数名是相同的,只不过一个指向了函数体,一个指向的数组。在一次运行中,数组名所指向的地址都是同一个地址,也符合指针常量的定义
    D选项:宏函数的名字则不是,原因在于宏在编译前先处理,类似于查找替换(支持正则表达式匹配),所以宏函数的名字只是简单的文本,并没有实际意义,所以不是指针常量

  20. 宏是在预编译时执行的,而函数是在运行时执行的。宏替换在编译前进行,不分配内存 . 宏展开不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)

  21. C/C++中浮点数由符号位、阶码和尾数构成,其二进制表示并不直接对应浮点数的大小,因此浮点数类型不能进行位运算,否则编译器报错;所以如果A选项正确,其指的应该是x的二次幂;而不是x与2进行逐位异或

  22. 虽然浮点数标准IEEE 754满足加法和乘法的交换律,不满足加法结合律,但是C++标准不保证IEEE 754标准的实现,于是C++编译器也不保证浮点数a+b的结果等于b+a

  23. 浮点数存在误差,直接比较大小往往不是预期的结果;通常引入一个比要求精度还要小几个数量级的实数epsilon来帮助比较大小。在我的机器上,精度取1e-8,0.9f == 0.9为假(0.9f是单精度浮点,精度比0.9低):

  24. EOF只能作为文本文件的结束标志

    • 因为在文本文件中所有的字符都是以ASCII码值保存,故不可能出现-1,因此可作为结束标志,但二进制文件就不同了。
  25.  char *s = "abcdefg";
    s += 2;
     printf("%d\n", s);
    

指针s指向的是字符串的首地址,也就是字符‘a’的地址

s+=2使得s指向字符‘c’

printf函数中使用%d做控制,要输出的是整型数字,也就是字符‘c’的地址

如果要输出字符‘c’,可用

fprintf(stderr, ``"%c\n"``, *s);

如果要输出剩余字符串,可用

fprintf(stderr, ``"%s\n"``, s);
  1. ● itoa():将整型值转换为字符串
    ● ltoa():将长整型值转换为字符串。
    ● ultoa():将无符号长整型值转换为字符串。
    ● gcvt():将浮点型数转换为字符串,取四舍五入。
    ● ecvt():将双精度浮点型值转换为字符串,转换结果中不包含十进制小数点。
    ● fcvt():指定位数为转换精度,其余同ecvt()。

    itoa()函数有3个参数:第一个参数是要转换的数字,第二个参数是要写入转换结果的目标字符串,第三个参数是转移数字时所用 的基数。在上例中,转换基数为1010:十进制;2:二进制...
    
    itoa并不是一个标准的C函数,它是Windows特有的,如果要写跨平台的程序,请用sprintf
    
    # include <stdio.h>
    # include <stdlib.h>
    void main (void)
    {
    int num = 100;
    char str[25];
    itoa(num, str, 10);
    printf("The number 'num' is %d and the string 'str' is %s. \n" ,
    num, str);
    }
    
    
    int atoi (char s[])
    {
    int i,n,sign;
    for(i=0;isspace(s[i]);i++)//跳过空白符;
    sign=(s[i]=='-')?-1:1;
    if(s[i]=='+'||s[i]==' -')//跳过符号
      i++;
    for(n=0;isdigit(s[i]);i++)
           n=10*n+(s[i]-'0');//将数字字符转换成整形数字
    return sign *n;
    

    ● atof():将字符串转换为双精度浮点型值。
    ● atoi():将字符串转换为整型值。
    ● atol():将字符串转换为长整型值。
    ● strtod():将字符串转换为双精度浮点型值,并报告不能被转换的所有剩余数字。
    ● strtol():将字符串转换为长整值,并报告不能被转换的所有剩余数字。
    ● strtoul():将字符串转换为无符号长整型值,并报告不能被转换的所有剩余数字。

  2. int f1(float);
    int f2(char);
    void f3(float);
    int (*pf)(float);

    函数指针变量:

    函数指针变量的声明方法为:

    返回值类型 ( * 指针变量名) ([形参列表]);
    根据定义,

    int(*pf)(float); int (*p)(float)=&f1;
    pf,p都是函数指针变量。

    函数地址 :

    在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。

    函数地址的获取,可以是函数名,也可以在函数名前加取地址符& 。

    C错误是因为函数形参类型不匹配。

    函数指针所指向的函数,返回值类型,形参列表必须完全匹配,对函数指针赋值可以采用以下方式pf=&p1或者pf=p1

  3. 按位运算是对字节或字中的实际位进行检测、设置或移位, 它只适用于字符型和整数型变量以及它们的变体, 对其它数据类型不适用。无论是float 还是double,在内存中的存储分为三部分:**符号位,指数位,尾数位;**位运算符对它们没有意义

  4. float类型的变量赋值后为什么必须在值后加"f"/“F”
    float x = 3.4F;
    这里将“3.4”赋值给float类型的变量x,如果不加F,系统会默认把赋值的数字当作double类型处理 1,然后在把这个double类型的值赋给float类型,这样就会出现精度丢失。
    float y = 3F;
    这里将“3”赋值给float类型的变量y,如果将整数类型的“3”赋值给float,系统会自动将其转化为double类型1,然后再赋值给float类型,这样虽然会编译成功,但会导致精度缺失。
    常量存储在常量缓冲区中,有且只有一份,常量缓冲区中的值默认空间大小,如果是整数,默认空间大小为32bit----int,如果是小数,默认空间大小为64bit----double。

  5. %f用于输入float,%lf用于输入double,%le用于科学计数法输入double型变量

  6. 形参与实参的之间的传递分类

  7. 1、按值传递(实形无联系)

    按值传递就是平常编程中经常用到的,定义一个基本数据类型的变量,在调用某函数时把该变量作为函数的实参传递给函数。这种传递方式采用的是单向值传递,实形无联系,形参改变不影响实参。

    2、按地址传递(通过操作形参可能会改变实参)

    按地址传递主要出现在函数参数是指针变量、数组等的时候。

    注意:

    实质上用指针做函数参数的情况下,在调用函数时,将实参变量的传递给形参变量,采取的依然是单向值传递。如果在被调函数中只是单纯改变了形参指针变量的值,在函数调用结束后这些形参被销毁,是不会影响调用函数时传入实参指针变量值

    只有当你在被调函数中通过操作形参指针变量,去改变了指针指向变量的值时,才可以改变实参指针变量所指向变量的值。也只有这种情况下形参改变才可能影响实参。

  8. 假设函数原型和变量说明如下:

    void f3(int(*p)[4]);``int a[4]={1,2,3,4},``int b[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    

    下面调用非法的是

    • f3(&a);
      
    • f3(b[1]);
      
    • f3(&b[1]);
      
    • f3(b);
      
      

      B。根据题目结合选项来看考察的是对函数的传参调用,其中参数涉及到数组指针

      void f3(int(*p)[4]); 其参数是数组指针 ,指向数组p的指针。

      • ​ 选项A:f3(&a); 参数为一个地址,符合指针定义。
      • ​ 选项B:f3(b[1]); 参数为一个数组的具体元素,不符合指针定义。所以B是非法的调用。
      • ​ 选项C:f3(&b[1]); 参数为一个数组元素的地址,符合指针定义。
      • ​ 选项D:f3(b); 参数为数组名,表示该数组的首地址,符合指针定义。

      b[1]&b[1]虽然值相等,但是含义不一样,&b[1]是行指针,类型是int (*)[4],和a的类型相同;而b[1]是个int *,和&b[1][0]等价。

      • b[1]作为第二行数组的数组名,如果单独出现,则表示首元素的地址,即元素b[1][0]的地址,类型是int*;如果加取地址&,变成&b[1],那么就是整个第二行数组的地址,和函数f(3)要求的形参保持一致,即该指针指向一个整型数组,数组共4个整型元素。并不是所谓的指针的指针。
  9. C语言中,未经赋值全局变量默认初始化为0auto类型、register类型不确定

  10. 所有的静态局部变量,即定义在函数内部的static int name形式的,默认初始化为0

  11. 外部变量可以供其所在的程序文件中的任何函数使用 — 错误

    • 全局变量也称为外部变量,它是在函数外部定义的变量,其作用域是从定义该变量的位置开始至源文件结束。全局变量不受作用域的影响(也就是说,全局变量的生命期一直到程序的结束)。
      • 如果在一个文件中使用extern关键字来声明另一个文件中存在的全局变量,那么这个文件可以使用这个数据。
      • 在全局变量前加一个static,使该变量只在这个源文件中可用,称之为全局静态变量。
  12. 指针就是地址,因此一个变量的指针就是该变量的地址。请问这句话的说法是正确的吗? 错误

    • 一个变量的指针指向的内容才是这个变量的地址
    • 若已包含标准库头文件及相关命名空间,用户也可以重新定义标准库函数,但是该函数将失去原有含义 – 错误
    • 若已包含标准库头文件及相关命名空间,则系统不允许用户重新定义标准库函数 –正确
    • 用户调用标准库函数前,不必使用预编译命令将该函数所在文件包括到用户源文件中 错误
    • 用户调用标准库函数前,必须重新定义 –错误
      • A选项,函数不能重新定义,只能重载,除非换作用域(那也不能叫重新定义); B选项,函数可以被重载而不能重新定义,重载后函数具有不同的形参,原有定义并不失效; C选项,正确; D选项,调用库函数果断需要#include(预处理包含)头文件啊……否则找不到函数定义
  13. 在 while 循环中以 EOF(-1) 作为文件结束标志,这种以 EOF 作为文件结束标志的文件,必须是文本文件。在文本文件中,数据都是以字符的 ASCII 代码值的形式存放。我们知道, ASCII 代码值的范围是 0~255 ,不可能出现 -1 ,因此可以用 EOF 作为文件结束标志

  14. A 选项正确,int a[10]={0, 0, 0, 0, 0}; 前 5 个元素为 0,后面 5 个元素编译器补为 0
    B 选项正确,int a[10]={ }; 编译器自动将所有元素置零
    C 选项正确,int a[] = {0}; 编译器自动计算元素个数
    D 选项错误,int a[10] = {10*a};a 是整型数组,10*a 操作非法

  15. 排序方法 平均情况 最好情况 最坏情况 辅助空间 稳定性

    冒泡排序 O(n^2) O(n) O(n^2) O(1) 稳定

    选择排序 O(n^2) O(n^2) O(n^2) O(1) 不稳定

    插入排序 O(n^2) O(n) O(n^2) O(1) 稳定

    希尔排序O(n*log(n))~O(n^2) O(n^1.3) O(n^2) O(1) 不稳定

    堆排序 O(nlog(n)) O(nlog(n)) O(n*log(n)) O(1) 不稳定

    归并排序 O(nlog(n)) O(nlog(n)) O(n*log(n)) O(n) 稳定

    快速排序 O(nlog(n)) O(nlog(n)) O(n^2) O(1) 不稳定

    冒泡排序经过优化以后,最好时间复杂度可以达到O(n)。设置一个标志位,如果有一趟比较中没有发生任何交换,可提前结束,因此在正序情况下,时间复杂度为O(n)。选择排序在最坏和最好情况下,都必须在剩余的序列中选择最小(大)的数,与已排好序的序列后一个位置元素做交换,依次最好和最坏时间复杂度均为O(n2)。

    插入排序是在把已排好序的序列的后一个元素插入到前面已排好序(需要选择合适的位置)的序列中,在正序情况下时间复杂度为O(n)。堆是完全二叉树,因此树的深度一定是log(n)+1,最好和最坏时间复杂度均为O(nlog(n))。归并排序是将大数组分为两个小数组,依次递归,相当于二叉树,深度为log(n)+1,因此最好和最坏时间复杂度都是O(nlog(n))。快速排序在正序或逆序情况下,每次划分只得到比上一次划分少一个记录的子序列,用递归树画出来,是一棵斜树,此时需要n-1次递归,且第i次划分要经过n-i次关键字比较才能找到第i个记录,因此时间复杂度是 ∑ i = 1 n − 1 ( n − i ) = n ( n − 1 ) / 2 \sum_{i=1}^{n-1}(n-i)=n(n-1)/2 i=1n1(ni)=n(n1)/2,即 O ( n 2 ) O(n^2) O(n2)

  16. 算法的5个基本特征:确定性、有穷性、输入、输出、可行性

  17. 顺序查找的平均时间是多少?——N/2

    • 严格意义上确实 应该是(n+1)/2,因为目标数据可以在任意位置,分别查找1,2,3,4,…,n次,一共为n*(n+1)/2,所以除以n,平均时间为(n+1)/2
  18. void hello(int a,int b=7,char* pszC="*");

    • hello(5)

    • hello(5,8)

    • hello(6,&quot;#&quot;)

    • hello(0,0,"#")

    • 参数从右向左匹配,C项 a没有匹配到,&quot;相当于半个引号,&quot ;#&quot;=“#”如上面D选项

      从左到右我们就说参数是 a b c A,函数从右往左匹配。b,c有现有的就不给了,5给a

      B, 8给b,5给a

      C, 那一坨给了c,6给了b, 没东西给a 。不能跳着给。6给a,b用先有的是不对的。

      D,挨个给就行了。

  19. 声明一个指向含有10个元素的数组的指针,其中每个元素是一个函数指针,该函数的返回值是int,参数是int*,正确的是()

    • (int *p[10])(int*)
      
    • int [10]*p(int *)
      
    • int (*(*p)[10])(int *)
      
    • int ((int *)[10])*p
      
    • 先看未定义标识符p,p的左边是*,p表示一个指针,跳出括号,由于[]的结合性大于,所以p指向一个大小为10的数组,即(*p)[10]。左边又有一个号,修释数组的元素,(*p)[10]表示*p指向一个大小为10的数组,且每个数组的元素为一个指针。跳出括号,根据右边(int *)可以判断((*p)[10])是一个函数指针,该函数的参数是int*,返回值是int。所以选C。

  20. C语言中的一个变量可以被定义为两个或多个不同的类型。请问这句话的说法是正确的吗?

    • 如果不在一个函数体中,可以的;但是如果在一个函数体中,会出现调用混淆,不允许。

      而且这是两个变量,只是变量名相同而已,被存在不同的内存单元中。

    • 如果在同一个函数内,那肯定不行,重复的定义,如果一个是全局一个是局部那就可以,还有在两个不同的函数内,也是可以的

  21. 双循环链表中,任意一结点的后继指针均指向其逻辑后继。

    • 逻辑后继:指在存储时按照需要给定的逻辑顺序在其后的数据块。循环链表中尾节点的逻辑后继应该为null而其后继指针指向了头节点。
  22. 递归先序遍历一个节点为n,深度为d的二叉树,需要栈空间的大小为_

    • 因为二叉树并不一定是平衡的,也就是深度d!=logn,有可能d > > logn(远大于),所以栈大小应该是O(d)
  23. 缓存策略中基于 LRU 的淘汰策略,在缓存满时,会把最近进入缓存的数据先淘汰,以保持高的命中率

    • 刚好说反了,LRU的过程如下(其实很好理解,访问的频率越高越不该丢弃):

      ​ 1. 新数据插入到链表头部;

      ​ 2. 每当缓存命中(即缓存数据被访问),则将数据移到链表头部;

        3. 当链表满的时候,将链表尾部的数据丢弃。
      
  24. 顺序表删除需要移动元素,而链表删除不需要移动元素

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值