文章目录
- 1.理解函数声明
- 2.运算符优先级问题
- 3.注意作为语句结束标志的分号
- 4.switch语句
- 5.函数调用
- 6.“悬挂”else引发的问题
语法问题不是语法错误,因此编译器不会将以下问题进行报错,但是这些问题又会对程序员预料的结果与实际结果又会有出入,会造成极难发现的bug。在写代码时应该时刻小心。
索引
《C陷阱与缺陷》——第一章(词法陷阱)
1.理解函数声明
在《C陷阱与缺陷》中,介绍到了这样一个例子:(*(void(*)())0)();
,这样的一个函数声明如何去理解呢?
解释与分析:首先我们一定要学会如何观察函数指针的类型,例如:void( * pf)(int,int),pf是该函数指针的名字,去掉名字,就是该函数指针的类型。因此去掉名字剩下的就是void( * )(int,int),它的意思指该指针存放的是返回类型为void,而形参中两个参数均为int类型。由此我们可以得出上面的例子中void( * )()是函数指针,存放的是返回值为void型,无形参的函数的地址。
而该函数指针在0前又有一个括号,指的是将0(为int类型)强制转换为函数指针类型,在最前面有一颗*,在指针中*代表的是解引用操作,即将指针存放的地址中的内容“拿”出来,并且如果要调用函数指针时,例如上述的函数指针,可以(*pf)(2,3)来调用函数,当然这只是便于理解,完全可以写为pf(2,3),它们的运行结果是一致的。
因此书中举的例子就是将常数0强制转换为返回类型为void型,无形参的函数指针类型,并且调用这个函数指针,不设置传入的实参。
我相信对上面的例子理解地非常透彻,对void(*signal(int,void(*)(int)))(int);
也是迎刃而解了。
2.运算符优先级问题
《C陷阱与缺陷》中举了这样的例子:原意是:假设FLAG一个整数,并且FLAG二进制中只有某一位是1,其余都为0。判断整型变量flags在该位上是否也为1,则判断部分写为:if(flags&FLAG !=0) ...
,但是!=运算符的优先级高于&,则实际上被解释为if(flags&(FLAG !=0)) ...
,结果完全不同,无论flags在该位上是否有1,判断结果都为真,除非flags整体值为0。
运算符优先级高低表如下:
注意:
- 优先级最高者其实不是真正意义上的运算符,包括:数组下标[ ],函数调用操作符(),各结构成员选择操作符.或->。它们都是从左至右结合。因此a.b.c含义是(a.b).c,而不是a.(b.c)。
- 单目运算符的优先级仅次于上述几种运算符,因此在所有真正的运算符中,它们的优先级是最高的。
- 优先级比单目运算符要低的,接下来就是双目运算符。双目运算符中,算术运算符的优先级最高,移位运算符其次,关系运算符再其次,接着是逻辑运算符,赋值运算符,最后是三目运算符。
因为运算符==与运算符!=的优先级要低于其他关系运算符的优先级。因此当我们比较a和b的大小顺序与c和d的大小顺序是否相同时,可以直接这样表示:a<b==c<d
, 当然我们也可以用括号来控制它们的运算优先级(a<b)==(c<d)
,但是如果括号过多代码的可读性就会降低。
举个相较复杂的例子:if((t=BTYPE(p1->aty)==START)||t==UNIONTY){
,这行代码的本意是将BTYPE的返回值赋值给t,再判断t与START或UNIONTY是否相等。但结果却大相径庭。因为==运算符比=运算符的优先级高,因此是先判断BTYPE与START的值是否相等,如果相等则结果为1,不等则结果为0。将0或1再赋值给t,接着再去与UNIONTY进行比较。
3.注意作为语句结束标志的分号
我相信各位同学在学分支、循环等语句的过程中难免会打多一个分号,那么打多一个分号与没有分号有什么本质区别呢?
例如这个例子if(x[i]>big);big=x[i];
我们的本意是如果x[i]大于big,则能够进入到if语句中,但此时结果却不同并且编译器下不会报错。在if语句后面不小心加了个分号,实际上相当于if(x[i]>big){}big=x[i];
,即big=x[i];这条语句不包含在if语句内,结果当然完全不同。
当一个函数结束时需要return;语句进行返回,返回值可以选择返回也可以不返回。但是,return后面一定要加分号,比如下面这个例子:
if(n<3)
return
logrec.date=x[0];
logrec.date=x[1];
logrec.date=x[2];
原意是该函数不返回任何值直接结束调用。以上代码中return后面缺少分号,则返回的是logrec.date=x[0];即等同于以下代码:
if(n<3)
return logrec.date=x[0];
logrec.date=x[1];
logrec.date=x[2];
这个代码实际上是一个错误的代码,但是编译器不会根据程序员自身的出发点来判断错误,编译器只会进行语法纠错,因此造成该错误可能是个潜伏很深、极难发现的程序bug。
还有另一种情形:
struct logrec{
int date;
int time;
int code;
}
main()
{
....
}
当一个声明的结尾紧跟一个函数定义时,如果函数声明结尾的分号被忽略,编译器可能会把声明的类型视作函数的返回值类型。
以上的代码认为main函数返回值类型为结构体类型,如果分号没有被省略,则函数main的返回值类型会缺省定义为int类型。
4.switch语句
switch语句中的break非常重要,但是常常被我们忽略,输出的结果跟我们自己的预想截然不同。例如:
switch(color){
case 1:printf("red");
case 2:printf("yellow");
case 3:printf("blue");
}
以上的程序打印结果为:redyellowblue ,因此我们可以知道当执行到第一个case中如果没有break,程序将会自然而然地执行下去,直到没有case语句。当然我们也要注意default语句,如果条件中没有case内的情况,则要执行default语句,而default语句中没有break,并且在switch中的句首,它仍然会按照顺序进行下去,直到switch结束为止。
5.函数调用
函数调用中必须包括"()"此运算符,如果函数调用时不带参数,也必须带有括号,如果没有括号,则计算的是该函数的地址,并不能调用该函数。例如:正规写法:f();
错误写法:f;
这条语句什么都不会执行。
6.“悬挂”else引发的问题
在刚学C语言时,我相信各位都被这个问题”坑“过,因为当时我们都是凭借感觉来判断哪个if与哪个else是结合的。例如以下代码:
if(x==0)
if(y==0)error;
else{
z=x+y;
f(&z);
}
如果我们凭借感觉来判断,会认为第一个if是与else结合的,其实不然。我们再将以上代码调整位置。
if(x==0)
if(y==0)error;
else
{
z=x+y;
f(&z);
}
此时就非常明确了,是第二个if与else结合的。在C语言中认为:else始终与同一对括号内最近的未匹配的if结合。即与它相邻最近的if语句结合。
原创不易,如果这篇文章对你有帮助,麻烦点个赞支持下谢谢!