C
陷阱和缺陷
1、误将十进制数写成八进制
测试:
int i = 10;
int j = 010;
int p = 0x10;
printf("i = %d/n", i);
printf("j = %d/n", j);
printf("p = %d/n", p);
2、’yes’和”yes”的混淆使用
测试:
printf("%c/n", '12');
注解:在BorlandC++V5.5和LCCV3.6种采取的做法是,忽略多余的字符,最后的整数值即第一个字符的整数值。而在VC++6.0和GCCV2.95种采取的做法是,依次用后一个字符覆盖前一个字符,最后得到的整数值即最后一个字符的整数值。
3、不太容易理解的函数声明
测试:
float *p( ), (*h)( );
注解:表达式表示*p( )和(*h)( )是浮点表达式。因为( )的结合优先级高于*,*g( )也就是*(g( ))。
g是一个函数,该函数的返回值类型为指向浮点数的指针。h是一个函数指针,h所指向函数的返回值为浮点类型。
4、函数指针形式的类型转换
测试:
float (*h)( );
à
(float (*)( ));
注解:一旦我们知道了如何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到了:
只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来就可以了。
上面的测试代码中的h是一个指向返回值为浮点类型的函数的函数指针,因此,
(float (*)( ))
表示一个“指向返回值为浮点类型的函数的指针”的类型转换符。
5、(*(void(*)())0)()的语法分解
注解:第一步,假定变量fp是一个函数指针,那么如何调用fp所指向的函数呢?调用方法如下:
(*fp)();
因为fp是一个函数指针,那么*fp就是该指针所指向的函数,所以(*fp)()就是调用该函数的方式。ANSI C标准允许程序员将上式简写成fp(),但是一定要记住,这种写法只是一种简写形式。
第二步,将常数0转型为“指向返回值为void的函数的函数指针”类型。可以这样写: (void(*)( ))0。因此,可以用(void(*)( ))0来替换fp,从而得到(*(void(*)( ))0)( )表达式。
第三步,如果使用typedef来声明,能够使表述更加清晰:
typede void (*funcptr)( );
(*(funcptr)0)( );
6、函数调用小陷阱
与其他程序设计语言不同,C语言要求:在函数调用时即使函数不带参数,也应该包括参数列表。因此,如果F是一个函数,
F();
是一个函数调用语句,而
F;
却是一个什么也不做的语句。更精确地说,这个语句计算函数F的地址,却并不调用该函数。
7、初始化列表陷阱
C语言允许初始化列表中出现多余的逗号,例如:
int days[] = { 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31, };
注解:这样设计的目的是方便代码美化工具的方便,如果换一种方式书写就更容易理解了。
int days[] = {
31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31,
};
8、数组使用陷阱
如果声明数组 int array[10]; 那么除了array被用作运算符sizeof的参数这一情形时,在其他所有的情形中,数组名array都代表指向数组array中下标为0的元素的指针。正如我们合乎情理的期待,sizeof(array)的结果是整个数组的字节总数大小,而不是指向数组array的元素的指针的大小。
实际上,关于数组下标的操作都是关于指针的操作。而我们一直比较常用的
array[i]
的引用方式却是这种写法的简记。
例如如下代码:
int days[] = { 31, 28, 31, 30, 31, 30,
31, 31, 30, 31, 30, 31, };
for (x = 0; x < 12; x++) {
printf("days[x] = %d/n", days[x]);
printf("days[x] = %d/n", x[days]);
}
注解:实际上,days+x 与 x+days的含义一样,因此,day[x]与x[days]也具有同样的含义。
也许有些汇编语言程序与会发现后种写法很熟悉,但是我们绝对不推荐这种写法。