作者:刘昊昱

博客: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>
2
3int main()
4{
5 printf("test1\n");
6 (*printf)("test2\n");
7
8 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语言运算符优先级如下表所示:

优先级

运算符

名称或含义

使用形式

结合方向

说明

[]

数组下标

数组名[常量表达式]

左到右

()

圆括号

(表达式)/函数名(形参表)

.

成员选择(对象)

对象.成员名

->

成员选择(指针)

对象指针->成员名

-

负号运算符

-表达式

右到左

单目运算符

(类型)

强制类型转换

(数据类型)表达式

++

自增运算符

++变量名/变量名++

单目运算符

--

自减运算符

--变量名/变量名--

单目运算符

*

取值运算符

*指针变量

单目运算符

&

取地址运算符

&变量名

单目运算符

!

逻辑非运算符

!表达式

单目运算符

~

按位取反运算符

~表达式

单目运算符

sizeof

长度运算符

sizeof(表达式)

/

表达式/表达式

左到右

双目运算符

*

表达式*表达式

双目运算符

%

余数(取模)

整型表达式/整型表达式

双目运算符

+

表达式+表达式

左到右

双目运算符

-

表达式-表达式

双目运算符

<<

左移

变量<<表达式

左到右

双目运算符

>>

右移

变量>>表达式

双目运算符

>

大于

表达式>表达式

左到右

双目运算符

>=

大于等于

表达式>=表达式

双目运算符

<

小于

表达式<表达式

双目运算符

<=

小于等于

表达式<=表达式

双目运算符

==

等于

表达式==表达式

左到右

双目运算符

!=

不等于

表达式!= 表达式

双目运算符

&

按位与

表达式&表达式

左到右

双目运算符

^

按位异或

表达式^表达式

左到右

双目运算符

|

按位或

表达式|表达式

左到右

双目运算符

&&

逻辑与

表达式&&表达式

左到右

双目运算符

||

逻辑或

表达式||表达式

左到右

双目运算符

?:

条件运算符

表达式1? 表达式2: 表达式

右到左

三目运算符

=

赋值运算符

变量=表达式

右到左

/=

除后赋值

变量/=表达式

*=

乘后赋值

变量*=表达式

%=

取模后赋值

变量%=表达式

+=

加后赋值

变量+=表达式

-=

减后赋值

变量-=表达式

<<=

左移后赋值

变量<<=表达式

>>=

右移后赋值

变量>>=表达式

&=

按位与后赋值

变量&=表达式

^=

按位异或后赋值

变量^=表达式

|=

按位或后赋值

变量|=表达式

,

逗号运算符

表达式,表达式,…

左到右

从左向右顺序运算

如果把这些运算符恰当分组,并且理解了各组运算符之间的相对优先级,那么这张表其实不难记住。

第一:

优先级最高者其实并不是真正意义上的运算符,包括:数组下标、函数调用操作符,结构成员选择符。它们的优先级是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章语法陷阱的更多相关文章

  1. C陷阱与缺陷代码分析之第1章词法陷阱

    作者:刘昊昱 博客:http://blog.csdn.net/liuhaoyutz 编译器中负责将程序分解为一个一个符号的部分,称为“词法分析器”.下面看一个例子: if(x > big) bi ...

  2. [C陷阱和缺陷] 第2章 语法“陷阱”

    第2章 语法陷阱 2.1 理解函数声明   当计算机启动时,硬件将调用首地址为0位置的子例程,为了模拟开机时的情形,必须设计出一个C语言,以显示调用该子例程,经过一段时间的思考,得出语句如下: ( * ...

  3. c缺陷与陷阱笔记-第二章 语法陷阱

    1.函数的调用和番薯返回值是函数指针的声明 定义一个函数指针,例如  int (*fp)(float),这个函数的返回值是Int,参数是1个float类型,调用这个函数的方法是 (*fp)(),还有f ...

  4. C语言学习书籍推荐《C陷阱与缺陷》下载

    下载地址:点我 凯尼格 (作者), 高巍 (译者) <C和C++经典著作:C陷阱与缺陷>适合有一定经验的C程序员阅读学习,即便你是C编程高手,<C和C++经典著作:C陷阱与缺陷> ...

  5. 阅读《C陷阱与缺陷》的知识增量

    版权声明:本文为Focustc原创文章.转载请注明作者及出处. https://blog.csdn.net/caozhankui/article/details/35925939 看完<C陷阱与 ...

  6. C陷阱与缺陷(二)

    第二章 语法陷阱 2.1 理解函数声明 (*(void(*)())0)();任何C变量的声明都由两部分组成:类型以及一组类似表达式的声明符.一旦我们知道了如何声明一个给定类型的变量,那么该类型的类型转 ...

  7. 我的《C陷阱与缺陷》读书笔记

    第一章 词法“陷阱” 1. =不同于== if(x = y) break; 实际上是将y赋给x,再检查x是否为0. 如果真的是这样预期,那么应该改为: if((x = y) != 0) break; ...

  8. 《C陷阱与缺陷》读书笔记

    1. 词法“陷阱” = 不同于 == , 可以通过if( 1 == a )来避免 & | 不同于 && || 词法分析中的“贪心法” 编译器将程序分解成符号的方法是,从左到右一 ...

  9. 常用 Java 静态代码分析工具的分析与比较

    常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...

随机推荐

  1. 南阳师范学院ACM官方博客使用说明

    登录之后跳到如下页面: 点击博客进入如下页面: 这里每个人都有一个专栏,大家可以把自己写得博客放到自己的专栏下,同时也可以查看其他人写的博客,相互交流! 在发表博客的时候,选择个人分类中自己的专栏即可 ...

  2. leetcode 211. Add and Search Word - Data structure design Trie树

    题目链接 写一个数据结构, 支持两种操作. 加入一个字符串, 查找一个字符串是否存在.查找的时候, '.'可以代表任意一个字符. 显然是Trie树, 添加就是正常的添加, 查找的时候只要dfs查找就可 ...

  3. 字符串操作函数<string.h>相关函数strcpy,strcat,等源码。

    首先说一下源码到底在哪里找. 我们在文件中包含<cstring>时,如果点击右键打开文档, 会打开cstring,我们会发现路径为: D:\Program Files\visual stu ...

  4. dubbo 服务化

    当网站变大后,不可避免的需要拆分应用进行服务化,以提高开发效率,调优性能,节省关键竞争资源等. 当服务越来越多时,服务的URL地址信息就会爆炸式增长,配置管理变得非常困难,F5硬件负载均衡器的单点压力 ...

  5. Poj 2299 Ultra-QuickSort(归并排序)

    题目链接:http://poj.org/problem?id=2299 思路分析:序列的逆序数即为交换次数,所以求出该序列的逆序数即可.根据分治法思想,序列分为两个大小相等的两部分, 分别求子序列的逆 ...

  6. H面试程序(15): 冒泡排序法

    #include<stdio.h> #include<assert.h> void display(int * a, int n) { for(int i = 0; i < ...

  7. - 通过 UIBezierPath 做一个中空的扫描器

    今天在公司的代码里看到通过 UIBezierPath 绘制 CALayer 然后实现中空的正方形,感觉还挺有意思的,简单记录一下 UIBezierPath 这个东西. 一条线 我们自定义一个 Bezi ...

  8. TCP/IP之DNS域名解析系统

    DNS系统是一个分布式的数据库,当一个数据库发现自己并没有某查询所需要的数据的时候,它将把查询转发出去,而转发的目的地通常是根服务器,根服 务器从上至下层层转发查询,直到找到目标为止.DNS还有一个特 ...

  9. CodeForces 546D Soldier and Number Game 打表(求质因子个数)

    题目:戳我这个题与HDUOJ 5317有异曲同工之妙 题意:题意看懂了上面的一大串英文之后其实很简单,就是给你一个正整数n,问你n有多少个质因子,不过这里n是通过a!/b!给定的,也就是说n=(a!/ ...

  10. c++子类和父类成员函数重名

    子类和父类返回值参数相同,函数名相同,有virtual关键字,则由对象的类型决定调用哪个函数. 子类和父类只要函数名相同,没有virtual关键字,则子类的对象没有办法调用到父类的同名函数,父类的同名 ...