对于一些简单的1错误将不列举,这些应该是必须掌握的。
1.词法缺陷
编译器第一部分是词法分析器。这里进行2次划分标记(token)。一次划分的目的是为了宏扩展;二是将进行宏替换的程序划分标记(token);
错误1:=不是==,&和|不是&&和||
错误2:多字符记号
y=x/*p /*p指向除数*/ (错误)
良好的程序书写习惯也是避免这种错误的方法之一,可以写成y = x / p。在中间加几个空格来划分标记。或者写成y = x / (*p)。
错误3:例外
组合赋值运算符实际上是2个标记
a + /**/ = 1和 a += 1一样的。看起来是1个标记,实际上是2个标记,这是一个特例。
错误4:字符串和字符 (略)
2.词法缺陷
理解c语言,仅了解构成他的标记是不够的。还要理解这些记号是如何构成声明、表达式、语句和程序的。
(1).理解声明
声明组成:一个类型和一组具有特定格式的期望对该类型求值的表达式。最简单的表达式就是变量,如 float f, g;
如:float ff();说明ff()是一个float,应此ff()是一个返回一个float的函数。
float pf;说明pf是一个float,并且pf是一个float的指针
这些形式对与组合声明表达式是一样的。因此,
float g(), (*h)();表示*g()和(*h)()都是float表达式,由于()比*绑定更紧密,*g()和*(g())是一样的。g是一个返回float指针的函数。h是一个指向返回float的函数的指针。
类型的模型:当我们知道如何声明一个给定类型的变量以后,就能够很容易的写出一个类型的模型(cast)。只要删除变量名和分号并将所有的东西包围在一对()中。
如:float *g()的模型就是(float *())。
理解 (*(void (*)())0)()
第一步:这是一个函数,变量为指向函数的指针,把整个表达式样写成(*fp)();*fp就是函数本身,我们现在需要寻找一个合适的表达式来替换fp。
第二步:本例中函数的地址就是0,可以将上面的(*fp)()写成(*0)(),但这样是不行的,因为*运算要求必须有一个可以指针作为他的操作数。另外,这个操作数还必须是一个指向函数的指针,以保证*的结果可以被真确的调用。应此我们需要将0转换为一个”指向返回为void的函数的指针”:(void (*)())0。接下来我们用(void (*)())0来替换fp得到(*( void(*)())0();结尾的”;”表示将这个表达式转换为一个语句。
我们可以使用typedef来简化这个过程:
typedef void (*funcp)();
(*(funcp)0)();
(2)运算符号并不总是具有你所想象的优先级
避免错误的方法就是多多使用()。
3.链接
一个c程序可以有多个部分组成,他们被编译,并通过链接器绑定到一起。
(1)必须自己检查外部类型
如有一个c程序,被划分为2个文件。
其中一个文件声明了int n,另外一个文件声明了long n。这不是一个有效的c程序,因为一些外部名称在两个文件中被声明为不同的类型。这个错误在编译时是不能被发现的,因为编译程序只对单个文件编译。这些检查工作有链接器来完成。
这些规则只在使用全局变量时候起作用。
4.语义缺陷
(1)c并不总是转换实参
如:
double s;
s = sqrt(2);
printf(“%g/n”, s);
sqrt()需要一个double作为参数,但没有得到。返回一个double值但没有这样的声明。
double s;
s = sqrt(2.0);
printf(“%g/n”, s);
main() {
int i;
char c;
for(i = 0; i < 5; i++) {
scanf("%d", &c);
printf("%d", i);
}
printf("/n");
}
这里错误的原因是scanf去读取一个整数,他需要一个指向整数的指针,而这里是一个字符的指针,他将输入当成一个整数存储到那里。一个整数占用比字符更多的内存,这样可能影响到附近的内存。
(2)指针不是数组