1. 一个枚举数据类型的每个枚举常量都表示一个特定的值,如果不是隐式地由枚举值列表中位置所决定,就是显式地使用一个常量表达式来初始化。由位置决定时,初始值是0。枚举中不同的常量,可以有相同的值。
2. 编译器隐式地将一个具有数组类型的表达式(比如数组名)转换成指向数组第一个元素的指针。只有在下面的情况下,数组表达式才不会被转换成指针:1)当数组是sizeof运算符的操作数时(获得的是数组的实际长度);2)当数组是地址运算符&的操作数时;3)当字符串字面值被用来初始化一个char数组时。关键字sizeof 求值是在编译的时候。
3. 左值是用来表示一个对象的表达式,最简单的左值就是变量名称。右值用来表示一个值,却没有指示一个对象。是否是左值:array[1]:是;&array[1]:否,ptr:是;*ptr:是;ptr+1:否;*ptr+1:否。左值可以被修改,所以一个地址不能做左值。
4. 在产生一个值的过程中,表达式可能会对环境做出其他的改变,这样个改变被称为副作用,诸如变量的值被修改,或者输入或输出流的数据有所变化。在程序的执行期间有一些点,在这些点中,一个特定表达式的所有副作用都会完成,而下一个表达式的副作用尚未发生。在两个连续的序列点之间,可以用任何次序做局部的计算。注意不要在两个连续的序列点之间多次修改任何对象(i=i++;错,两次修改i的值,执行结果无法预料)。在表达式f()+g()中,f()和g()是两个函数,C没有指定那个函数会先进行,所以程序员必须肩负起责任,确保表达式结果和计算的次序无关(array[i]=array[++i];错误,array赋值和i赋值是两个连续的序列点,行为没有定义)。
5. 每个函数都只能被“定义”一次,但一个函数可以被多次“声明”和“调用”。函数无法返回函数或数组。然而,返回值可以是指针,指向一个函数或数组。
6. 如果将一个函数声明为static,则只有在定义该函数的地方函数名才有意义。因为static类型的函数名称不是外部的标识符,所以不可以在其他源代码文件中使用它。默认的函数类型是extern(可不带),表示是一个外部的标识符,可以被外部使用。static在C中相当于java中的private。
7. 如果需要将一个数组当作自变量传入函数,应把参数声明成:类型 名称[]。当把数组名作为函数自变量时,数组名会自动被转换为指针,所以前面的声明等同于:类型 *名称。
8. 和其他函数不同的是,翻译单元只要用到某个inline函数,就必须重复定义此inline函数。编译器必须准备好此函数,以便插入inline的程序代码。因此,inline函数定义经常写在头文件中。如果使用extern来声明一个被定义成inline的函数,那么此函数的定义就是外部的。
9. 如果数组具有自动的生存周期(定义发生在语句块内部,并且不具有static修饰符),那么数组元素的值就是没有定义的。否则,所有的元素都会被初始化为0(如果数组元素是指针,就会被初始化为NULL)。
10. 数组初始化规则:1)如果数组具有静态的生存周期,那么此数组的初始化必须是常量表达式,如果数组具有自动生存周期,那么可以在初始化中使用变量;2)初始化时可以不用指定数组长度,此时数组长度由最后一个数组元素的索引值决定;3)如果指定长度,数组长度由方括号内表达式指定。如果大于初始化个数,则初始化为0或NULL,多出的元素会被忽略。
11. 字符串的长度就是字符的数量,不计结尾的空字符。
12. 当数组作为函数参数时,编译器会将其隐式地转换成“指向数组第一个元素”的指针。对于n维数组也是一样,只不过数组的第一个元素变成了(n-1)维数组,所以在声明中将参数改为(n-1)为指针即可。针对二维数组,参数形式为:int (*val)[NCOLS]。这个声明是一个指针,指向二维数组的第一个元素,每一个元素都是长度为NCOLS的int型一维数组。括号不能去,去掉就变成一个指针数组了。为了使用大于二维的数组,最好利用typedef将(n-1)维数组重命名,这样使程序更容易阅读。
13. 常量指针:int *const name,表示指向固定的变量,但是我们可以修改变量的内容。“指向常量对象”的指针:const int * name,只可以使用这样的指针来读取所指向的对象,不能修改所指向的对象。两种指针的区别是const一个在*前,一个在*后。
14. 可以利用stddef.h中的offsetof函数获得结构体成员和结构起始地址之间的位偏差。利用sizeof获取结构体大小。
15. 存储类修饰符:auto、register、static和extern。存储类修饰符可以修改标识符的链接和对应对象的生存周期。链接涉及一个标识符在一个或多个文件中指向的问题,只有对象和函数标识符可以有外部或内部链接。对象的生存周期涉及在程序执行期间,每个对象在内存中存在时间的问题。一个涉及编译阶段,一个涉及运行阶段。
16. auto。声明中有auto修饰符的对象,具有自动生存周期。这种修饰符只能用在函数内的对象声明。默认情况下,函数内的对象具有自动生存周期,不需要使用auto修饰符。
17. register。当声明对象有自动生存周期时,可以使用register修饰符。但是编译器可能不将对象放入寄存器。但只要被声明为register,对象就不可以被用作“地址运算符”的操作数。函数参数只可以使用register存储类修饰符。
18. static。函数标识符如果被声明为static,就具有内部链接。即别的翻译单元无法使用此标识符来存取此函数。在函数外面声明的,并且使用static存储类修饰符的函数或对象标识符,具有内部链接。函数内部,没有使用extern(static或无存储类标识符)的对象标识符是无链接的。对象被声明为static,就具有静态生存周期。静态生存周期的对象被存储在程序内存的数据区域,寿命覆盖整个程序的执行过程。
19. extern。函数和对象标识符在声明时,如果有extern,就表示该标识符具有外部链接,可以在程序中任何地方使用这些标识符。外部对象有静态的生存周期。对于没有使用存储类修饰符的“函数声明”,编译器会视他们具有extern修饰符(自动可被其他文件调用)。任何对象的标识符只要是声明在函数外部,并且没有存储类修饰符,则默认地具有外部链接。extern 可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,不是本文件定义的,提示编译器遇到此变量和函数时在其他模块中寻找其定义。
20. volatile关键字高中编译器在每次使用此对象的值时,都要重新读取,即使程序本身并没有修改它的值。
21. 你可以声明一个标识符,声明的次数不限制,但是只有“其作用域内的其中一次”有效。声明也是定义:1)如果在声明函数时同时给出函数体;2)如果在对象声明时给此对象分配内存;3)具有初始化的声明也是定义;4)函数体内所有的声明都是定义,除非具有extern存储类修饰符。
22. 函数外面声明一个对象,没有初始化,并且没有extern限定符,这个声明就是“临时性定义”。如果此翻译单元内定义另一个同名的标识符,那么此标识符的临时性定义只是声明。如果此翻译单元内没有另一个同名标识符的定义,那么编译器就会为此临时性定义初始化为0,使其变成正式的定义。
23. 当翻译一个复杂声明符的时候,要先从标识符开始,依次反复执行下面的步骤,直到把声明符内所有符合都翻译出来:1)如果左圆括号(或左方括号)出现在紧邻的右边(只要不包含*都算紧邻),那么翻译整对的圆括号或方括号;2)否则,如果星号*出现在左边,则翻译此星号。声明的一般格式为:[typedef | 存储类修饰符] 类型声明符[, 声明符 [, …]];
24. 具有自动生存周期的对象,如果没有初始化,那么初始值是没有定义的。函数参数(也属于自动生成周期)会被初始化为自变量的值。其他的静态生存周期对象,除非其定义中具有显式的初始化,否则就会被隐式地初始化为0或NULL。
25. malloc不会初始化,calloc会将内存初始化为0.
26. #pragma预处理指令时“提供额外信息给编译器”的标准方法。#pragma pack(n),这导致编译器让结构成员对齐到特定的字节边界。成员对齐有一个重要的条件, 即每个成员按自己的方式对齐. 也就是说虽然指定了按n 字节对齐,但并不是所有的成员都是以n 字节对齐。其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小) 和指定对齐参数(这里是n 字节)中较小的一个对齐,所以有时指定无效。
27. 可以利用预定义的宏:__LINE__和__FILE__来定位程序报错的文件和位置。
28. ctype.h头文件中声明的函数用于对单个字符进行分类和转换操作(isalnum、tolower)。limits.h头文件包含的宏可以表示每个整数类型的“最小可表示值”和“最大可表示值”。
29. 库函数qsort和bsearch分别实现了快速排序和二分查找。
30. 可以利用union 类型数据的特点:所有成员的起始地址一致来判断系统的存储模式。可以定义一个union包括一个int和一个char,给int赋值1,比较int和char的值。
31. typedef的真正意思是给一个已经存在的数据类型(注意:是类型不是变量)取一个别名,而非定义一个新的数据类型。typedef old new,表示将old重命名为new,如果old和new之间有*,则*属于old。
32. 编译器会将注释剔除,但不是简单的剔除,而是用空格代替原来的注释。
33. 按位异或操作可以实现不用第三个临时变量交换两个变量的值:a^=b;b^=a;a^=b。
34. 左移和右移的位数不能大于数据的长度,不能小于0。
35. C 语言有这样一个规则:每一个符号应该包含尽可能多的字符。也就是说,编译器将程序分解成符号的方法是,从左到右一个一个字符地读入,如果该字符可能组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符组成的字符串已不再可能组成一个有意义的符号。所以a+++b被解释为a++ +b。
36. 除法的规则:1),最重要的一点,我们希望q*b + r == a (a/b=q…r),因为这是定义余数的关系。2),如果我们改变a 的正负号,我们希望 q 的符号也随之改变,但 q 的绝对值不会变。3),当b>0 时,我们希望保证r>=0 且r<b 。但是当除数或被除数有一个为负时,上述规则不可能都满足。一般的C编译器选择满足前两条。
37. &a+1获得的是下一个数组地址,a+1获得的是数组a的下一个元素地址。