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


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

    函数地址

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

    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;
    }
  • 相关阅读:
    JS实现延迟载入图片
    三星指纹识别新专利:手势打开不同应用
    与计算机之间的另一种沟通方式 ——“手势识别”
    手写数字识别系统之图像分割
    机器学习实战八大分类器识别树叶带源码
    构建CTC语音识别解码网络
    MFC CListCtrl 条目取消选中
    C++ 将输入的字符串中英文大写字母改成对应小写字母,并且过滤掉非英文字母字符
    C++遍历SQLite数据库下的所有表名 .
    MFC 操作注册表 Open QueryValue等
  • 原文地址:https://www.cnblogs.com/smart-zihan/p/11343853.html
Copyright © 2020-2023  润新知