C编程语言|《c陷阱与缺陷》1-3章笔记

词法陷阱 1、注意=与==的使用
比较容易犯错的是将==写成了=,除了自己多加注意以外,养成习惯将要比较的常量值放在左边,这样如果写成了=号,编译就会报错。
类似的还有&和&&、|和||。
2、对于多运算符操作的语句,直接用括号来显示运算优先级,使代码易读清晰
语法陷阱 1、理解函数声明

分析的过程:
A、从左开始查看变量或函数名,然后按照优先级顺序依次读取
B、优先级从高到低依次是:
1. 声明中被括号括起来的那部分
2. 后缀操作符:括号()表示这是一个函数,而方括号表示这是一个数组
3. 前缀操作符:星号*表示 “指向…的指针”。
C、如果const和volatile关键字的后面紧跟类型说明符(如int, long等),那么它作用于类型说明符。在其他情况下,const和(或)volatile关键字作用于它左边紧邻的指针星号。
比如singal函数:
【C编程语言|《c陷阱与缺陷》1-3章笔记】void (*signal(int sig, void(*func)(int)))(int);
1、先找到signal名,后面是个括号,先把括号里的东西作为一个整体,表示signal是个函数。
2、signal前面是个星号,表示函数返回一个指针。
3、星号所在括号后面紧跟一个括号,表示指针指向带有一个int型参数并返回void的函数指针
4、signal函数参数则是一个int型及一个指向带有一个int型参数并返回void的函数指针
这里找出共同部分就是指向带有一个int型参数并返回void的函数指针
将该共同部分定义成一个新类型, 则singal函数可声明如下:
typedef void (*handle)(int);
handle signal(int , handle);
2、注意作为语句结束标志的分号是否漏写了。
3、注意switch语句的每个case处理完操作后是否漏写break语句,如果确实不需要,最好添加注释说明
4、习惯if语句代码块用大括号包住,避免出现else语句混乱的问题
语义陷阱 1、指针与数组
1、c语言中只有一维数组,而且数组的大小必须在编译期就作为一个常数确定下来。然后c语言的数组中的元素可以是任何类型的对象,当然也可以是另一个数组。
2、对于一个数组,我们只能够做两件事:确定该数组的大小,以及获得指向该数组下标为0的元素的指针。其他有关数组的操组,哪怕看上去是以数组下标进行运算的,实际上都是通过指针进行的。
3、数组名如果不是用于sizeof的操作数,总是被转换成一个指向该数组的起始元素(指向该数组下标为0的元素)的指针。
4、任何指针都是指向某种类型的变量。如果p指向一个整数,那么p+1指向的是计算机内存中的下一个整数(计算步长以指针所指向的变量类型为单位),在大多数现代计算机中,它都不同于p所指向地址的下一个内存位置。
int calendar[12][31]; // 声明一个指向该数组的指针 // 1、首先得到该一维数组的元素是有着31个整型元素的数组 // 2、声明指向上述元素类型的指针 int (*p)[31]; p = calendar; <==> p = &calendar[0] p+1指向的就是&calendar[1]

2、非数组的指针
1、尤其要注意strlen、strcpy、strcmp等字符串相关函数其传入的参数不能是空指针
2、用strncpy在做拼接字符串的时候,尤其注意”%s”所对应的字符串是不是一定以‘\0’结尾的,否则会出现越界的情况。
3、用strlen来计算长度并malloc动态申请空间,考虑要多申请一个字节用于存储’\0’,因为strlen是不计算’\0’字符的
4、注意内存的申请和释放
3、作为参数的数组声明
前面说过数组名除在sizeof操作符情况下,都作为第一个元素的起始地址,当数组名作为参数传递函数的时候,实际与将数组第一个元素的地址作为参数传递的作用是一样的,如
char hello[] = “hello”;
printf(“%s\n”, hello); <==> printf(“%s\n”, &hello[0]);
因此,数组作为函数参数将毫无疑义(这个我真有点理解不过来…)。所以c语言中会自动将作为参数的数组声明转换为相应的指针声明。如:
int strlen(char s[]); <==> int strlen(char *s);
但是
extern char *hello; 和 extern char hello[]; 是完全不同的
如果一个指针参数并不实际代表一个数组,即使从技术上而言是正确的,采用数组形式的记法经常会起误导作用。
4、指针在使用前一定要判断是否为空指针
5、边界计算与不对称边界
1、两个原则:
  • 首先考虑最简单情况下的特例,然后将得到的结果外推
  • 仔细计算边界,绝不掉以轻心
2、不对称边界
比如要定义一个拥有10个元素的数组,那么0就是数组下标的第一个“入界点”(指处于数组下标范围以内的点,包括边界点),而10就是数组下标中的第一个“出界点”(指不在数组下标范围以内的点,不含边界点),为此我们这样写:
int a[10], i;
for (i = 0; i < 10; i++)
而不是写成下面这样:
int a[10], i;
for (i = 0; i <= 9; i++)
3、–n >= 0 与 n– > 0 是等效的
前者是先将n进行减1,再进行判断;后者是将n保存,再将n减1,然后在将原先保存的值做比较
6、求值顺序
C语言中只有四个运算符(&&、||、?:和,)存在规定的求值顺序。
  • 运算符&&和||首先对左侧操作数求值,只在需要时才对右侧操作数求值
  • 运算符?:有三个操作数:在a?b:c中,a先被求值,再根据a的值求操作数b或c
  • 逗号运算符,首先对左侧操作数求值,然后该值被“丢弃”,再对右侧操作数求值
其他运算符对其操作数的求值顺序时未定义的,尤其时赋值运算符
7、整数溢出
这一话题建议多搜索看看,比如 C语言的整型溢出问题
8、为函数提供返回值

    推荐阅读