C陷阱与缺陷
词法陷阱
1.符号
1.编译器将程序分解为符号的方法是,从左到右一个字符一个字符的读入,如果该字符可能组成一个符号(如/* 或==等等),那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分,如果可能,继续读入下一个字符,重复以上。
例如:
y = x/p; / p指向除数 /
本意是x除以p指向的数,再付给y。但是/ 被编译器解释为一段注释的开始,编译器不断读入字符,直到遇到*/。
解决方法:
y = x / p; / p指向除数 */
或者
y = x/(p); / p指向除数 */
2.char str = ’ / '; / 错误,因为’ / '不是指针,而是整数值*/
语法陷阱
1.函数声明
float g(),(h)();
()的优先级大于,所以g()即为*(g()):g是一个函数,该函数的返回值类型是一个指向float类型的指针。同理,h是一个函数指针,h所指向的函数返回值是浮点类型的值。
所以,只需把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装起来”即可,故float (h)();的类型为*( float (*) () )**。
例:分析 ( (void()())0 ) ()
第一步:假定fp是一个函数指针,那么调用该fp指向的函数的方法为
(fp)(); //fp是函数指针,那么fp就是该函数,则fp)()就是调用该函数。()优先级高于。fp必须是指针,因为*必须要一个指针来作为操作数,且还是函数指针
第二步:对0做类型转换
(void (*)())0 :将常数0转型为”指向返回值为void的函数的指针“类型,再用此表达式替代fp
语义陷阱
指针和数组
c语言中只有一维数组,而且数组胡大小必须在编译期就作为一个常数确定下来,但是数组的元素可以是任何类型的对象,当然也可以是另外一个数组。
数组名用作sizeof的操作数时,是计算数组的总字节数,而其他场合,数组名总是被转换成为一个指向数组的起始元素的指针。
int a[3];
int *p;
p = a; //正确
p = &a;//错误,因为&a是一个指向数组的指针,两者类型不一致。
除了a被用作sizeof的参数这一类型,其他所有情况中数组名都代表指向数组a中下标为0的元素的地址。
int b[12][31];
int *p;
int i;
p = b[4]; //b[4]是b数组的第五个元素,即12个有着31个整数元素的数组之一。
int (ap)[31]; //**声明了ap是一个拥有31个整形元素的数组,因此ap就是一个指向这样的数组的指针**。
ap = b; //ap将指向数组的第一个元素
注:数组中实际不存在的“溢界”元素的地址位于数组作战内存之后,这个地址是可以用来进行赋值和比较的。当然,如果要引用该元素,就是非法的了。
整数溢出
在无符号算术运算中,没有“溢出”一说,所有无符号运算都是以2的n次方为模。如果算术运算符的一个操作数是有符号整数,另一个是无符号整数,那么有符号会被转换成无符号整数,“溢出”也不可能发生。但是,两个操作数都是有符号整数时,“溢出”就有可能发生,而且其结果是未定义的。 一种正确的方式是将两者强制转换为无符号整数,再与可能的最大整数比较。
连接
声明与定义
extern 修饰变量表明这个变量是在程序的其他地方定义的。可以是在同一个源文件内,也可以是位于不同的源文件内。且每个外部变量最好只定义一次。
static 是一个能够减少命名冲突的有用工具。限制了变量或者函数的作用域范围。
如果某个函数的调用和定义分别位于不同的文件中, 那么我们必须在调用它的文件中声明此函数。(例如引用头文件)
假定现在有一个c程序,有两个源文件,其中一个有一个外部变量 extern int n;另一个包含一个外部变量 long n;则此程序无效。因为同一个外部变量在两个不同的文件中被声明为不同的类型。
预处理器
宏
我们最好吧宏定义的每个参数都用括号括起来,整个表达式最好也是括起来。
考虑下面的代码:
#define T1 struct foo*
typedef dtruct foo *T2
上面两个定义,T1,T2从概念上相同,都是指向结构foo的指针。但是用他们来声明多个变量时,就会不同。
小心使用 typedef 带来的陷阱
接下来看一个简单的 typedef 使用示例,如下面的代码所示:
typedef char* PCHAR;
int strcmp(const PCHAR,const PCHAR);
在上面的代码中,“const PCHAR” 是否相当于 “const char*” 呢?
答案是否定的,原因很简单,typedef 是用来定义一种类型的新别名的,*它不同于宏,不是简单的字符串替换。因此,“const PCHAR”中的 const 给予了整个指针本身常量性,也就是形成了常量指针“charconst(一个指向char的常量指针)”。即它实际上相当于“charconst”,而不是“const char(指向常量 char 的指针)”。当然,要想让 const PCHAR 相当于 const char 也很容易,如下面的代码所示:
typedef const char PCHAR;
int strcmp(PCHAR, PCHAR);
还需要特别注意的是,虽然 typedef 并不真正影响对象的存储特性,但在语法上它还是一个存储类的关键字,就像 auto、extern、static 和 register 等关键字一样。因此,像下面这种声明方式是不可行的:
typedef static int INT_STATIC;
最后,使用typedef关键字定义结构体类型 定义结构体类型的同时定义结构体类型变量
typedef struct student
{
int age;
int height;
}std;
//std相当于struct student struct student
{
int age;
int height;
}std1,std2;
//定义了student数据类型的结构体和std1
、std2结构体变量