# 函数指针 & 回调函数

## :pencil2: 1、指针函数

&#x20;**指针函数：** 本质是一个函数，不过它的返回值是一个指针。其声明的形式如下所示：

```cpp
ret *func(args, ...);
```

指针函数和普通函数没有区别，但有一点需要注意的是： **避免出现返回局部变量指针的情况。**&#x770B;例子：

```cpp
int* getSum(int n)
{
    if (n < 0)
        return NULL;
    int sum = 0;   // static int sum = 0;
    for (int i = 0; i < n; i++)
    {
        sum += i;
    }
    return &sum;
}

int main() {
    cout << *getSum(10) << endl;
    cout << "Wait for a while...\n" << endl;
    int* iptr = getSum(10);
    cout << *iptr << endl;
    return 0;
}
```

在GCC编译器下没有输出，也就是说程序运行到第14行就强行退出了，原因在于，一般的局部变量是存放于栈区的，当函数结束，栈区的变量就会释放掉，如果我们在函数内部定义一个变量，在使用一个指针去指向这个变量，当函数调用结束时，这个变量的空间就已经被释放，这时就算返回了该地址的指针，也不一定会得到正确的值。并且该地址可能会被后面的程序所使用，这时候得到的就不是我们想要的结果。甚至更严重的是，如果因此访问到了不可访问的内容，很有可能造成段错误等程序崩溃的情况。（需要说明的是该程序在`VC++`编译器下可以正常运行，这与编译器的优化有关，但不符合C语言的特性）

解决方法，将局部变量sum声明成static int， 用`static`修饰的变量是静态变量，而静态变量是存放在数据段的，它的生命周期存在于整个程序运行期间，只要程序没有结束，该变量就会一直存在，所以该指针就能一直访问到该变量。还有一种解决方案是使用全局变量，因为全局变量也是放在数据段的，但是并不推荐使用全局变量。

## :pencil2: 2、函数指针

**函数指针** 的本质是一个指针，该指针的地址指向了一个函数。函数的定义是存在于代码段，因此，每个函数在代码段中有着自己的入口地址，**函数指针就是指向代码段中函数入口地址的指针。**&#x5176;声明的形式如下所示：

```cpp
ret (*p)(args, ...);
```

&#x20;`ret`为返回值，`*p`作为一个整体，代表的是指向该函数的指针，`args`为形参列表。其中`p`被称为**函数指针变量** 。

1. `函数指针`的定义形式中的`数据类型`是指`函数的返回值的类型`。
2. `指向函数的指针变量`不是固定指向哪一个函数的，而只是表示定义了一个这样类型的变量，它是专门用来存放函数的入口地址的；在程序中把哪一个`函数的地址`赋给它，它就指向哪一个函数。
3. 在给函数指针变量赋值时，只需给出函数名，而不必给出参数。
4. 在一个程序中，`指针变量p`可以先后指向不同的函数，但一个函数不能赋给一个不一致的函数指针（`即不能让一个函数指针指向与其类型不一致的函数`）。
5. 定义了一个`函数指针`并让它`指向`了一个`函数`后，对函数的`调用`可以通过`函数名调用`，也可以通过`函数指针调用`（即用指向函数的指针变量调用）。
6. 函数指针只能指向函数的入口处，而不可能指向函数中间的某一条指令。不能用`*(p+1)`来表示函数的下一条指令。
7. `函数指针变量`常用的用途之一是`把指针`作为`参数`传递到其他`函数`。、

### :microphone: 2.1、三种声明方式

```cpp
int func(int a, int b)
{
    cout << "func(int, int)" << endl;
    return 0;
}

int func(int a, int b, int c)
{
    cout << "func(int, int, int)" << endl;
    return 0;
}

//方式1 :声明一种函数类型
typedef int(MY_FUNC)(int, int);  // MY_FUNC是一个函数

//方式2 :声明一种函数类型的指针类型
typedef int(*MY_FUNC_P)(int, int);// MY_FUNC_P是一个函数指针

int ()
{
    //1.方式1举例
    MY_FUNC* fp = NULL;
    fp = func;
    fp(15, 20);

    //2.方式2举例
    MY_FUNC_P fp1 = NULL;
    fp1 = func;
    fp1(25, 30);

    //方式3 :直接通过指针类型创建，不需用typedef预定义。
    int(*fp3)(int, int) = NULL;    // pf3是指向某函数的指针
    fp3 = func; //fp3 ---> func(int,int)
    fp3(27, 89);

    //实际上在给函数指针赋值的时候，是会发生函数重载匹配的
    //在调用函数指针的时候，所调用的函数就已经固定了。
    int(*fp4)(int, int, int) = NULL;
    fp4 = func; //fp4 ---> func(int,int,int)
    fp4(10, 30, 30);
    
    // 获取函数指针的大小
    unsigned psize = sizeof ( int (*) (int, int));
    cout << psize << endl;  // 4

    return 0;
}
```

## :pencil2: 3、回调函数

**函数注册和回调：回调函数**是对函数指针的应用，通过使用函数指针来调用一个函数，而**函数注册**就是把函数指针作为参数传递出去便于别的模块使用的过程。回调函数可以把调用者与被调用者分开，所以调用者不关心谁是被调用者以及被调用者如何实现。

在设计模式中注册回调的方式叫做回调模式。在`SDK`开发中，为增强开发者的`SDK`通用性，排序或者一些算法逻辑需要使用者进行编写。这时候就需要向`SDK`传递回调函数。 注册回调能使下层主动与上层通信。从而避免了上层不停询问下层的模式。`SDK`的接口会提供一个注册回调函数，来规范回调函数的格式，如返回值，参数等。使用者编写一个固定格式的回调函数，来调用`SDK`提供的注册回调函数。

在c中注册回调的实现方式是通过函数指针，在c++中可以通过function和bind实现。

```cpp
// SDK
typedef void(*P)(string s);//使用函数指针通常先typedef
P p = nullptr;//sdk模块创建函数指针对象

void print(P p, string str)//回调函数
{
    p = prin;
    p(str);
}
//使用者
void my_print(string str)//回调函数
{
    printf(str.c_str());
}

int test1()
{
    print(my_print, "hello word");
    return 0;
}
```

许多编译器有几种调用规范。如在Visual C++中，可以在函数类型前加`_cdecl`，`_stdcall`或者`_pascal`来表示其调用规范（默认为`_cdecl`）。C++ Builder支持`_fastcall`调用规范。调用规范影响编译器产生的给定函数名，参数传递的顺序（从右到左或从左到右），堆栈清理责任（调用者或者被调用者）以及参数传递机制（堆栈，CPU寄存器等）。
