第二十二篇 -- 研究下函数(五)—— 函数指针

所谓函数指针就是指向函数的指针。函数指针也是一个变量,可以指向不同的函数。同时通过函数指针可以调用其指向的函数,从而使得函数调用更加灵活。

函数地址

函数也是有地址的。编译之后的函数,其实是一组指令的集合。这样一组指令在程序运行时存在于内存中,其起始地址就是该函数的地址,也称做函数的入口地址。在编写程序时,可以用函数名来表示函数的地址,也可以在函数名之前加上取地址符号“&”表示函数的地址。这两种方式是等价的。例如,对于下列求和函数:

int Add(int a, int b)
{
  return a + b;
}

 

函数名“Add”以及“&Add”都表示Add函数的地址。如图8.10所示。

typedef int (* FUN_PTR) (int a, int b);  //定义函数指针类型FUN_PTR
FUN_PTR pf1;
FUN_PTR pf2;

 

        图8.10 内存中的Add函数

 如图8.10所示,内存地址0x401390即函数Add在内存中的地址。可以使用强制类型转换,将该地址保存在一个长整型变量中:

    long a = (long)Add;       //使用C语言方式的类型转换
    long b = (long)&Add;
    long c = static_cast<long>(Add);     //使用C++语言方式的static_cast操作符
    long c = static_cast<long>(&Add);

 

另外,也可以通过库函数或者cout对象将Add函数的地址输出到显示器上:

    printf("%p", Add);   //调用print函数,输出Add函数的地址
    printf("%p", &Add);
    cout << hex << Add;   //使用cout对象输出Add函数的地址,其中hex表示以16进制方式输出
    cout << hex << &Add;

 

 

定义函数指针

定义函数指针包括两个部分,即声明和初始化。在函数声明的基础上做一个小小的改变,就可以将其变成一个函数指针的声明。例如对于Add函数,只要将函数名Add替换成一个函数指针名,例如“pf”,并在其前面加上“(*”、在后面加上“)”即可:

int (*pf) (int a, int b);   //定义函数指针变量pf

 

当心:函数指针名“*pf”两侧的括号不能省略,否则就成了一个返回“int *”类型的函数声明。正式这个括号使得星号“*”和标识符“pf”组成了一个整体,表示pf是一个指针。

对于函数指针的声明,参数列表和返回类型是关键部分。只要这两项确定,就确定了该函数指针的类型。这个类型也决定了该函数指针所能指向的函数类型,即具有相同参数列表和返回类型的函数。

有的时候函数的参数列表和返回值比较复杂,每次定义这样的函数指针都要重写一遍比较繁琐。因此可以用类型定义运算符“typedef”为该函数定义一个简单的类型名。有了这样一个类型名之后,就可以用来定义函数指针变量,而不用重写函数参数列表和返回类型。例如:

 

    typedef int(*FUN_PTR) (int a, int b);   //定义函数指针类型FUN_PTR
    FUN_PTR pf1;
    FUN_PTR pf2;

 

虽然相比定义函数指针变量,定义函数指针类型只是多了一个typedef关键字。但也正因为如此,FUN_PTR也不再是一个变量,而变成了一个类型。同普通指针一样,如果没有明确初始化,则函数指针的值是一个随机数,使用这样的指针非常危险。因此在使用函数指针之前必须对其进行初始化或者赋一个初值,即将一个函数名赋给该函数指针变量。例如:

int Add(int a, int b)
{
    return a + b;
}

int(*pf1)(int a, int b) = Add;
typedef int(*FUN_PTR) (int a, int b);
FUN_PTR pf2 = Add;
FUN_PTR pf3 = &Add;

 

 

使用函数指针调用函数

使用函数指针调用函数同函数调用一样,只要在函数指针后面加上实参列表即可。例如:

int Add(int a, int b);

int main()
{
    
    int(*pf)(int a, int b) = Add;
    int x = pf(3, 4);
    cout << "x = " << x << endl;

    return 0;
}

int Add(int a, int b)
{
    return a + b;
}

 

使用函数指针时不必像使用一般指针那样解引用。不过有时为了明确起见,也可以解引用。例如:

int x = (* pf) (3, 4);   //函数指针解引用

 

这样做的好处是可以明确指明pf是一个函数指针,否则只看到定义,才能分辨出pf到底是一个函数指针,还是一个函数。

 

函数指针的用途

如果仅仅是像上节那样使用函数指针,那就有点儿画蛇添足了,还不如直接使用函数来得方便。实际上,函数指针通常用做回调函数。所谓回调函数是指将一个函数用函数指针保存下来,然后直到需要的时候在进行调用。至于函数指针到底保存的是什么函数,则由设置者决定,而调用者是无从知晓的。这个调用过程称作回调,这个函数指针称作回调函数指针,简称回调函数。

比如现在有一个排序函数sort,用来排序一个数组。但是按照什么标准排序(从大到小,还是从小到大),则需要由sort函数的调用者确定。调用者可以通过给函数传递一个函数指针来确定排序的标准。下面使用回调函数确定排序标准。

void sort(int ary[], int n, FUN_PTR pf);

int main()
{
    cout << "——使用函数指针确定排序标准——" << endl;
    int ary[3] = {5, 3, 6};

    bool less(int a, int b);     //声明函数less
    sort(ary, 3, &less);         //使用less回调函数,从大到小排列数组

    for (int i = 0; i < 3; i++) {
        cout << ary[i] << ' ';
    }
    cout << endl;

    bool big(int a, int b);      //声明函数big
    sort(ary, 3, &big);          //使用big回调函数,从小到大排列数组

    for (int i = 0; i < 3; i++) {
        cout << ary[i] << ' ';
    }
    cout << endl;

    return 0;
}

void sort(int ary[], int n, FUN_PTR pf) {
    for (int i = 0; i < n - 1; i++) {
        int index = i;
        int val = ary[i];
        for (int j = i + 1; j < n; j++) {
            if (pf(val, ary[j])) {
                val = ary[j];
                index = j;
            }
        }
        if (val != ary[i]) {
            ary[index] = ary[i];
            ary[i] = val;
        }
    }
}

bool less(int a, int b) {
    return a < b;
}

bool big(int a, int b) {
    return a > b;
}

 

posted @ 2019-08-13 09:20  o云淡风轻o  阅读(194)  评论(0编辑  收藏  举报