zoukankan      html  css  js  c++  java
  • C陷阱与缺陷代码分析之第2章语法陷阱

    作者:刘昊昱 

    博客: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
    ");
    6    (*printf)("test2
    ");
    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语言运算符优先级如下表所示:

    优先级

    运算符

    名称或含义

    使用形式

    结合方向

    说明

    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级),

    接着是逻辑运算符(如按位与&、按位或|、逻辑与&&、逻辑或||,等等),

    接下来是条件运算符(?:其实这是一个三目运算符),

    赋值运算符(=,/=,*=等等),

    优先级最低的是逗号运算符(,)。

    我们需要记住的最重要的一点是:

    算术运算符(加减乘除)>

    移位运算符(左移>>、右移<<) >

    关系运算符(大于>、小于<、等于==,等等) >

    逻辑运算符(按位与&、按位或|、逻辑与&&、逻辑或||,等等)

  • 相关阅读:
    CentOS 7 firewalld vsftpd开放端口
    sqlalchemy操作----多表关联
    sqlalchemy 或者pysql 连接数据库时支持中文操作
    sqlalchemy操作----外键关联,relationship
    sqlalchemy操作----建表 插入 查询 删除
    python 操作mysql
    指针和二维数组的关系要点简单分析
    二维数组基本操作练习题
    单独取出整数的个位,十位,百位,以及素数的小综合题
    LeetCode | Add Two Numbers II
  • 原文地址:https://www.cnblogs.com/dyllove98/p/3199076.html
Copyright © 2011-2022 走看看