1.词法常犯错误
1.1=和==的区别
很多刚学C语言的朋友,最容易犯的一个错误就是=和==混淆,本意是作比较运算符,却无意中误写成了赋值运算
例如:
本意是判断x是否等于y
if(x=y)
break;
结果却是将y的值赋值给了x,然后检查该值是否等于零
1.2看似相同却另有乾坤
除了字符串和字符常量,符号的中间不能嵌有空白(空白符、制表符和换行符)
==//单个字符
= =//两个字符
例如:
也许你想表达的是用x除以p所指向的值,把所得的尚再赋值给y,实际却是/被编译器理解为一段注释的开始,直到遇到/出现才停止
y=x/*p
应该将其改写为
y=x/(*p) //p指向除数
这样才能达到你想要的效果
1.3字符和字符串
单引号括起的一个字符代表一个整数,而双引号括起来的一个字符代表一个指针
2.语法常犯错误
2.1运算符的优先级问题
C语言运算符优先级问题(由上至下,优先级依次递减)
运算符 | 结合性 |
---|---|
() [] -> . | 自左向右 |
! ~ ++ – - (type) * & sizeof | 自右向左 |
* / % | 自左向右 |
+ - | 自左向右 |
<< >> | 自左向右 |
< <= > >= | 自左向右 |
== != | 自左向右 |
& | 自左向右 |
^ | 自左向右 |
| | 自左向右 |
&& | 自左向右 |
|| | 自左向右 |
?: | 自右向左 |
assignments | 自右向左 |
, | 自左向右 |
因为函数调用优先级高于单目运算符的优先级,如果p是一个函数指针,要调用p所指向的函数,应该写成
(*p)()
如果写成
*p()
编译器会解释成
*(p())
++运算符的优先级是自右向左,因此
*p++
会被编译器解释为
*(p++)
即取指针p所指向的对象,然后将p递增1
而不是
(*p)++
即取指针p所指向的对象,然后将该对象递增1
重点:
1.任何一个逻辑运算符的优先级低于任何一个关系运算符
2.移位运算符的优先级比算术运算符要低,但是比关系运算符要高
2.2语句结束标志的分号
如果不小心多加了一个分号,可能会产生意想不到的后果。
如:
- 这个分号也许会被视作一个不会产生任何实际效果的空语句
- 编译器可能会报警告
- 如果在if或者while语句之后需要紧跟一条语句时,如果此时多了一个分号,原来紧跟在if或者while子句之后的语句就是一条单独的语句,与条件判断部分没有任何关系了
2.3“悬挂”else引发的问题
if(x==0)
if(y==0) error();
else
z=x+y;
f(&z);
这段代码本意是应该有两种情况:
x等于0以及x不等于0.对于x等于0的情形,除非y也等于0(此时调用函数(error),否则程序不作任何处理;
对于x不等于0的情形,程序首先将x与y之和赋值给z,然后以z的地址为参数来调用函数f
然而这段代码实际上所做的却与我们想要的结果相差很大,原因在于C语言中有这样的规则,即else始终与同一对括号内最近的未匹配的if结合
正确的写法应该是
if(x==0){
if(y==0)
error();
}
else
{
z=x+y;
f(&z);
3.语义常犯错误
3.1边界计算和不对称边界
在C语言中,一个拥有n个元素的数组,却不存在下标为n的元素,其元素的下标范围是从0到n-1
3.2求值顺序
C语言中其他所有运算符对其操作数求值的顺序是未定义的,特别是赋值运算符并不能保证任何求值顺序
下面这种从数组x中复制前n个元素到数组y中的做法是不正确的,因为它对求值顺序做了太多的假设
i=0;
while(i<n)
y[i]=x[i++];
上面的代码假设y[i]的地址将在i的自增操作执行之前被求值,这一点并没有任何保证!在C语言的某些实现上,有可能在i自增之前被求值,而在另外一些实现上,有可能与此相反。同理,下面这种版本的写法与前类似
f(x,y)
x和y的求值顺序是未定义的
g((x,y))
确定先x后y的顺序
3.3运算符&&、||和!
C语言中的两类逻辑运算符,某些时候可以互换,按位运算符&、|、和~,以及逻辑运算符&&、||、!。有时候你发现程序依旧可以照常运行,其实这都是巧合而已。
按位运算符&、|和~对操作数的处理方式是将其视作一个二进制的位序列,分别对其每个位进行操作
逻辑运算符&&、||、!对操作数的处理方式是:要么将其视作“真”,要么将其视作“假”。
因此,我们能够很容易求得这个表达式的结果:!10的结果是0,因为10是非0数,10&&12的结果是1,因为10和12都不是0;10||12的结果也是1,并且12根本不会被求值
3.4整数溢出
当两个操作数都是有符号操作数时可能发生溢出,而溢出的结果是未定义的