该文章为笔记,因此许多内容摘抄自《C陷阱与缺陷》。
《C陷阱与缺陷》,全书不厚,但是感觉十分有提醒与启迪作用,值得阅读。是打好良好C语言基础的必读手册之一。
1.1 =不同于==
C语言使用 = 作为赋值运算,使用 == 作为比较运算。这两者有巨大的差别。当我们在实际使用的时候,往往会出现如下的错误。
// example 1
if(x = y)
break;
我们本意是用x与y来比较从而来控制程序的进退,但是这里将会进入一个死循环。
当我们误将比较运算写成赋值运算的时候,会出现意想不到的问题。
//example 2
while(c = ' ' || c == '\t' || c == '\n')
c = getc(f);
本例中的循环语句的本意是跳过文件中的空格符、制表符和换行符。
由于在比较 ' ' 和变量c的时候,误将比较运算符==写成了赋值运算符= 。因为赋值运算符=的优先级低于逻辑运算符 || ,因此实际上是将以下表达式的值赋值给了c。
' ' || c == '\t' || c == '\n'
' ' 永远为真,所以上述表达值的求值永远为1,所以程序将会进入一个四循环中。
当我们把比较运算符误写成赋值运算符的时候,同样将会出现编译器无法捕捉的错误。
if((filedesc == open(argv[i], 0)) < 0)
error();
本例中,如果open函数执行成功,将会返回0或正数,执行失败,将会返回-1。本例的本意是将open的返回数与filedesc比较,二者比较的结果与0相比判断程序是否错误。暂时不思考filedesc中存储的是什么值,比较运算符==的最后结果只可能是0或-1,因此结果永远都不可能小于0,因此这个error函数,将没有被机会执行。
这是一个隐藏的错误,编译器不会报任何错误,因为在它看来,这是一个符合规则的合法程序。
1.2 & 和 | 不同于 && 和 ||
这里的具体内容,将在稍后补充。
1.3 词法分析中的“贪心法”
原则为每个符号应该包含尽可能多的字符。编译器从左到右读取每一个字符,读了一个再读一个判断是否可以构成一个有意义的字符。我们可以看下面这个例子。
y = x/*p;
大家看颜色可以发现,读到/,再读下一个,发现是,因此编译器认为这个是注释的开头,因此接下里编译器就不断的往下读取,直至出现/为止。
因此,这里推荐,抓不准的时候,请一定要加上括号。
y = x/(*p);
然后我们看下比较变态的例子。
#include <stdio.h>
int main(void){
int a = 3;
int b = 5;
int sum = a+++b;
printf("%d,%d,%d\n",a,b,sum);
}
//4,5,8
最后输出4,5,8。这里做一个词法分析。
1)读入a,读入下一个
2)读入+,读入下一个
3)读入+,两个+,当成自增符号++使用,左结合给a,因此左边为a++
4)读入+,读入下一个
5)读入b,前面的+当作加法来算
因此就是(a++)+b。
假设我们往a+++b里多加几个+,变成诸如a+++++b,这样编译器会报错。因为a++ 后在读入两个+,编译器认为是自增符号因此左结合给a++,然而a++后不能在自增了,因此程序报错。
但是如果我们添加空格,比如这样a+++ ++b,这样就可以编译通过了,因为编译器在读取第三个+后,再往后读取一个,发现是空格,所以就把第三个+当作加号来使用,编译就通过了。
这些题,完全没有意义。建议使用的时候加括号,然后部门中如果出现这么写的人,建议早点把他辞退。
1.4 整型常量
这里需要注意,如果一个整型常量的第一个字符是数字0,那么该常量将被视作八进制数。
1.5 字符与字符串
用单引号引起的一个字符实际上代表一个一个整数,整数值对应于该字符在编译器采用的字符集中的序列值。
用双引号引起的字符串,代表的却是一个指向无名数组起始字符的指针。
这里只简单举一个例子。
# include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
char str1 = 'A';
int str2 = 65;
cout<<str1<<endl<<(char) str2<<endl;
return 0;
}
最后会输出两个A。
网友评论