前言:
先在这里和关注我的小伙伴们说一声对不起,因为我已经连续三天没更新文章了。是因为学校的线上课程结束了,线下几乎每一天都是满课,写博客的时间少了很多,不过我会在五一假期期间尽量把之前的补回来。
这个分栏是对《C陷阱与缺陷》这本书的介绍以及我对这本书的理解,希望可以帮助到大家,另外有什么不足的地方还请指出,毕竟一千个读者就有一千个哈姆雷特。
❤️ 温馨提示:
不要整天坐在电脑前,还是要多多运动。定时眺望一下远方和散散步对眼睛和身体好,我们绝不以牺牲自己的健康,来换取自己技术的提升。
目录
⚾️一、=不等于==
1️⃣区分
2️⃣陷阱与缺陷
3️⃣代码示例
4️⃣x == 2 与 2 == x
⚽ 二、& 和 | 不等于 && 和 ||
✏️三、词法分析中的“贪心法”
1️⃣陷阱与缺陷
2️⃣老版本的C语言=+代替+=的一系列问题
3️⃣代码示例
4️⃣y = x/(*p)和y = x出现为止(*/被规定为C语言注释的结束符)。就是说,此语句会直接将x赋给y,而直接忽视了后面的p。
正确写法:
int y = x / *p;
2️⃣老版本的C语言=+代替+=的一系列问题
在老版本的C语言中允许使用=-来代替现在-=的含义。
例1:
a=-1;
当前语句的含义是将-1赋给变量a,然而在老版的C语言中会被理解成将a-1的值赋给a。
也就是这样写:
a =- 1;
也可以理解为:
a = a - 1;
如果程序员的原意是要将-1的值赋给a,还是要加上空格隔开才不会出现意料之外的情况。
也就是这样写:
a = -1;
例2:
a=/*b;
因为在书中也没有指出这条语句的含义是什么,而且我也看不懂,所以就不能给予解答了。不过我猜可能是将a除以a与b的乘积得到的值赋给a。如果大家有什么不同的见解,可以一起在评论区讨论。
尽管/*看上去像是一段注释的开始,但是在老版C语言中会把当前代码当做:
a =/ *b;
当前语句的含义是将a除以b所指向的值,再把商赋给a。
值得一提的是这种老版本的编译器还会将复合赋值视为两个符号,因而可以毫无疑问的处理
a >> = 1;
而一个严格的ANSI C 编译器则会报错。
根据以上的例子,可以很容易的看出加或不加空格对程序结果的影响是非常大的。
3️⃣代码示例
#include
int main()
{
int a = 10;
int b = 20;
int c = a+++b;//含义是将a+b的值赋给c,然后a再自加1
printf("a = %dn", a);
printf("b = %dn", b);
printf("c = %dn", c);
return 0;
}
#include
int main()
{
int a = 10;
int b = 20;
int c = a++ + b;//含义是将a+b的值赋给c,然后a再自加1
printf("a = %dn", a);
printf("b = %dn", b);
printf("c = %dn", c);
return 0;
}
显而易见加空格的写法可以更加清晰表达出想要表示的含义。
4️⃣y = x/(*p)和y = x/*p
当我们要编写诸如y = x/*p此类的代码时,可能会因为一些原因忘记加空格。可以使用圆括号将*p括起来, 因为这样写即使是忘记加空格了。代码也不会报错,并且也会得到我所希望得到的结果。
✒️四、整型常量
如果一个整形常量的第一个字符是数字0,那么该常量将被视作八进制数。因此,10与010的含义截然不同。此外,许多C编译器会把8和9也作为八进制数字处理。这样多少有点奇怪的处理方式来自八进制的定义。例如,0195的含义是1*(8*8)+9*(8*1)+5*(8*0),也就是141(十进制)或者0215(八进制)。我们当然不建议这种用法,ANSI C标准也禁止这种用法。
需要注意以下这种情况,有时候在上下文中为了格式对齐的需要,可能无意间将十进制数写成了八进制数 ,例如:
struct{
int part_bunber;
char* description;
}parttab[] = {
046, "left-handed widget" ,
047, "right-handed widget" ,
125, "frammis"
};
⏰五、字符与字符串
C语言中的单引号和双引号含义迥异,在某下情况下如果把两者弄混,编译器并不会报错,从而在运行时产生难易预料的结果。
⚠️
注意:用单引号括起来的叫字符,用双引号括起来的叫字符串。
例1:
printf("hello worldn");
当前语句是输出一个字符串“hello worldn”,需要注意的是中间的空格也算一个字符。
例2:
char hello[] = { 'h', 'e','l','l','o',' ', 'w', 'o', 'r', 'l', 'd', 'n' };
当前语句是将一个一个的字符放到hello数组里面,但是这两个例子都可以将hello world输出。
1️⃣陷阱
因为用单引号括起的一个字符代表一个整数,而用双引号括起的一个字符代表一个指针,如果两者混用,那么编译器的类型检查功能将会检测到错误。
例如:
char* slash = '/';
在编译的时候将会生成一条错误的信息,因为'/'并不是一个字符指针。然而有些编译器对函数参数并不会进行类型检查,特别是printf函数的参数。也就造成了下面的现象。
如果用
printf('n');
来代替正确的
printf("n");
则会在程序运行的时候产生难以预料的错误,而不会给出编译器诊断信息。在后面的内容还会对此问题进行更加详细的讨论。
译注:现在的编译器一般能够检测到在函数调用时混用单引号和双引号的情形。
整型数(一般为16位或32位)的存储空间可以容纳多个字符(一般为8位),因此有的C编译器允许一个字符常量(以及字符串常量)中包括多个字符。也就是说,用'yes'代替 "yes" 不会被该编译器检测到。后者(即"yes")的含义是 “依次包含y'、'e'、's'以及空字符 '的4个连续内存单元的首地址”。前者(即'yes')的含义并没有准确地进行定义,但大多数C编译器理解为,“一个整数值 由'y'、'e'、's'所代表的整数值按照特定编译器实现中定义的方式组合得到”。因此,这者如果在数值上有什么相似之处,也完全是一种巧合而已。
2️⃣代码示例
如图代码顺利打印出了字符串
可以看到使用单引号并没有成功打印字符串
⌚六、练习
⛳练习 1-1
某些编译器允许嵌套注释。请写一个测试程序,要求无论是对嵌套注释的编译器,还是对不允许嵌套注释的编译器,该程序都能正确通过编译(无错误消息出现),但是这两种情况下程序执行的结果却不相同。提示:在双引号括起的字符串中,注释符 /* 属于字符串的一部分,而在注释中出现的双引号 '' '' 又属于注释的一部分。
⛳练习 1-2
如果由你来实现一个C编译器,你是否会允许嵌套注释?如果你使用的C编译器允许嵌套注释,你会用到编译器的这一特性吗?你对第二个问题的回答是否会影响到你对第一个问题的回答?
⛳练习 1-3
为什么 n-->0的含义是 n-- > 0,而不是n- -> 0?
⛳练习 1-4
a+++++b的含义是什么?
对于以上的问题大家可以在评论区讨论



