分类: C/C++
2012-02-13 19:09:24
(1)有一个运行在单个微处理器上的程序,当这台机器启动时,硬件会自动去调用存储在地址为0的这段程序。为了仿真机器的启动过程,我们必须要去设计一段程序去显式调用这个子程序,可以采用如下语句来执行:
(* (void (*)( ) ) 0)( );
一旦知道怎样去定义一个指定类型的变量,那就很容易写出此指定类型的原型,只要从定义表达式中去掉变量名和分号,然后将整个语句用()包含就行了。因而语句
float *g( );
定义g为一个返回float型指针的函数,则(float *())就是此类函数的原型。
掌握以上原理后,我们来分析(* (void (*)( ) ) 0)( );语句。我们可以把此条声明分作两部分,首先我们假设有一函数指针变量fp,当想去调用由fp所指向的函数时,可以用以下的语句:
(*fp)();
fp是一函数指针,*fp就是函数本身,则(*fp)()就是调用此函数的方法,在(*fp)中()是必须的,如果去掉,表达式就等同于*(fp())。现在我们把问题简化成如何去寻找一个合适的表达式来代替fp。
现在来进一步分析以上的问题,假设C编译器能理解我们的意图,则以上的语句可以写成:
(*0)();
但这条语句是不能工作的,因为*操作符坚持要用指针变量作为它的操作数,而且此操作数必须是一个函数指针,这样加上*操作符后才能够被调用。因此我们必须将0转换成一个指向返回void型的函数指针。
如果fp是一个指向void型函数的指针,则(*fp)()是一个void型的数,它的声明形式应当为:
void (*fp)();
一旦知道怎样声明一个变量,我们就能够将一个常量进行类型转换,将其类型转换为该变量的类型:只要在变量声明中将变量名去掉即可。因此我们可以用如下的方式将0转换成一个指向返回void型的函数指针:
(void(*)())0
现在可以用(void(*)())0来代替fp进行函数调用,如下所示:
(*(void(*)())0)();
到这里问题就讨论结束了,另外还有一种更清晰方法来实现上面的功能,使用“typedef”出定义一种新的变量类型:
typedef void (*funcptr)();
(* (funcptr) 0)();
(2)signal 函数 void (*signal(int, void(*)(int))) (int)
Signal 函数在系统头文件signal.h中声明,接受两个参数: 一个是代表需要“被捕获的”特定的signal的整数值;另一个是指向用户提供的函数的指针,该函数用于处理“捕获到”的特定的signal,返回类型为void。
首先考虑用户自定义信号处理函数,可定义如下:
void sigfunc(int n){/*特定信号处理部分*/}
可以声明一个指向sigfunc函数的指针变量:
void (*sfp)(int);
因为signal函数的返回类型和sfp的返回类型一样,上式也就声明了signal函数:
void (*signal(something)) (int);
something为signal函数的参数类型。上面的声明可以这样理解:传递适当的参数以调用signal函数,对signal函数的返回值(为函数指针类型)进行反引用,然后传递一个整型参数调用反引用后所得的函数,最后返回值为void类型。因此,signal函数的返回值是一个指向返回类型为void类型的函数的指针。
Signal参数something包含两个部分:int,void(*)(int)
所以signal函数可以声明如下:
void (*signal(int, void(*)()))(int);
同样,用typedef可以简化上面的函数声明:
typedef void(*HANDLER)(int);
HANDLER signal(int,HANDLER);
C语言运算符优先级(上高下低) () [] -> . ! ~ ++ -- - (type) * & sizeof <- / % * + - << >> < > <= >= == != & ^ | && || ?: <- = (赋值) <- ,
在C语法中,总共有15个运算符优先级,要想全部记住他们的优先级不是一件容易的事,但是我们可以将它们进行分组来帮助记忆。
运算符中具有最高优先级的是那些不参与运算的4个操作符:下标运算符( [] ),函数调用(()),结构体变量运算符(. ->),它们的结合方向是自左向右。
接着就是一元运算符,它们在参与运算的操作符中具有最高优先级。因为函数调用的优先级高于一元运算符,因此当p为一函数指针时,必须用(*p)()来调用函数,*p()则表明p为返回一指针的函数。一元运算符的结合方向是自右向左,因此*p++应于*(p++)相同,而不与(*p)++相同。
再接着就是二元运算符,算术运算符在里面具有最高的优先级,再下面是移位运算符,关系运算符,逻辑运算符,条件运算符,赋值运算符,最后是逗号运算符。
重点记忆:算术 > 移位 > 关系 > 逻辑!
当判断这些运算符的优先级时,有两点要记住:
1、任何一个逻辑运算符的优先级低于任何一个关系运算符的优先级
2、移位运算符的优先级高于关系运算符,但是低于算术运算符
乘法、除法、取余具有相同的优先级,加法、减法具有相同的优先级,两个移位运算符具有相同的优先级。
六个关系运算符具有不相同的优先级:“==”和“!="的优先级比其他四个要低。
任何两个逻辑运算符具有不同的优先级。
所有的按位运算符的优先级要高于顺序运算符。
每个”与”运算符要比相应的“或”优先级高。
按位异或的优先级介于按位与和按位或之间。
三元条件运算符的优先级比上面提及的运算符的都要低,但比赋值运算符高,赋值运算符是唯一一个比三元条件运算符还要低级的二元运算符。
所有的复合赋值运算符具有相同的优先级,并且它们的运算方向是从右到左。因此:
a = b = c等同于b = c; a = b;
优先级最低的是逗号运算符,这比较容易理解,因为当一条语句由多个表达式组成时,逗号在这里相当于分号的功能。
关于运算符的结合性,只有单目、三目及二目中的赋值运算符是从右至左运算的,其它运算符的结合性都是从左到右。
在优先级判断中,赋值运算符是比较棘手的。
while (c=getc(in) != EOF)<加个括号>
putc(c,out);
“while”语句中要实现的功能是给变量c赋值,然后与EOF进行比较来终止循环,不幸的是,赋值操作的优先级低于比较操作的优先级,因此c的值是getc(in)与EOF比较的结果,getc(in)的值将被弃掉,因此拷贝生成的文件将是一连串的1。
想实现以上的功能并不困难,可以简单修改如下:
while ((c=getc(in)) != EOF)
putc(c,out);
C语言中,逻辑运算符的优先级分配有其历史的原因。B语言,也就是C语言的前身,也有相当于C语言中的 & 和 | 操作符,尽管它们被定义用作位运算符,但是当用于条件上下文时,编译器会自动将它们当作 && 和 || 运算。C语言将两种用法进行了区分,但考虑到熬兼容性,并没有对它们的优先顺序进行大的调整。
(1)语句中一个额外的分号通常会产生小的分歧:他可以是一条不产生任何影响的空语句,或者是用于使编译器产生一个诊断信息,使之容易去掉。然而如果在if 或while语句后面加一个分号,会产生严重的歧义。看下面的例子:
if (x[i] > big); big = x[i];
第一行后面的分号编译时能够通过,但这段程序的功能跟下面的程序完全不同:
if (x[i] > big)
big = x[i];
前面的那段程序相当于:
if (x[i] > big) { }
big = x[i];
当x、i、big不是宏,不具有其他影响时,可以再简化为:
big = x[i];
(2)漏掉分号
if(x>2)
return
x =3;
y=4;
return 后面漏掉了分号,意义改变。
(3)另外一个能产生重大歧义的地方是在一个函数的定义前面有一个类型的声明。看下面的一段程序:
struct foo {
int x;
}
f(){. . .}
在第一个}后面忘记写了一个分号,然后函数f就紧跟在后面定义,那这样就定义了函数 f 返回结构体foo的一个变量。假如分号存在的话,则 f 返回的是缺省的int型数。
在C语言中,Switch语句中的各个case分支能够相互作用,这和别的程序语言不同。现在看一下C和Pascal的两个例子:
switch (color) {
case 1: printf ("red");
break;
case 2: printf ("yellow");
break;
case 3: printf ("blue");
break;
}
case color of
1: write ('red');
2: write ('yellow');
3: write ('blue')
end
这两段程序非常相似,仅仅是Pascal程序没有break语句。这是因为在C语言中case标号是一个真实的标号,因此程序流程会畅通无阻地径直通过case标号(相当于goto语句),而pascal中每个case标号都隐含的结束了前一个case部分。
现在以另外的方法来看这个问题,下面这段程序是仿照Pascal的样式写的:
switch (color) {
case 1: printf ("red");
case 2: printf ("yellow");
case 3: printf ("blue");
}
现在假设color的值等于2,那样程序将打印“yellow”和“blue”。因为当程序调用完第二个printf()后,就很自然的执行它以后的程序。
这既是C语言switch语句的优点,也是它的缺点。说它是缺点是因为在程序的编写中很容易漏写一个break,而这样会导致程序的错误执行,并且错误不容易发现。作为优点,你可以故意漏掉一些break语句,让多个分支共用相同的处理程序。特别是在一些具有很多case分支的程序中,你经常会发现一些case分支的处理大同小异:对于某个分支稍作处理,剩余部分即完全等同于另一个分支情况下的处理。下面是一个虚拟机器的解释程序,程序里面包含了一段switch语句去处理不同的指令,在这种机器上,在第二个操作数的符号反转后,加法与减法的操作是一样的,因此程序编写成下面这样是很好的:
case SUBTRACT:
opnd2 = -opnd2;
/* no break */
case ADD:<用IF语句实现也可以>
. . .
注意上面加了注释,说明是故意省略掉break语句的,以免引起误解!
另外一个典型的例子是编辑器中的一段小程序,它实现的功能是忽略空格,得到一个记号(token),算法中空格、tab键、换行符都是同样处理,除了换行符能使行计数器加1,程序如下所示:
case '\n':
linecount++;
/* no break */
case '\t':
case ' ':
. . .
跟别的程序设计语言不一样,C语言要求函数调用时必须有参数列表,即使此参数列表是空的。假设 f 是一个函数,则
f();//函数调用的语句,参数为空
f;//不作任何事情,表示这个函数的地址,但是并不进行函数调用。
int x = 3;
int y = 2;
if(2==x)
if(2==y) cout<<"leo"<
else
cout<<"jia"<
这段代码什么也不输出!因为else始终与同一对括号内最近未匹配的if结合,即else与第二个if结合。
(1)其逻辑如下:
if(2==x)
{
if(2==y)
cout<<"leo"<
else
cout<<"jia"<
}
(2)应该这样写:
if(2==x)
{
if(2==y) cout<<"leo"<
}
else
cout<<"jia"<
(3)也可以利用宏定义写成这样:
#define IF {if(
#define THEN ){
#define ELSE }else{
#define END }}
……
IF(2==x)
THEN
IF(2==y)
THEN cout<<"leo"<
END
ELSE
cout<<"jia"<
END
但不建议后者,写成(2)的样子就ok了。