目录
[一、= 不同于 == :](#一、= 不同于 == :)
[二、& 和 | 不同于 && 和 || :](#二、& 和 | 不同于 && 和 || :)
一、= 不同于 == :
对于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个空格)和空行、创建变量最好要进行初始化。