• 函数指针与回调函数


    引入:数组

    我们知道,在c/c++语言中,无论是数组中存储的是哪一种数据类型的元素,数组都占一块连续的内存空间。而数组名就是首元素的地址,也就是数组所占的连续内存空间的首字节的地址。如程序所示:

    #include<cstdio>
    #include<iostream>
    #include<string>
    
    using namespace std;
    
    int main()
    {
        int a[] = {0,1,2,3,4,5,6};
        cout << a << endl;
        cout << &a[0] << endl;
        cout << &a[1] << endl;
        int* p = a;
        cout << *p+100 << endl;
        cout << *(p+1) << endl;
        return 0;
    }

    上述代码输出结果可以说名,数组名可以当作该数组的指针使用,也就是数组名是一个指针变量,指向数组所占据的连续的内存空间。或者说数组名可以被认为数操作该内存区域的入口。

    主题:函数的存储

    一个函数在内存空间中也总是占据一块连续的内存空间。而函数名往往就是这块连续内存空间的首地址。编译器在进行编译时,每个函数都有一个入口地址,这个入口地址就是函数所占连续内存空间的首地址,也就是函数名。在这一点上与数组的存储非常相似。同时,这个函数所占据的连续内存空间的首地址就是指向该函数的指针。我们把这个函数的首地址赋予一个指针变量,使该指针变量指向函数所在的内存空间,那么我们就可以通过这个指针变量找到并调用该函数。这个指针变量就是函数指针。函数指针通常有两个用途:调用该函数和将该函数作为函数的参数。

    上段我们提到,数组名是函数所占的连续内存空间的首地址,所以我们在创建函数指针的时候会使用函数名来创建。将函数名的所代表的地址赋给一个函数指针变量。

    函数指针的定义类型为

    returntype(*pointername)(param list);
    
    //returntype是这个函数指针指向的函数的返回值类型
    //pointername 是自己所定义的函数指针的名字
    //param list 是函数参数列表。该列表可同时给出参数的类型和名称,也可只给出参数类型。
    
    //因为()的优先级高于*,所以第一个括号不能省略。如果写成returntype* pointnerame(param list);就会称为一个新定义的函数原型。

    创建函数指针的完整语句和使用函数指针的完整语句

    函数名本身就是指向函数所在的连续空间,是该连续空间的首地址。而我们在创建函数指针的时候,创建的指针变量也是存储的是函数所占连续空间的首地址,所以在一定程度上,函数名和创建的指针变量可以认为是相同的。所以我们在使用函数指针的时候和使用函数名的方法类似。

    returntype (*pointername)(param list) = functionname;
    //functionname为函数名
    //使用定义好的函数指针

    //执行函数指针指向的函数

    (*pointername)(param list);

    如下例子有

    #include<cstdio>
    #include<iostream>
    #include<string>
    
    using namespace std;
    
    void test()
    {
        cout << "hello,world" << endl;
    }
    int max(int a,int b)
    {
        if (a > b)
        {
            return a;
        }
        return b;
    }
    int main()
    {
        void(*function_point)() = test;
        (*function_point)();
    
        int(*maxmum)(int,int) = max;
        int c = (*maxmum)(3, 10);
        cout << c << endl;
        
        return 0;
    }

    下面再介绍两种新的函数指针使用方法

    #include<cstdio>
    #include<iostream>
    #include<string>
    
    using namespace std;
    
    void test()
    {
        cout << "hello,world" << endl;
    }
    int max(int a,int b)
    {
        if (a > b)
        {
            return a;
        }
        return b;
    }
    int main()
    {
        //typedef 原类型名 别名; 是用来给变量的数据类型起一个别名
        //但typedef也可以用来定义新的数据类型
    
        typedef void(*function_pointer)();
        //定义了一种函数指针。这个函数指针类型名为function_pointer。
        //这个函数指针指向一个函数,并且函数的参数表为空,并且函数的返回值为空。
    
        typedef int(*maxmum)(int a,int b);
        //定义了一种函数指针,这个函数指针的类型名为maxmum
        //这个函数指针指向一个函数,且函数的参数表只有一个int型参数,并且函数的返回值为int型。
        
        function_pointer p1 = test;
        maxmum p2 = max;
    
        //调用函数
        p1();
        int c = p2(3,10);
        cout << c << endl;
        
        return 0;
    }

    接下来进入我们今天的主题——回调函数

    首先看一下百度百科对于回调函数的解释:

    回调函数就是一个通过函数指针调用的函数。如果你把函数的指针作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这这个被函数指针指向的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的时间货条件发生时由另外的一方调用的,用于对该事件或条件的响应。

    维基百科对于回调函数的解析:

    把一段可执行的代码像参数一样传递给其他代码,而这段代码会在某个时刻被调用执行,这就叫回调。如果代码被立即执行就称为同步回调,如果在之后晚点的某个时间被调用执行,则称为异步回调。

    再看一位大神的表述:函数F1调用函数F2的时候,函数F1通过参数传递给函数F2传递了一个函数F3的指针,在函数F2执行的过程中,函数F2调用了函数F3,这个动作就叫做回调。而先被当作指针传入、后面又被回调的函数F3就是回调函数。

    综上所述:

    在使用回调函数的过程中必须有三种函数:F1、F2和F3。不妨将其分别称为主调函数、中间函数和回调函数。

    在c/c++语言中,我们的主函数就是main()函数,它主导程序自上向下执行,调度各个函数的运行。回调函数就是一个能够独立实现功能的函数,它可以通过函数指针被当作其他函数的参数。中间函数可以认为是调用回调函数的函数,它要完成登记回调函数、通知函数执行成功的任务。

    回调函数在一些可能情况下,可能会觉得与函数调用没什么不同。

    回调函数无参数的时候的代码

    #include<cstdio>
    #include<iostream>
    #include<string>
    
    using namespace std;
    
    int callback1()
    {
        cout << "this is callback1" << endl;
        return 0;
    }
    
    int callback2()
    {
        cout << "this is callback2" << endl;
        return 0;
    }
    int callback3()
    {
        cout << "this is callback3" << endl;
        return 0;
    }
    
    //回调函数要作为中间函数的参数传入,所以要在中间函数的参数列表中定义指向回调函数的指针
    //值传递其实是在函数的内存空间中完成了从实参赋值给形参的拷贝过程。
    //也就是在函数的内存空间中完成了 形参=实参;的赋值语句
    //所以当函数被当作参数传递的时候其实是 returntype(*pointername)(param list) = function_name;的语句
    //所以中间函数的参数表中出现的是 returntype(*pointername)(param list),在这条语句中,创建的形参名是pointername
    //因为pointername是函数指针变量名,而在一定程度上函数名也可以认为存储函数所占连续空间的首地址的函数指针变量。
    //所以pintername可被认为是函数的别名,在调用的时候,就把原来函数名应该出现的地方,也就是pointername(param list)也可以表示原来的函数。
    //而在使用函数名的时候后面会有括号,以表示这是一个函数并且可能会传递参数,所以使用pointername的时候也应该表示加上括号
    int middle_function(int(*callback)())
    {
        cout << "this is middle function" << endl;
        callback();
        return 0;
    }
    
    int main()
    {
        middle_function(callback1);
        middle_function(callback2);
        middle_function(callback3);
        return 0;
    }

    回调函数有参数的时候,程序示例

    #include<cstdio>
    #include<iostream>
    #include<string>
    
    using namespace std;
    
    int callback1(int a,int b)
    {
        cout << "this is callback1" << endl;
        cout << "a=" << a << endl;
        cout << "b=" << b << endl;
        return 0;
    }
    
    int callback2(int c)
    {
        cout << "this is callback2" << endl;
        cout << "c=" << c << endl;
        return 0;
    }
    
    
    //注意当有参数时函数的调用方式。不光在写函数指针的时候添加的参数表,还在前面又添加了与参数表相应个数的
    //在中间函数的内部在调用函数的时候也要在里面写上相应的参数
    int middle_function(int x,int y,int(*callback)(int x,int y))
    {
        cout << "this is middle function" << endl;
        callback(x,y);
        return 0;
    }
    
    //写函数指针时只写了参数类型
    int middle_function1(int z, int(*callback)(int ))
    {
        cout << "this is middle function" << endl;
        callback(z);
        return 0;
    }
    
    int main()
    {
        middle_function(3,10,callback1);
        middle_function1(8,callback2);
        return 0;
    }
  • 相关阅读:
    C#读写INI配置文件(转)
    关于DBNull
    XNA项目运行错误:No suitable graphics card found.
    C#3.0新增特性
    Windows 8 Metro开发疑难杂症(三)——导航(2),数据保存,数据虚拟化
    Windows 8 Metro开发疑难杂症(一)——导航
    windows 8 metro 风格开发(7)发布一个常用控件类库
    Windows 8 Metro开发疑难杂症(四)——(伪)数据库
    windows 8 metro 风格开发(9)Interactive(Behavior和EventTrigger)
    Windows 8 Metro开发疑难杂症(六)——APP的挂起状态
  • 原文地址:https://www.cnblogs.com/hxhlrq/p/12344960.html
Copyright © 2020-2023  润新知