您的位置:首页 > 编程语言 > C语言/C++

C语言陷进和缺陷学习心得

2010-08-02 00:16 239 查看
1 词法缺陷
1.1= 不是 ==
一些 C 编译器会对形如 e1 = e2 的条件给出一个警告以提醒用户。当你趋势需要先对一个变量进行
赋值之后再检查变量是否非零时,为了在这种编译器中避免警告信息,应考虑显式给出比较符。换句话说,
将:
if(x = y)
foo();
改写为:
if((x = y) != 0)
foo();
这样可以清晰地表示你的意图。

1.2多字符记号
一些 C 记号,如/、*和=只有一个字符。而其他一些 C记号,如/*和==,以及标识符,具有多个字
符。当 C编译器遇到紧连在一起的/和*时,它必须能够决定是将这两个字符识别为两个分离的记号还是一
个单独的记号。 C语言参考手册说明了如何决定: “如果输入流到一个给定的字符串为止已经被识别为记号,
则应该包含下一个字符以组成能够构成记号的最长的字符串”。因此,如果/是一个记号的第一个字符,并
且/后面紧随了一个*,则这两个字符构成了注释的开始,不管其他上下文环境。
下面的语句看起来像是将 y 的值设置为 x的值除以 p所指向的值:
y = x/*p /* p 指向除数 */;
实际上,/*开始了一个注释,因此编译器简单地吞噬程序文本,直到*/的出现。换句话说,这条语句仅仅
把 y的值设置为 x的值,而根本没有看到 p。(这个问题在智能一点的编辑器里面最容易发现,因为注释部分会变成其他颜色...)将这条语句重写为:
y = x / *p /* p 指向除数 */;
或者干脆是
y = x / (*p) /* p 指向除数 */;
它就可以做注释所暗示的除法了。
这种模棱两可的写法在其他环境中就会引起麻烦。例如,老版本的 C 使用=+表示现在版本中的+=。
这样的编译器会将
a=-1;
视为
a =- 1;

a = a - 1;
这会让打算写
a = -1;
的程序员感到吃惊。
另一方面,这种老版本的 C 编译器会将
a=/*b;
断句为
a =/ *b;
尽管/*看起来像一个注释。

1.3 字符串和字符
使用一个指针来代替一个整数通常会得到一个警告消息(反之亦然),使用双引号来代替单引号也会
得到一个警告消息(反之亦然)。但对于不检查参数类型的编译器却除外。因此,用
printf('/n');
来代替
printf("/n");
通常会在运行时得到奇怪的结果。
由于一个整数通常足够大,以至于能够放下多个字符,一些 C 编译器允许在一个字符常量中存放多个
字符。这意味着用'yes'代替"yes"将不会被发现。后者意味着“分别包含 y、e、s 和一个空字符的四个
连续存贮器区域中的第一个的地址”,而前者意味着“在一些实现定义的样式中表示由字符 y、e、s 联合构
成的一个整数”。这两者之间的任何一致性都纯属巧合。
int s='yes';
int d='y'+'e'+'s';
printf("%d/n",s);
printf("%d",d);
结果7955827/*这个结果的意义是什么?*/
337

2 句法缺陷
2.1 理解声明
float *g(), (*h)();
表示*g()和(*h)()都是 float 表达式。由于()比*绑定得更紧密,*g()和*(g())表示同样的东西:g
是一个返回指 float 指针的函数,而 h是一个指向返回 float 的函数的指针。 (*h)())中的
括号是必须的,否则这个表达式将会被分析为*(h()),函数调用比一元运算符绑定得更紧密。
一元运算符是右结合的,因此*p++表示*(p++),而不是(*p)++。

