C++:函数:回调函数:还不懂回调函数来捶我(二)

回调函数目录

1:什么是回调函数

2:为什么需要回调函数

3:有哪些函数可以做回调函数

4:小结

1:什么是回调函数 

回调函数本质上也是普通函数,只是调用机制有所区别,----首先通过传参的形式将该函数的地址传递给其他函数,然后在其他函数中通过函数指针调用该函数,那么在其他函数中通过函数指针调用该函数的过程就称为:回调。而作为被调用的该函数则被称为回调函数。下面我们一步一步解释,为什么需要回调函数。

2:为什么需要回调函数

1:  这就不得不说联合开发带来的后果,-----接口兼容性问题,举个例子:程序员小A和小B联合开发一个项目,要求小A开发的函数必须为小B开发的函数提供灵活的接口。

// test.h
#pragma once
#include
using namespace std;

// 声明一个接口,提供给小B,开发
int add(int, int);

int Add(int a, int b) {
	cout << add(a, b) << endl;
}


// main.cpp
#include "test.h"
#include
#include
using namespace std;

// 小B定义接口。是不是和JAVA里面接口回调很相似。
int add(int a, int b) {
	return a + b;
}

void main() {
	Add(1, 2);
}

2:  现在为了并行开发:开发内容没有变化,但是需要小B单独在另一个文件中开发,来提高工作效率。

// test.h
#pragma once
#include
using namespace std;

// 声明一个接口,提供给小B,开发
int add(int, int);

inline void Add(int a, int b) {
	cout << add(a, b) << endl;
}

// test.cpp
#include "test.h"
#include
#include
using namespace std;

// 小B定义接口。是不是和JAVA里面接口回调很相似。
int add(int a, int b) {
	return a + b;
}


// main.cpp
#include "test.h"
#include
#include
using namespace std;

void main() {
	Add(1, 2);
}

 3: 但是这-似乎也没什么,不就是在一个函数中调用另一个函数吗?(简单来说就是:小A声明一个接口,小B定义这个接口,然后在合适时机小B定义的这个函数被调用。)

  • 但是你是否意识到为了实现上面功能:小A和小B必须提前商量好接口的名称,返回值,参数列表。
  • 对于小A来说,每次调用小B函数,需要先声明,函数多了将会非常麻烦
  • 对于小B来说,由于小A定义的函数体对于小B来说是不知道的,小B的函数是如何传入小A也是不那么直观,而且小B定义函数必须和小A声明的函数保持一致。
  • 那么为了解决上述问题,回调函数登场了

2.1:回调函数登场

1:小A不再需要每次调用小B定义的函数之前,都要进行声明。

2:小A只需要提供一个函数指针来接收小B传过来的函数地址,而不用在考虑函数名

3:小A只需要用这个指针可以直接调用小B定义的函数。

4:小B可以自己起函数名。

5:通过函数指针实现回调函数

// test.h
#pragma once
#include
using namespace std;

// 定义一个匿名的函数指针
inline void Add(int(*callbackFun)(int, int), int a, int b) {
	cout << callbackFun(a, b) << endl;
}

// test.cpp
#include "test.h"
#include
#include
using namespace std;

// 小B定义接口。是不是和JAVA里面接口回调很相似。
inline int add(int a, int b) {
	return a + b;
}


// main.cpp

#include "test.h"
#include "test.cpp"
#include
#include
using namespace std;

void main() {
	Add(add,1, 2);
}

显然从上面的例子可以看出:回调函数必须通过指针进行传递和调用,为了简化代码,一般会将函数指针起各别名,格式为:

             typedef  返回值类型   (*指针名) (参数列表) 

回调函数规避了在调用函数前声明的弊端,而且能够让用户直观的感受到自动定义的函数被调用,小A需要在声明函数指针时规定参数列表,小B在定义回调函数时需要与小A声明的函数指针保持相同的参数列表。

当然如果小A提前为回调函数形参设置了默认值,那么小B也可以决定不使用。

// test.h

#pragma once
#include
using namespace std;

// 定义函数指针类型
typedef int(*callbackFun)(int, int, int);

inline void Add(callbackFun callback, int a, int b,int c = 10) {
	cout << callback(a, b,c) << endl;
}


// test.cpp
#include "test.h"
#include
#include
using namespace std;

// 小B定义接口。是不是和JAVA里面接口回调很相似。
inline int add(int a, int b,int c) {
	return a + b +c;
}


// main.cpp
#include "test.h"
#include "test.cpp"
#include
#include
using namespace std;

void main() {
	Add(add,1, 2);
}

 3: 有哪些函数可以做回调函数

