《C陷阱与缺陷》读书笔记(一)

目录

[一、= 不同于 == :](#一、= 不同于 == :)

[二、& 和 | 不同于 && 和 || :](#二、& 和 | 不同于 && 和 || :)

三、词法分析中的贪心法:


一、= 不同于 == :

对于C语言中的"="和"==",它们虽然只相差了一个等号,但是含义确实千差万别。"="在C语言中是非常常用的操作符,它的含义是将右边的值赋值给左边的值,这个过程要求左值是可以更改的。而"=="的含义是比较两个数字(或者字符)是否相等,通常用于逻辑判断语句中。

而我们虽然知道它们的含义,但还是有可能在编写代码时将它们写错的,比如书中给出了几个例子:

cpp 复制代码
if (x = y)
    break;

//注意,C语言不允许if语句中使用break关键字
//所以我推测这段代码应该是循环语句中的一部分

上面代码本意是判断x和y是否相等,如果它们相等,就跳出循环,但是却将"=="误写成了"=",这样一来if条件判断语句永远为真(赋值语句的结果恒为真),所以就会跳出循环,没有判断x和y相等这一过程。那么想避免这种情况的发生,其实也是可以的,我们可以利用赋值操作的左值必须可更改,在判断是否相等时将左值变成不可更改的操作数,比如下面的代码:

cpp 复制代码
const int x = 0;
int y = 0;
//我将上面代码的x和y进行了定义,并且将x设定成const修饰的常变量

//如果这样写,程序就会报错,因为x不可更改
if (x = y)
    break;

//当我们看到报错信息,就知道是这里写错了,所以就会及时改正
if (x == y)
    break;

//同时,再进行与常量相比较的操作中,也可以采用这种方法
10 = x;//这样写就会报错
10 == x;

下面看书中的下一个例子:

cpp 复制代码
while (c = ' ' || c == '\t' || c == '\n')
    c = getc(f);

这个代码中误将"=="写成了"=",导致最开始是为c赋值为空字符串,那这行语句恒为真,所以"||"运算符有一个短路现象,它就不会向下执行,也就不会判断c是否等于"\t"和"\n"了。同时既然"||"不会向下判断,那while循环的条件也恒为真,该循环很可能是死循环。

对于这种误写,可以采用上面的方法,将字符放在左值,这样遇到"="时就会报错。或者还可以将"=="判断改成"!="判断,之后逻辑上取反即可,这样写也未尝不可,只是有时候逻辑上会显得有点绕,比如下面的代码:

cpp 复制代码
//本来是如果x和y相等就进入语句,但是为了防止==写成=,可以采用!=替代==
if (x == y)
    break;

//使用!=替代==,这样当x与y不相等的逻辑反(实际上还是x与y相等)就进入语句
if (!(x != y))
    break;

//上面的while循环也可以这样改写,并且我习惯将每个逻辑判断部分加上括号
while (!(c != ' ') || !(c != '\t') || !(c != '\n'))
    c = getc(f);

虽然上面代码可以解决将"=="写成"="的情况,但是我们在编程时还要多多细心才好,万一漏写了逻辑反符号呢对吧。另外,如果是错写了函数名或变量名还好,编译器会报错,那要是错写或漏写操作符的话,可能会导致各种问题,比如程序死循环、栈溢出、越界访问,这都不太致命,最致命之一的是运行结果与期望结果不符。

另外,像我上面那样多加括号也是一种编程的好习惯,这样不仅在观感上清晰明了,也可以避免因操作符优先级而带来的一系列问题。

下面看看将"="误写成"=="的代码:

cpp 复制代码
if ((filedesc == open(argv[i], 0)) < 0)
    error();

这段代码本意是将函数的返回值赋给变量filedesc,可结果却是比较filedesc与open()函数的值,之后使用比较结果与0进行比较。open()函数如果执行成功将返回0或者正数执行失败将返回-1,这样通过open()函数的返回值与0比较(实际是先将返回值赋值给filedesc之后再进行比较),当open()函数执行失败后会利用error()函数打印错误信息。

那对于这个代码该如何处理呢?我想到的办法是将filedesc初始化为-1(我发现书中的一个不太好的习惯就是不进行初始化),并且不和0比较,代码如下:

cpp 复制代码
const int filedesc = -1;

if (filedesc == open(argv[i], 0))
    error();

我们看open()函数的返回值无非是-1和0(或正数),如果返回-1,刚好与filedesc相等,进入if语句,否则不进入。当filedesc定义为const的常变量时,即使误写成了"=",程序也会报错提醒。当然,原来的代码本意应该是使用filedesc去保存open()函数返回的值,我该的代码直接判断open()是否执行正确了,所以还不如写成下面这样:

cpp 复制代码
const int filedesc = open(argv[i], 0);

if (!filedesc)
    error();

注意啊,这样写代码有一个好处就是变量filedesc直接初始化为open()函数的返回值,因此如果误将"="写成"==",程序就会报错。

二、& 和 | 不同于 && 和 || :

对于C语言中的位操作符和逻辑操作符,这其实很好区分。"&"可以翻译为"and","|"可以翻译为"or",所以它们表示的是二进制的按位与和按位或。"&&"和"||"是逻辑与和逻辑或,与"&"和"|"没有关系。当然如果考虑到再进行逻辑判断时,将"&&"和"||"误写成"&"和"|",这样的话可能会得到一个比较奇怪的结果,我也想不到了如果误写后的改正方法。

三、词法分析中的贪心法:

对于这一部分呢,书中的阐述还是一些使用操作符时的歧义问题,比如看下面代码:

cpp 复制代码
int a = 0, b = 0, x = 0, y = 0, p = 0;

//如果写成这样a---b,就会产生歧义
a - --b;
a-- - b;

y = x /* p;//C语言中的/*被规定为注释的开始
y = x / *p;//这个代码是x除以p的解引用

a = -1;//a赋值
a =- 1;//这行语句在古老的编译器上表示a -= 1,现在已经不支持这种语法了

对于上面产生的一系列问题,我给出的解决办法是不管操作符的优先级和结合性,按照自己的想法加上括号即可,代码如下:

cpp 复制代码
int a = 0, b = 0, x = 0, y = 0, p = 0;

a - (--b);//a减去--b
(a--) - b;//a--减去b

y = x / (*p);//这样就不会被编译器翻译为注释了

a = -1;//a赋值
a -= -1;//a = a - (-1)
a -= 1;//a = a - 1

注:再次强调,一些良好的编程习惯有多写有意义的注释、多加小括号、对代码进行适量的缩进(一般是4个空格)和空行、创建变量最好要进行初始化。

相关推荐
咩咩觉主16 分钟前
C# &Unity 唐老狮 No.7 模拟面试题
开发语言·unity·c#
大丈夫在世当日食一鲲16 分钟前
Java中用到的设计模式
java·开发语言·设计模式
卑微小文39 分钟前
2025国内网络反爬新高度:代理IP智能轮换算法揭秘
后端·算法·架构
却道天凉_好个秋1 小时前
c++ 嵌入汇编的方式实现int型自增
开发语言·汇编·c++
33三 三like1 小时前
软件工程画图题
java·开发语言·软件工程
&岁月不待人&2 小时前
Kotlin和Java区别
java·开发语言·kotlin
gallonyin2 小时前
免root运行python保活守护进程supervisor
linux·开发语言·python
tyler-泰勒2 小时前
c++:迭代器的失效
开发语言·c++
白晨并不是很能熬夜2 小时前
【JVM】字节码指令集
java·开发语言·汇编·jvm·数据结构·后端·javac
IT、木易2 小时前
大白话解释 JavaScript 中的this关键字,它在不同场景下是如何取值的?
开发语言·javascript·ecmascript