C陷阱与缺陷代码分析之第2章语法陷阱
来源:互联网 发布:淘宝bug是什么意思啊 编辑:程序博客网 时间:2024/05/16 17:17
作者:刘昊昱
博客:http://blog.csdn.net/liuhaoyutz
陷阱1 理解函数声明
作者提出一个问题:有一个首地址为0的函数,该函数返回值类型为void,没有参数。怎样用C语言的语句调用这个函数?
答案是(*(void (*)())0)();
要理解这个调用形式,要清楚如下两个问题:
一是函数指针。
假设fp是一个函数指针,则调用fp所指向的函数的方法是
(*fp)();
因为fp是一个函数指针,所以*fp是该指针所指向的函数,所以(*fp)()就是调用该函数的方式。ANSI C允许将(*fp)()简写为fp(),fp()也是我们比较常见的形式,但是一定要知道这种写法是一种简写形式。例如prinf()函数,printf就是函数指针,它的完整形式是(*printf)()。为了说明这个问题,我们来看一个测试程序page17.c,代码如下:
1#include <stdio.h>23int main()4{5 printf("test1\n");6 (*printf)("test2\n");78 return 0;9}
编译运行结果如下:
二是强制类型转换符的声明方式。
某类型的强制类型转换符,只需要把该类型变量声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号封装起来即可。例如,声明一个int型指针变量的方式是
int *p;
按照上面的原则,int型指针强制类型转换符就是把变量名p和末尾的分号去掉,再把剩余的部分用一个括号封装起来,即(int *)。
同理,声明一个返回值为void,没有参数的函数指针变量f的方式是:
void (*f)();
按照上面的原则,返回值为void,没有参数的函数指针类型强制转换符就是把变量名f和最后的分号去掉,再把剩余的部分用一个括号封装起来,即(void (*)())。
有了上面的预备知识,我们可以来看作者提出的问题了。首地址为0的函数,也就是函数指针的值为0,函数返回值类型为void,没有参数。所以我们把0强制转换为(void (*)())类型就是该函数的函数指针,有了函数指针,要调用该函数,则是(*(void (*)())0)();
如果使用typedef能够使表述更加清晰:
typedef void (*funcptr)();
(*(funcptr)0)();
作者举的第二个例子是signal函数,其函数声明如下:
void (*signal(int, void(*)(int)))(int);
怎样来理解这个函数声明呢?
signal函数有两个参数,第一个参数是一个整数,代表需要“被捕获”的特定信号。第二个参数是一个函数指针,它是信号处理函数指针,它的返回值类型为void,该信号处理函数同样有一个int型参数代表要处理的信号。
让我们从信号处理函数开始,信号处理函数的函数指针声明如下:
void (*sfp)(int);
信号处理函数指针类型可以通过把指针变量名sfp和最后的分号去掉得到,即:
void (*)(int)
signal函数的返回值是原来的信号处理函数指针,即singnal函数的返回值类型是void(*)(int)。
综上所述可知,signal函数的声明形式应该是:
void (*signal(int, void(*)(int)))(int);
同样地,使用typedef可以简化signal函数的声明:
typedef void (*HANDLER)(int);
HANDLER signal (int, HANDLER);
陷阱二运算符的优先级
记住C语言运算符的优先级是非常有益的,但是,C语言运算符优先级多达15个,记住它们并不是一件容易的事。完整的C语言运算符优先级如下表所示:
优先级
运算符
名称或含义
使用形式
结合方向
说明
1
[]
数组下标
数组名[常量表达式]
左到右
()
圆括号
(表达式)/函数名(形参表)
.
成员选择(对象)
对象.成员名
->
成员选择(指针)
对象指针->成员名
2
-
负号运算符
-表达式
右到左
单目运算符
(类型)
强制类型转换
(数据类型)表达式
++
自增运算符
++变量名/变量名++
单目运算符
--
自减运算符
--变量名/变量名--
单目运算符
*
取值运算符
*指针变量
单目运算符
&
取地址运算符
&变量名
单目运算符
!
逻辑非运算符
!表达式
单目运算符
~
按位取反运算符
~表达式
单目运算符
sizeof
长度运算符
sizeof(表达式)
3
/
除
表达式/表达式
左到右
双目运算符
*
乘
表达式*表达式
双目运算符
%
余数(取模)
整型表达式/整型表达式
双目运算符
4
+
加
表达式+表达式
左到右
双目运算符
-
减
表达式-表达式
双目运算符
5
<<
左移
变量<<表达式
左到右
双目运算符
>>
右移
变量>>表达式
双目运算符
6
>
大于
表达式>表达式
左到右
双目运算符
>=
大于等于
表达式>=表达式
双目运算符
<
小于
表达式<表达式
双目运算符
<=
小于等于
表达式<=表达式
双目运算符
7
==
等于
表达式==表达式
左到右
双目运算符
!=
不等于
表达式!=表达式
双目运算符
8
&
按位与
表达式&表达式
左到右
双目运算符
9
^
按位异或
表达式^表达式
左到右
双目运算符
10
|
按位或
表达式|表达式
左到右
双目运算符
11
&&
逻辑与
表达式&&表达式
左到右
双目运算符
12
||
逻辑或
表达式||表达式
左到右
双目运算符
13
?:
条件运算符
表达式1?表达式2: 表达式3
右到左
三目运算符
14
=
赋值运算符
变量=表达式
右到左
/=
除后赋值
变量/=表达式
*=
乘后赋值
变量*=表达式
%=
取模后赋值
变量%=表达式
+=
加后赋值
变量+=表达式
-=
减后赋值
变量-=表达式
<<=
左移后赋值
变量<<=表达式
>>=
右移后赋值
变量>>=表达式
&=
按位与后赋值
变量&=表达式
^=
按位异或后赋值
变量^=表达式
|=
按位或后赋值
变量|=表达式
15
,
逗号运算符
表达式,表达式,…
左到右
从左向右顺序运算
如果把这些运算符恰当分组,并且理解了各组运算符之间的相对优先级,那么这张表其实不难记住。
第一:
优先级最高者其实并不是真正意义上的运算符,包括:数组下标、函数调用操作符,结构成员选择符。它们的优先级是1级,都是自左向右结合,因此a.b.c的含义是(a.b).c,而不是a.(b.c)。
第二:
单目运算符的优先级仅次于前述运算符,它们的优先级是2级。在所有真正意义上的运算符中,它们的优先级最高。单目运算符是从右向左结合的,因此*p++会被编译器解释成*(p++),即取指针p所指向的对象,然后将p递增1,而不是(*p)++,即取指针p所指向的对象,然后将该对象的值加1。
第三:
优先级比单目运算符低的,接下来就是双目运算符。
在双目运算符中,算术运算符优先级最高(乘、除、取余为3级,加、减为4级),
移位运算符次之(左移>>、右移<<,为5级),
关系运算符再次之(如>、<、<=等等,为6级,==和!=,为7级),
接着是逻辑运算符(如按位与&、按位或|、逻辑与&&、逻辑或||,等等),
接下来是条件运算符(?:其实这是一个三目运算符),
赋值运算符(=,/=,*=等等),
优先级最低的是逗号运算符(,)。
我们需要记住的最重要的一点是:
算术运算符(加减乘除)>
移位运算符(左移>>、右移<<) >
关系运算符(大于>、小于<、等于==,等等) >
逻辑运算符(按位与&、按位或|、逻辑与&&、逻辑或||,等等)
- C陷阱与缺陷代码分析之第2章语法陷阱
- C陷阱与缺陷代码分析之第1章词法陷阱
- C陷阱与缺陷之语法陷阱
- 《C陷阱与缺陷》2章:语法陷阱
- C陷阱与缺陷代码分析之第0章导读
- C陷阱与缺陷读书笔记(第2章)语法“陷阱”
- c陷阱与缺陷 语法陷阱有感
- 《c陷阱与缺陷》第二章,语法陷阱
- 《C陷阱与缺陷》 第二章 语法“陷阱”
- C陷阱与缺陷第二章 语法“陷阱”
- C陷阱与缺陷 —— 读书笔记-2、语法“陷阱”
- C陷阱与缺陷之词法陷阱
- C陷阱与缺陷(二)语法"陷阱"
- 《C陷阱与缺陷》读书笔记--第一章语法陷阱1
- c陷阱与缺陷
- 《c陷阱与缺陷》
- C陷阱与缺陷
- C陷阱与缺陷
- linux head命令
- java设计模式---单例模式
- ios 常用第三方库
- 修复 Ubuntu 12.04 Grub 引导
- Vi编辑器的使用方法
- C陷阱与缺陷代码分析之第2章语法陷阱
- Quartz初接触---属于自己的开荒
- jquery mobile 从一个html的page跳转到另一个html的page
- 关于迁移Crystal Reports 2008到Crystal Reports 2011
- 500W 文本数据排序去除重复数据
- 自动跳转
- 杭电1010-Tempter of the Bone
- Qt QTreeWidget 树形结构实现
- paip.按键替换映射总结