前言:笔者在学习数据结构经典问题“四种方法求解最大子列和”时,遇到了一个需求:计时是重复动作,因此想创建一个可以为算法运行时间计时的函数。由于计时的特殊性(被计时的函数要被掐头又掐尾),因此要设计一个能调用函数的函数。这就涉及到 回调函数 / 函数指针 的应用了(初学C/C++时,不了解回调函数提出的意义,现在因为自己的需求明白了)。
附:最大子列和问题的四种解法为:暴力法O(n3)、不做重复加法O(n2)、二分法O(n log2(n))、在线处理法O(n),详细解析请见笔者的笔记算法实例:最大子列和,实现代码于ds_cou_1_maxSum.cpp。
需求分析
#include <time.h>
#include <stdio.h>
#define MAXK 1e5
clock_t start, stop;
double duration;
start = clock();
for (int i = 0; i < MAXK; i++)
{
foo(10, 1.1);
}
stop = clock();
duration = ((double)(stop - start) / CLK_TCK / MAXK);
printf("ticks2 = %f\n", (double)(stop - start));
printf("duration2 = %6.2e\n", duration);
如上图,foo()为被计时函数,start与stop需要分别在foo()调用前后取值。现在要将计时功能封装,则其设计概念如下。
double timeCal(function())
{
clock_t start, stop;
double duration;
start = clock();
for (int i = 0; i < MAXK; i++)
{
function(); // 这个function需要能传入
}
stop = clock();
duration = ((double)(stop - start) / CLK_TCK / MAXK);
return duration;
}
检索解决需求的方案(回调函数)
搜索:C中,如何将函数作为参数在另一个参数中调用?
方案:使用回调函数。
“回调函数…这个名词好像在大一下的课上听过”,先不管那么多了…开始学习回调函数的使用,实现我的需求。
回调函数
指针可以指向数值,当然也可以指向函数(函数指针)
理解回调函数,首先要了解函数指针这个概念。
我们知道,数据是存储在计算机内存上的,计算机要使用这个数据时,要找到这个数据在内存上的位置,就是地址。
而指针,就是指向地址的变量。 如下图蓝线所示。
数据存在内存上,那函数存在哪里呢?
答:函数也存在内存上呗!函数说白了,就是一堆指令,也是一串01组成的数据。
那自然,也要有指针,用来指向函数的地址,就是函数指针。 如下图。
在c中,我们用星号(*)声明指针,自然,函数指针也是用星号声明的。例子如下。
int a = 1; // 声明整数变量
// (为变量a申请一块内容空间,并赋值)
int foo() { return 0; } // 声明函数
int *p_int = &a; // p_int 是指针,值为a的地址
int (*p_func)() = foo; // p_func 是指针,指向foo()函数
回调函数就是把函数指针放在参数表中的函数
理解了函数指针,就不难理解回调函数了:把函数指针放在参数表中的函数。
则,我们的计时函数可以写成:
double timeCal(int (*MaxSubseqSum)(int *, int), int A[], int N)
{
clock_t start, stop;
double duration;
start = clock();
for (int i = 0; i < MAXK; i++)
{
MaxSubseqSum(A, N);
}
stop = clock();
duration = ((double)(stop - start) / CLK_TCK / MAXK);
return duration;
}
可以在main中如下调用:
int main()
{
int a[15] = {-1, 4, -1, -3, 5, 1, -6, 3, 3, 5, -21, 1, -3, 6, -2};
int N = 15;
double duration[4];
duration[0] = timeCal(MaxSubseqSum1, a, N);
duration[1] = timeCal(MaxSubseqSum2, a, N);
duration[2] = timeCal(MaxSubseqSum3, a, N);
duration[3] = timeCal(MaxSubseqSum4, a, N);
for (int i = 0; i < 4; i++)
printf("duration_%d = %6.2e \n", i + 1, duration[i]);
return 0;
/*
duration_1 = 1.46e-006
duration_2 = 2.80e-007
duration_3 = 2.70e-007
duration_4 = 3.00e-008
数据规模小,分而治之的方法与不做重复加法的方法相比,优势不明显。
*/
}
其中,*MaxSubseqSum代表一个函数指针,而MaxSubseqSum()这一系列的函数要使用两个参数(一个整型数组 / 也可以是数组首个元素的地址、一个整型变量),因此我们要在回调函数中传入他们。
如上图,*MaxSubseqSum就好像一个…固定了形状的零件?根据main()指令,只要是符合【返回值类型为int,传入参数为int[]与int】这标准形状的,*MaxSubseqSum就可以变成它,并去调用,实现其功能。
参考资料
[1] 这个世界根本没有什么面向对象!
[2] C 函数指针与回调函数 | 菜鸟教程
[3] 函数指针与回调函数
[4] C语言:值传递,地址传递和引用传递(example:值交换)