从放弃到重启 C++—函数指针

143 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第15天,点击查看活动详情

有的时候我们写技术文章,可绘画也有点类似,一个好的画家首先不会专注过多细节,而是从画面整体主题出发,大量色块来渲染画面氛围,只要氛围对了,方向正确接下来就好办了。而作为我这样门外汉往往会从细节开始,注意线条准确性,到最后细节做的比较足,反而整体效果却不是预期的。

函数指针

函数指针是指向一个存储函数的内存地址的变量,随后可以通过函数指针来调用该函数。在 js 这样函数是一等公民的语言里,可以将一些行为抛出给调用者来定义,可以通过传递函数作为参数来控制行为,将函数赋值给一边变量func = function(){return something},然后通过 func() 来直接调用这个函数。

注意函数名称为函数地址起始位置

我们都是知道函数是代码块,是一个可以完成一定功能的代码集合,通常可以理解行为,例如求和,或者添加一个按钮。有时候我们将一件复杂的事划分为若干连续动作,动作之间界限可以根据功能进行设定。

使用函数指针的场景

函数作为参数

如果要写一个排序函数,可能想让函数的调用者来选择数据的排序方式,可能是升序排序、有的时候也可能需要降序排序,以及是采用冒泡排序、快速排序还是堆排序这些都交给调用者来实现,这样就具有更大灵活性。所以可以使用函数指针,然后将

回调函数

函数指针的另一个用途是设置 "监听器 "或 "回调 "函数,当一个特定的事件发生时来调用函数。这个在 js 是非常常见的,通过回调来实现异步编程。

那么我们结合自己了解异步编程场景来说一说如何使用回调函数。例如我们监听按钮点击事件,当用户点击按钮就会触发一个事件,所以这里点击就是事件,那么发生这个事件,需要有相应处理,或者用户对网络地址发起请求来获取数据,应该网络请求是耗时的操作并且具有不确定,具体需要多长时间请求的服务器才会响应也是不确定的,所以这样需要回调机制来实现,当请求数据返回后对数据的处理。

下面代码create_button 来在屏幕上创建一个按钮,x 和 y 其位置,const char* text 表示按钮上文字,function 就是一个回调函数,可以监听用户点击事件,然后对点击按钮进行处理

void create_button(int x, int y, const char* text, function callback_func);

有些程序员可能需要以升序排序,有些人可能喜欢降序排序,还有一些人可能想要类似于但不完全是这些选择之一的东西。让你的用户指定做什么的一种方法是提供一个标志作为函数的参数,但这是不灵活的;排序函数只允许一组固定的比较类型(例如,升序和降序)。

函数指针声明与使用

声明函数指针的语法看起来比较晦涩难懂,但是随着不断接触,可能你就会对其有一定认识,其实并没有想象那么难。

void (*func)(int);

大多数人可能都困惑与上面式子。

我们简单读解一下上面 ,func 是一个指向函数的指针,接受一个类型为int(整型)的参数,函数返回一个 void。这就好像声明了一个名为 *func 的函数,接受 int 类型作为参数,并返回void 类型值。现在*func 是一个函数,那么 func 就应该是函数的指针。(这个和之前学习定义指针比较类似 int *x 声明了指向存放 int 类型内存地址,所以 *x 是一个 int 类型的数据,那么 x 就是是一个指向存放 int类型内存地址的指针)。

声明函数指针

void my_func(int a)
{
    std::cout << "a: " << a << std::endl;
}

int main()
{
    void (*func)(int);
        func = &my_func;

    return 0;
}

函数指针的调用

要调用一个函数指针所指向的函数,你要把这个函数指针当作你想调用的函数的名字。调用它的行为执行了解除引用;不需要自己去做。

(* func)(3);

请注意,函数指针的语法是比较灵活的,有时候过于灵活也不算好。在这里可以大多数指针使用方法一样,可以通过 & 来对指针取址,* 取值符号,func 和数组有点类似,本身就是一个指针,可以直接 func() 这样调用。

func(5);

函数指针应用例子

排序

让我们回到排序这个例子上,个人建议使用函数指针来写一个泛化排序程序

void qsort(void *base, size_t nmemb, size_t size, int(*compar)(const void*, const void*))

注意这里使用 void*s 来允许 qsort 对任何类型的数据进行操作(在 C++ 中,通常会使用模板来完成泛型的任务,但是 C++ 也允许使用 void* 指针),因为 void* 指针可以指向任何类型的数据内存地址。所以并不知道 void*组成数组中元素的个数,这里 nmemb 用于提供数组中元素的个数,还需要提供 base 还需要提供数组的中元素的的大小。

但是我们真正感兴趣的是 qsort 的比较参数:它是一个函数指针,接收两个void *s并返回一个int。这使得任何人都可以指定如何对数组基数的元素进行排序,而不需要编写专门的排序算法。请注意,compar返回一个int;如果第一个参数小于第二个参数,指向的函数应该返回 -1,如果它们相等,则返回 0,如果第二个参数小于第一个参数则返回 1。

函数指针数组

根据状态变量取值来调用函数。

void run() { printf("start\r\n"); }
void stop() { printf("stop\r\n"); }
void exit() { printf("exit\r\n"); }

static void (*command[])(void) = {run, stop, exit};

int OnStateChange(uint state) {

    if (state > 3) {
        printf("Wrong state!\n");
        return false;
    }

    command[state]();
    return 0;
}

函数指针小结

语法

声明

声明函数指针,虽然可以使用 *func_pointer 来声明一个函数指针。

void (*func_pointer)(int);

调用指针函数

  • 可以获取函数的地址然后
func_pointer = &func;
(* func_pointer)(a);

函数指针的作用

  • 函数指针提供了一种传递如何做某事的指令的方式
  • 可以编写更加灵活的函数和库,允许使用者通过传递函数指针来自定义行为
  • 这种灵活性也可以通过使用带有虚拟函数的类来实现