zoukankan      html  css  js  c++  java
  • 详解C/C++函数指针声明

     要理解一个C程序,仅仅理解组成该程序的符号是不够的。程序员还必须理解这些符号是如何组合成声明、表达式、语句和程序的。

         我们先来看看下面的一个语句:

    1
    ( *( void(*)())0)();

         这是当计算机启动时,硬件将调用首地址为0位置的子例程。像这样的表达式恐怕会令每个C/C++程序员的内心都“不寒而栗”吧。

         然而,完全不用害怕,任何C变量的声明都是由两部分组成:类型以及一组类似表达式的声明符。最简单的声明变量,如:

    1
    float f , g ;

         这个声明的含义是:当对其求值时,表达式f和g的类型为浮点型。

         同样的逻辑也适用于函数和指针类型的声明,例如:

    1
    float ff();

         这个声明的含义是:表达式ff()求值结果是一个浮点数,也就是说,ff是一个返回值为浮点类型的函数,类似地:

    1
    float *pf;

         这个声明的含义是*pf是一个浮点数,也就是说,pf是一个指向浮点数的指针。

         以上这些形式在声明中还可以组合起来,就像在表达式中进行组合一样,因此:

    1
    float *g() , (*h)();

    表示*g()与(*h)()是浮点表达式。因为()结合优先级高于*,*g()也就是*(g()):g是一个函数,该函数的返回值类型为指向浮点数的指针。同理,可以得出h是一个函数指针,h所指向函数的返回值为浮点类型。

         一旦我们知道了如何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到了:只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个“封装”起来即可。例如:

    1
    float (*h)();

    表示h是一个指向返回值为浮点类型的函数的指针,因此,

    1
    (float (*)())

    表示一个“指向返回值为浮点类型的函数的指针”的类型转换符。

         那么,我们现在来看看前面我们提出的表达式:

    1
    ( *( void(*)())0)();

         第一步,假定变量fp是一个函数指针,那么如何调用fp所指向的函数呢?调用方法如下:

    1
    (*fp)();

         因为fp是一个函数指针,那么*fp就是该指针所指向的函数,所以(*fp)()就是调用该函数的方式。

         表达式(*fp)()中,*fp两侧的括号非常重要,因为函数运算符()的优先级高于单目运算符*。如果*fp两侧没有括号,那么*fp()实际上与*(fp())的含义完全一致。

         现在剩下的问题就只是找到一个恰到的表达式来替换fp。我们将在分析的第二步来解决这个问题。如果C编译器能够理解我们大脑中对于类型的认识,那么我们可以这样写:

    1
    (*0)()

         上式并不能生效,因为运算符*必须要一个指针来做操作数。而且这个指针还应该是一个函数指针,这样经运算符*作用后的结果才能作为函数被调用。因此,在上式中必须对0作类型转换,转换后的类型可以大致描述为:“指向返回值为void类型的函数的指针”。

         如果fp是一个指向返回值为void类型的函数的指针,那么(*fp)()的值为void,fp的声明如下:

    1
    viod (*fp)();

         因此,将常数0转型为“指向返回值为void的函数的指针”类型,可以这样写:

    1
    (void (*)())0

         因此,我们可以用(void(*)())0来替换fp,从而得到:

    1
    ( *( void(*)())0)();

         当然,我们用typedef来解决这个问题能够表述更加清晰:

    1
    2
    typedef void (*fp)();
    (*(fp)0)();

    这个问题就可以解决了。

         我们再来考虑signal库函数,一般情况下,程序员并不主动声明signal函数,而是直接使用头文件signal.h中的声明。那么,在头文件signal.h中,signal函数是如何声明的呢?

         首先,让我们从用户定义的信号处理函数开始考虑,这无疑是最容易解决的。该函数可以定义如下:

    1
    2
    3
    void sigfunc(int n){
            /* 特定信号处理部分*/
    }

         函数sigfunc的参数是一个代表特定信号的整数值,此处我们暂时忽略它。

         上面假设的函数体定义了sigfunc函数,因而sigfunc函数的声明可以如下:

    1
    void sigfunc(int );

         现在假定我们希望声明一个指向sigfunc函数的指针变量,不妨命名为sfp。因而sfp指向sigfunc函数,*sfp就代表sigfunc函数,因此*sfp可以被调用。因此我们可以如下这样声明sfp:

    1
    void (*sfp)(int);

         因为signal函数的返回值类型与sfp的返回值类型一样,上式也就声明了signal函数,我们不妨可以如下声明signal函数:

    1
    void (*signal(something))(int);

         此处的something代表了signal函数的参数类型,我们还需要进一步了解如何声明它们。上面声明可以这样理解:传递适当的参数以调用signal函数,对signal函数返回值(为函数指针类型)解除引用,然后传递一个整型参数调用解除引用后所得函数,最后返回值为void类型。因此,signal函数的返回值是一个指向返回值为void类型的函数指针。

         那么,signal函数的参数又是如何呢?,signal函数接受两个参数:一个整型的信号编号,以及一个指向用户定义的信号处理函数的指针。我们此前一定定义了指向用户定义的信号处理函数的指针sfp:

    1
    void (*sfp)(int);

         sfp的类型可以通过将上面的声明中的sfp去掉而得到,即 void(*)(int)。此外,signal函数的返回值是一个指向调用前的用户定义信号处理函数的指针,这个指针的类型与sfp指针类型一致。因此我们可以如下声明signal函数:

    1
    void (*signal(int,void(*)(int)))(int);

         同样地,使用typedef可以简化上面的函数声明:

    1
    2
    typedef void (*HANDLER)(int);
    HANDLER signal(int , HANDLER);

         那么,现在的你对函数指针理解了吗?如果你看完了此篇文章,相信你一定会有意想不到的收获哦!

    参考书籍:C陷阱与缺陷

  • 相关阅读:
    UVA 10618 Tango Tango Insurrection
    UVA 10118 Free Candies
    HDU 1024 Max Sum Plus Plus
    POJ 1984 Navigation Nightmare
    CODEVS 3546 矩阵链乘法
    UVA 1625 Color Length
    UVA 1347 Tour
    UVA 437 The Tower of Babylon
    UVA 1622 Robot
    UVA127-"Accordian" Patience(模拟)
  • 原文地址:https://www.cnblogs.com/xumaojun/p/8541569.html
Copyright © 2011-2022 走看看