《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个空格)和空行、创建变量最好要进行初始化。

相关推荐
缘友一世11 分钟前
从广义线性回归推导出Softmax:理解多分类问题的核心
算法·机器学习·分类
firshman_start17 分钟前
第六章,BGP---边界网关协议
开发语言·网络·php
山海风z28 分钟前
力扣 : 871. 最低加油次数
c语言·数据结构·c++·算法·leetcode
依旧阳光的老码农44 分钟前
Qt SQL 核心类说明文档
开发语言·sql·qt
搏博1 小时前
神经网络的基本概念与深度解析——基于生物机制的仿生建模与工程实现
人工智能·深度学习·神经网络·学习·算法·机器学习
小梦白1 小时前
RPG7.准备GAS的工作
java·开发语言
武昌库里写JAVA1 小时前
【iview】icon样式
java·开发语言·spring boot·学习·课程设计
-XWB-2 小时前
【Java】打印运行环境中某个类引用的jar版本路径
java·开发语言
Cuit小唐2 小时前
C++ 单例模式详解
开发语言·c++·单例模式
正在走向自律3 小时前
Python面向对象编程实战:从类定义到高级特性的进阶之旅(2/10)
开发语言·python·面向对象·python基础知识