第二章: 语法陷阱
2.1 Understanding function declarations: (函数声明)
1. 变量声明:
可以声明一个浮点型变量为 : float f;
例:
float ((f)); // 也是声明一个浮点型变量 f ;
float ff(); // 声明一个函数,返回值类型为 float;
float *pf; // 声明一个指向 float 类型变量的指针;
float *g(); // 声明一个返回值为 float 指针的函数;
float (*h)(); // 声明一个函数指针,指向返回值为 float 类型的函数;
注:
*g() means *( g() ); ( ) binds more tightly than *;
2. 写一个表达式的 cast :
将变量名和声明语句的分号去掉,并加上括号:
例:
float ( *h )();
( float( * ) () );
所以对一个表达式为 : ( *( void( * ) ( ) ) 0 ) ( )
我们可以分析:
1. 对于一个函数指针 fp 我们有:
调用函数格式为 : ( *fp ) ( ) 简写为 fp( )
注:
函数结合优先级 > 一元操作符 , 所以有:
对于 *fp() means : *( ( *fp ) ( ) ) 即为一个函数指针;
2. 对于( *0 ) ( ) :
把 *0 cast 成为 一个 pointer to function returning void;
所以由1.2. 我们有:
void (*fp ) ( ); == ( void ( * ) ( ) ) 0
所以对于原式子,我们可以用上式子代替 fp 得:( *( void( * ) ( ) ) 0 ) ( )
用 typedef 定义有 : typedef void ( *funcptr ) ( ); ( *( funcptr ) 0 ) ( ) ;
2.2 Operators dont always have the precedence you want : (运算优先级陷阱)
1. if 中的判断优先级:
例:
对一个 if( flags & FLAG ! = 0 )
目的是为了判断 flag 是否为零;但是是错误的!!!
因为 != 的优先级要高于 &, 所以原句等价于: if( flags & ( FLAT != 0 ) )
2. 假设两个数 low 和 hi ( 取值为 0~15 ), 想用一个整数 r 高四位表示 hi ,第四位表示 low:
有: r = hi << 4 + low;
结果错误,因为加法运算的优先级要高于位移优先级,原句等价为: r = hi << ( 4 + low );
Code:
#include
typedef void (*funcptr)();
int arr[50];
int num = 0;
int main(){
int low = 6,hi = 10; // low = 0110 , hi = 1010;
int r = 0;
r = hi;
r = (r << 4) + low;
BineryOutput(r);
for(--num; num >= 0; num--)
{
printf("%d",arr[num]);
}
printf("\n");
}
void BineryOutput(int n)
{
while(n)
{
if((n%2) == 1)
{
arr[num] = 1;
}
else
{
arr[num] = 0;
}
num++;
n = n/2;
}
}
1. 优先级最高的,往往不是真正的运算符:
注释,函数调用,成员运算符 . (左结合)
2. 一元运算符运算顺序次之:
一元运算符在所有真正运算符中优先级最高;
因为函数调用比一元运算符优先: 必须用 ( *p ) ( ) 调用函数;
因为一元运算符为右结合性: 有 *p++ 等价于 *(p++);[ 先指向p ,之后p再自加 ]
3. 二元运算符:
数学运算符的优先级最高,之后是位移运算符,关系,逻辑,赋值,条件运算符;
注:
一、 关系运算符要大于逻辑运算符;
二、 运算 > 位移 > 关系;
乘法,除法和取余有着相同的优先级; 加法和减法有着相同的优先级;
例:
1/2*a; 等价于 (1/2)*a ;
六个关系运算符不等价,其中 != 和 == 为其中的低优先级;
例:
a < b == c < d; 可以实现两个判别式的关系运算!!
逻辑运算符的优先顺序全都不同;
4. 三元运算符:(仅次于赋值运算 和 最低的逗号运算符)
因为三元运算符的优先级不高,我们可以实现:
tax_rate = income > 40000 && residency < 5 ? 3.5 : 2.0;
equals to:
tax_rate = ( (income > 40000) && (residency < 5) ) ? 3.5 : 2.0;
5. 赋值运算符(仅大于逗号运算符):
home_score = visitor_score = 0; 可以省略一句;
6. 逗号运算符(优先级别最低的运算符):
例: 复制一个文件的内容到另一个文件:
while( ( c = gerc( in ) ) != EOF )
putc( c, out );
看起来这个程序像是先把getc 的值传递给 c 之后 再判断是不是应该为 EOF。
但事实上: 赋值的优先级很低,所以将先会和EOF比较,之后将值( 取值为1 ) 赋值给 c ,一直到EOF。
例2:
if( ( t = BTYPE( pt1 ->aty ) == STRTY ) || t = UNIONTY ) { }
原本目的是判断 t 的值是不是和 STRTY or UNIONTY 相同,但是赋值优先级较低,产生错误;
2.3 Watch those semicolons : (分号陷阱)
通常,在C语言中多余的分号,将作为一条空语句,但是还有一些特殊情况:
1. if 语句中的多加分号陷阱:
例:
if( x[ i ] > big );
big = x[ i ];
将等价于:
if(...) { };
big = x[ i ]; 即无论if判断如何,都执行下一条语句;
2. if语句中的少加分号陷阱:
例:
if( n < 3 )
return ;
logrec.data = x[ 0 ];
logrec.time = x[ 1 ];
logrec.code= x[ 2 ];
这里少加了一个分号,所以将变为:
if( n < 3 )
return logrec.data = x[ 0 ];
logrec.time = ........
此时:
如果函数的返回值为 void,编译系统会提示 error;
但是如果未标明返回值,或者返回为int, 则会出现逻辑错误(但是编译正常通过);
3. 函数声明之前少加分号:
例:
struct logrec {
int data;
int time;
int code;
.....
} ;
main( )
{
....
}
此时将造成 main函数的返回值为 struct logrec 类型,造成严重错误!
2.4 The switch statement : (switch陷阱)
在C语言中,switch 语句在没有break时,会连续执行之后的所有语句!
例:
switch( color ){
case 1 : printf("red");
break;
case 2 : printf( " yellow " );
break;
..........
}
C语言中 需要break语句的原因是:
case labels in C behave as true labels, in that control flows unimpeded right through a case label.
2.5 Calling functions : (函数调用陷阱)
函数调用的格式为 : f( ); 对于 : f;仅仅表示函数 f 的地址,但是不可以调用函数;
2.6 The dangling else problem : (else函数调用陷阱)
例:
if( x == 0 )
if( y == 0 ) error();
else{
z = x + y;
f( &z );
}
编程的目的是有两个主要的判断 x = 0 或者 x ≠ 0;
但是:
在C 语言中:
else 永远跟随在离他最近的一个在同一个大括号中的 if !
所以源程序应当修改为:
if( x == 0 ){
if( y == 0 )
error();
}
else{
z = x + y;
f( &z );
}