可以做回调函数的函数在C++中目前有两种情况,第一种C语言风格函数,第二种静态成员函数。第一种就是上面的格式,这个我们已经做 了介绍,下面详细说说静态成员函数。

3.1: 静态成员函数做回调函数 

众所周知:类的非静态成员函数的参数列表中隐含了一个this指针,当用类的对象访问类的非静态成员函数时,编译器慧将this指针指向该对象,从而保证了函数体中操纵的成员变量是该对象的成员变量,即使你没有写this指针,编译器在编译的时候,还是会自动添加this指针。

那么这就会造成一个问题:非静态成员函数的参数列表和函数指针参数列表个数无法匹配。

如下所示:函数指针callbackFun有两个参数,非静态成员函数隐含this指针,从而有三个参数,显然不匹配。


#include
#include
using namespace std;

class AddClass {
private:
	int a, b;
public:
	int add(int a, int b);
};

int AddClass::add(int aa, int bb) {
	this->a = aa;
	this->b = bb;
	return a + b;
}

void Add(int(*callbackFun)(int, int), int c =0, int d = 0) {
	cout << callbackFun(c, d) << endl;
}

void main() {
	//Add(add,1, 2);
	Add(AddClass().add, 1, 2);   // error : 指向绑定函数的指针只能用于调用函数
    // 显然非静态成员函数参数列表有 三个参数
    //  而 函数指针:int(*callbackFun)(int ,int) 只有两个参数
}

但是你如果执着,使用非静态成员函数,就只能通过全局函数中转,如下例所示:


#include
#include
using namespace std;

class AddClass {
private:
	int a, b;
public:
	int add(int a, int b);
};

int AddClass::add(int aa, int bb) {
	a = aa;
	b = bb;
	return a + b;
}

void Add(int(*callbackFun)(int, int), int c =0, int d = 0) {
	cout << callbackFun(c, d) << endl;
}

int MyAdd(int a, int b) {
	AddClass addClass;
	return addClass.add(a, b);
}

void main() {
	//Add(add,1, 2);
	Add(MyAdd, 1, 2);
}

 由于:类的静态成员函数属于类,为所有对象共享,它没有this指针,因此这里我们采用静态成员函数作为回调函数,但是这样我们遇到另一一个问题:静态成员函数无法方位类的非静态成员。

因此为了解决上述这个问题,我们需要在静态成员函数参数列表中做个修改:

在形参列表中加入 万能指针 *void 作为形参,然后在函数体中做类型强转。

// test.h

#pragma once
#include
using namespace std;

// 定义函数指针类型
typedef int(*callbackFun)(int, int, void*);

inline void Add(callbackFun callback, int a, int b,void* p) {
	cout << callback(a, b, p) << endl;
}


// test.cpp
#include "test.h"
#include
#include
using namespace std;

class AddClass {
private:
	int a, b;
public:
	inline static int add(int a, int b,void* temp);
};

int AddClass::add(int aa, int bb,void* p) {
	AddClass* temp = (AddClass*)p;
	if (temp)
	{
		temp->a = aa;
		temp->b = bb;
	}
	return temp->a + temp->b;
}


// main.cpp
#include "test.cpp"
#include
#include
using namespace std;

void main() {
	//Add(add,1, 2);
	AddClass* addclass = new AddClass;
	Add(AddClass::add, 1, 2, addclass);
	delete addclass;
}

4:  小结

1:回调函数本质其实是对函数指针的一种应用,上面的例子都比较简单,还没有完成体现回调函数的威力。

2:回调函数是一种设计系统的思想,能够解决系统架构中的部分问题,但是系统中不宜过多使用回调函数,因为回调函数会改变整个系统运行轨迹和执行顺序,耗费资源,而且会使代码变得臃肿

3:C++ STL中大量使用了回调函数的例子,比如遍历函数 for_each()中的 lambda表达式就是一个回调函数。

#include
#include
#include
using namespace std;

int main(){
    vector v{1,2,3,4,5};
    for_each (v.begin(), v.end(), [=](int val){

        cout<< val<< " "<< endl;
    });
}

五:回调函数使代码延迟执行 


#include 
#include
#include
using namespace std;

class A
{
    std::function callback_;
public:
    A(const std::function& f) :callback_(f) {
        cout << "构造函数" << endl;
    };

    void notify(void)
    {
        cout << "notify" << endl;
        callback_();
    }
};
class Foo {
public:
    void operator()(void)
    {
        // __FUNCTION__是对应的函数名
        std::cout << __FUNCTION__ << std::endl;
    }
};
int main(void)
{
    Foo foo;
    // 仿函数调用
    foo();
    // 仿函数对象表示:匿名的 function
    A aa(foo);
    aa.notify();
}

你可能感兴趣的:(#,C++精华,C++回调函数)