函数指针 & 回调函数
✏️ 1、指针函数
指针函数: 本质是一个函数,不过它的返回值是一个指针。其声明的形式如下所示:
ret *func(args, ...);
指针函数和普通函数没有区别,但有一点需要注意的是: 避免出现返回局部变量指针的情况。看例子:
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 ∑
}
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
修饰的变量是静态变量,而静态变量是存放在数据段的,它的生命周期存在于整个程序运行期间,只要程序没有结束,该变量就会一直存在,所以该指针就能一直访问到该变量。还有一种解决方案是使用全局变量,因为全局变量也是放在数据段的,但是并不推荐使用全局变量。
✏️ 2、函数指针
函数指针 的本质是一个指针,该指针的地址指向了一个函数。函数的定义是存在于代码段,因此,每个函数在代码段中有着自己的入口地址,函数指针就是指向代码段中函数入口地址的指针。其声明的形式如下所示:
ret (*p)(args, ...);
ret
为返回值,*p
作为一个整体,代表的是指向该函数的指针,args
为形参列表。其中p
被称为函数指针变量 。
函数指针
的定义形式中的数据类型
是指函数的返回值的类型
。指向函数的指针变量
不是固定指向哪一个函数的,而只是表示定义了一个这样类型的变量,它是专门用来存放函数的入口地址的;在程序中把哪一个函数的地址
赋给它,它就指向哪一个函数。在给函数指针变量赋值时,只需给出函数名,而不必给出参数。
在一个程序中,
指针变量p
可以先后指向不同的函数,但一个函数不能赋给一个不一致的函数指针(即不能让一个函数指针指向与其类型不一致的函数
)。定义了一个
函数指针
并让它指向
了一个函数
后,对函数的调用
可以通过函数名调用
,也可以通过函数指针调用
(即用指向函数的指针变量调用)。函数指针只能指向函数的入口处,而不可能指向函数中间的某一条指令。不能用
*(p+1)
来表示函数的下一条指令。函数指针变量
常用的用途之一是把指针
作为参数
传递到其他函数
。、
🎤 2.1、三种声明方式
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;
}
✏️ 3、回调函数
函数注册和回调:回调函数是对函数指针的应用,通过使用函数指针来调用一个函数,而函数注册就是把函数指针作为参数传递出去便于别的模块使用的过程。回调函数可以把调用者与被调用者分开,所以调用者不关心谁是被调用者以及被调用者如何实现。
在设计模式中注册回调的方式叫做回调模式。在SDK
开发中,为增强开发者的SDK
通用性,排序或者一些算法逻辑需要使用者进行编写。这时候就需要向SDK
传递回调函数。 注册回调能使下层主动与上层通信。从而避免了上层不停询问下层的模式。SDK
的接口会提供一个注册回调函数,来规范回调函数的格式,如返回值,参数等。使用者编写一个固定格式的回调函数,来调用SDK
提供的注册回调函数。
在c中注册回调的实现方式是通过函数指针,在c++中可以通过function和bind实现。
// 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寄存器等)。
最后更新于
这有帮助吗?