2.2 运算符并不总是具有你所想象的优先级
函数调用比一元运算符绑定得更紧密所以(*h)())中的括号是必须的,否则这个表达式将会被分析为*(h())。一元运算符是右结合的,因此*p++表示*(p++),而不是(*p)++。
在接下来是真正的二元运算符。其中数学运算符具有最高的优先级,然后是移位运算符、关
逻辑运算符、赋值运算符,最后是条件运算符。需要记住的两个重要的东西是:
1. 所有的逻辑运算符具有比所有关系运算符都低的优先级。
2. 一位运算符比关系运算符绑定得更紧密,但又不如数学运算符。
在这些运算符类别中,有一些奇怪的地方。乘法、除法和求余具有相同的优先级,加法和减法具有相
同的优先级,以及移位运算符具有相同的优先级。
还有就是六个关系运算符并不具有相同的优先级:==和!=的优先级比其他关系运算符要低。这就允许
我们判断 a和 b是否具有与 c和 d相同的顺序,例如:
a < b == c < d
在逻辑运算符中,没有任何两个具有相同的优先级。按位运算符比所有顺序运算符绑定得都紧密,每
种与运算符都比相应的或运算符绑定得更紧密,并且按位异或(^)运算符介于按位与和按位或之间。
三元运算符的优先级比我们提到过的所有运算符的优先级都低。这可以保证选择表达式中包含的关系
运算符的逻辑组合特性,如:
z = a < b && b < c ? d : e
这个例子还说明了赋值运算符具有比条件运算符更低的优先级是有意义的。另外,所有的复合赋值运
算符具有相同的优先级并且是自右至左结合的,因此
a = b = c

b = c; a = b;
是等价的。
具有最低优先级的是逗号运算符。这很容易理解,因为逗号通常在需要表达式而不是语句的时候用来
替代分号。
赋值是另一种运算符,通常具有混合的优先级。例如,考虑下面这个用于复制文件的循环:
while(c = getc(in) != EOF)
putc(c, out);
这个 while 循环中的表达式看起来像是 c被赋以 getc(in)的值, 接下来判断是否等于 EOF 以结束循环。
不幸的是,赋值的优先级比任何比较操作都低,因此 c的值将会是 getc(in)和 EOF比较的结果,并且会
被抛弃。因此,“复制”得到的文件将是一个由值为 1的字节流组成的文件。
上面这个例子正确的写法并不难:
while((c = getc(in)) != EOF)
putc(c, out);

2.3 看看这些分号!
struct foo {
int x;
}

f() {
...
}
在紧挨着 f的第一个}后面丢失了一个分号。它的效果是声明了一个函数 f,返回值类型是 struct foo,
这个结构成了函数声明的一部分。如果这里出现了分号,则 f 将被定义为具有默认的整型返回值。

3 语义缺陷
3.1 表达式求值顺序
C中所有其它的运算符对操作数的求值顺序都是未定义的。事实上,赋值运算符不对求值顺序做出任
何保证。
出于这个原因,下面这种将数组 x中的前 n个元素复制到数组 y中的方法是不可行的:
i = 0;
while(i < n)
y[i] = x[i++];
其中的问题是 y[i]的地址并不保证在 i增长之前被求值。在某些实现中,这是可能的;但在另一些实现
中却不可能。另一种情况出于同样的原因会失败:
i = 0;
while(i < n)
y[i++] = x[i];
而下面的代码是可以工作的:
i = 0;
while(i < n) {
y[i] = x[i];
i++;
当然,这可以简写为:
for(i = 0; i < n; i++)
y[i] = x[i];

3.2 C 并不总是转换实参
下面的程序段由于两个原因会失败:
double s;
s = sqrt(2);
printf("%g/n", s);
第一个原因是 sqrt()需要一个 double值作为它的参数,但没有得到。第二个原因是它返回一个
double 值但没有这样声明。改正的方法只有一个:
double s, sqrt();
s = sqrt(2.0);
printf("%g/n", s);
C中有两个简单的规则控制着函数参数的转换:(1)比 int 短的整型被转换为 int;(2)比 double
短的浮点类型被转换为 double。所有的其它值不被转换。确保函数参数类型的正确行使程序员的责任。
因此,一个程序员如果想使用如 sqrt()这样接受一个 double 类型参数的函数,就必须仅传递给它
float 或 double类型的参数,没有更进一步声名的函数被假设返回 int。常数 2 是一个 int,因此其类型是错误的。

3.3 移位运算符
如果待移位的数长度为 n,则移位的数量必须大于等于 0 并且严格地小于 n。因此,在一次单独的操作中不可能将所有的位从变量中移出。
例如,如果一个 int是 32 位,且 n是一个 int,写 n << 31 和 n << 0是合法的,但 n << 32和 n << -1是不合法的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: