C/C++中函数指针与回调函数
1.函数指针
(1)函数指针的定义
函数:完成某一个特定功能的代码块;
指针:它是一种特殊的变量,用来保存地址值,指针是有类型的,某类型的指针指向某类型的地址。
函数指针:顾名思义,通过一个指针指向一个函数的地址
定义格式:返回值 (*指针名)(参数列表);
比如:
int add(int a, int b)
{
return (a + b);
}
这段代码在编译生成后存储在代码区,而这段代码其实是可以取到地址,而其地址就是函数名。我们可以使用指针存储这个函数的地址—函数指针。
对于上述函数的函数指针定义:
int (*p)(int,int) //参数列表中可以写形参也可以不写
也可以写成
int (*)(int a, int b)
(2)通过函数指针去调用函数
直接像使用函数名一样使用函数指针来调用函数,即:
函数指针名(参数列表)
示例:
#include <stdio.h>
// 两个数相加
int add(int a, int b)
{
return (a + b);
}
// 求平方
int square(int a)
{
return (a*a);
}
int main()
{
int(*p)(int, int);
//printf("p未做赋值前 = %p\n", p); //未做赋值p的值是不确定的
// printf("p未做赋值前 p(1,2) = %d\n", p(1,2)); 在定义后未指向任何函数就调用它,程序运行时崩溃
p = add; //p指向add函数
int c = p(2, 300); //使用p来调用add函数
printf("c = %d\n", c);
int(*p_func)(int a) = NULL; //一般定义的时候可以先初始化为NULL
p_func = square; //p指向square函数
int d = p_func(2); //使用p来调用square函数
printf("d = %d\n", d);
return 0;
}
(3)函数指针的匹配问题
函数指针的最终目的,就是通过函数指针可以访问到指向的函数,而函数指针是一种强类型指针,定义一定要百分百类型匹配(返回值类型,参数类型,参数个数要相同),最简单的方式就是将函数名换成(*指针名)
但不同函数但函数指针类型一样,那定义的函数指针类型变量可以指向不同的函数,如:
#include <stdio.h>
int add(int a, int b)
{
return (a + b);
}
int del(int c, int d)
{
printf("del c = %d, d = %d\n", c, d);
return (c - d);
}
int del2(int c, char d)
{
printf("del2 c = %d, d = %d\n", c, d);
return (c - d);
}
int main()
{
int(*p_add)(int, int) = NULL;
int(*p_del)(int, int) = NULL;
// 运算 不同的函数其函数指针类型可能是一样,具体看 (1)返回值,(2)参数类型,(3)参数个数, 都需要匹配上
int(*p_opt)(int, int) = NULL;
int(*p_opt2)(int, char) = NULL; // 匹配del2
int ret = 0;
p_add = add;
ret = p_add(1, 300);
printf("p_add(1, 300) = %d\n", ret);
p_del = del;
ret = p_del(1, 300);
printf("p_del(1, 300) = %d\n", ret);
p_opt = add; // 加法
ret = p_opt(1, 300);
printf("加法 p_opt(1, 300) = %d\n", ret);
p_opt = del;
ret = p_opt(1, 300);
printf("减法 p_opt(1, 300) = %d\n", ret);
p_opt2 = del2; // 不匹配del,只能匹配del2
ret = p_opt2(1, 100); // 不匹配
printf("减法 p_opt2(1, 300) = %d\n", ret); // 结果错误
return 0;
}
2.回调函数
(1)什么是回调函数?
回调函数(callback function)指通过函数指针调用的函数!
示例:
#include <stdio.h>
int add(int a, int b)
{
return (a + b);
}
int del(int a, int b)
{
return (a - b);
}
// 运算
void opt1(int a, int b, int type)
{
if (0 == type) // 加法
{
printf("运算结果 = %d\n", add(a, b));
}
else if ((1 == type)) // 减法
{
printf("运算结果 = %d\n", del(a, b));
}
else
{
printf("未知的运算方法\n");
}
}
// 函数指针回调的方式做运算
void opt2(int a, int b, int(*callback)(int, int))
{
if (callback)
printf("运算结果 = %d\n", callback(a, b));
}
int main()
{
// 加法
opt1(1, 300, 0);
// 减法
opt1(1, 300, 1);
// 新的写法
printf("\n新的写法\n");
//加法
opt2(1, 300, add);
//减法
opt2(1, 300, del);
return 0;
}
上述代码中:
在opt2的写法中,它把具体要调用哪一个函数交给了调用者,而运算函数只提供统一的函数指针接口,把具体的业务放到外面来,运算函数内部根本不知道add和del等函数的存在,实现了隔离
而在opt1的写法中,运算函数是知道add和del等函数的存在的
补充:上述的判断语句if(0 == type)中先写数字的好处:
如果少写了一个=的话编译器会报错,而写在右边不会报错不容易找到错误,所以遇到这种数字的判断就把数字写在等号的前面
(2)回调函数的意义
可以提供统一的调用接口,把具体要做的事情放到回调函数(回调函数做什么工作是根据你应用需求)。比如线程的创建,但线程具体的工作需要在回调函数中指定。
示例:
#include <stdio.h>
#include <windows.h>
DWORD WINAPI ThreadFunc1(LPVOID p)
{
printf("我是Darren老师,我要撸代码了\n");
int i = 0;
while (1)
{
if(i++ > 5)
{
break; // 退出线程
}
printf("我是Darren老师,写了%d行代码了\n", i*100);
Sleep(1000);
}
printf("我是Darren老师,代码撸完了\n");
return 0;
}
DWORD WINAPI ThreadFunc2(LPVOID p)
{
printf("我是Martin老师,我要上Linux公开课了\n");
int i = 0;
while (1)
{
if(i++ > 10)
{
break; // 退出线程
}
printf("我是Martin老师,上了%d分钟课了\n", i*5);
Sleep(1000);
}
printf("我是Martin老师,公开课上完了\n");
return 0;
}
int main()
{
HANDLE hThread1;
DWORD threadId1;
HANDLE hThread2;
DWORD threadId2;
hThread1 = CreateThread(NULL, 0, ThreadFunc1, 0, 0, &threadId1); // 创建线程
hThread2 = CreateThread(NULL, 0, ThreadFunc2, 0, 0, &threadId2); // 创建线程
printf("两个线程创建完毕\n");
WaitForSingleObject(hThread1, INFINITE);//等待线程返回,用sleep()就太山寨了
WaitForSingleObject(hThread2, INFINITE);
CloseHandle(hThread1); // 释放资源
CloseHandle(hThread2);
printf("程序结束\n");
return 0;
}
图示理解:
在Linux下也是一样,创建一个线程也是设置一个回调函数,让不同的回调函数指向不同的业务