函数式编程

函数式编程的主要思想其实就是“回调函数”,即把一个函数A当成参数传给另一个函数B,这样B的就可以根据传入形参的不同,而能够做更多的事情

实现回调函数主要可以通过以下几种方式传入可调对象作为形参:

  • 函数指针
  • lambda表达式
  • std::function
  • 函数对象(重载了()运算符的类)

函数指针

1.定义:函数指针是指向一个函数入口地址指针C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数

2.语法:

<返回值类型> (*<函数指针名>)(<形参列表>) ,如果形参列表为void,则说明没有参数。

(*<函数指针名>)是一个整体,括号不能省

1
2
3
4
//如:
int (*fun)(int,int);
int (*p)(); //函数指针的另一种定义方式,不过不建议使用
int (*p)(int a, int b); //也可以使用这种方式定义函数指针

使用时要注意:

  • 参数的数量、类型保持一致
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void test04()
{
vector<int> v;
for (int i = 0; i < 5; i++) {
v.push_back(i);
}
int total = accumulate(v.begin(), v.end(), 100);
cout << "total = " << total << endl;
}

int main()
{
void (*p)() = test04;//直接用函数名和&函数名是一样的,都返回入口地址
p();
}

3.函数指针数组:顾名思义,就是一个数组,数组的每一项都指向不同的函数

1
2
void (*p[3])() = { test01,test04,test02 };//数组的每一项都指向不同的函数
p[1]();//调用test04

4.typedef 在函数指针中的用法

在函数指针中,也可用typedef起别名,从而快速的声明函数指针,但用法和其他地方不太一样。

简单的理解方法:将函数指针看成是一个模板类,只有正确的写出”模板类型“(形参列表),才能定义出对象。

1
2
3
4
typedef int (*MyFUN)(int,int);//MyFUN就成了int (*MyFUN)(int,int)的别名
int Max(int a,int b);
MyFUN pMyFun;//这样pMyFun成了一个函数指针
pMyFun= Max;

使用MyFUN这个别名代表了整个函数指针类型,就像typedef name vector<int>一样

重要!!!!!!:

MyFUN 也可以做函数的形参,但传入被调用的返回值类型不是MyFUN,而由typedef那句话决定.

比如LVGL的event回调

函数指针.png

这里形参中回调函数的类型是 lv_event_cb_t

实际是他是typedef void (*lv_event_cb_t)(lv_event_t * e);即给函数指针起的一个别名,因此在传入回调函数时,得传入返回值是void、形参是lv_event_t*数据类型的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void btn_event_cb(lv_event_t* envent)
{
lv_obj_t* obj = lv_event_get_target(envent);//获取触发事件的对象
if (obj == btn)
{
lv_obj_del(obj1);//子对象同时都会被删
//lv_obj_clean(obj1);//只处理子对象
cout << "The button has benn delete!" << endl;
}
}
lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_CLICKED,NULL);

//或者
lv_event_cb_t p =btn_event_cb;
lv_obj_add_event_cb(btn, p, LV_EVENT_CLICKED,NULL);

实际该函数lv_obj_add_event_cb()的调用流程应该是:首先 lv_event_cb_t一个函数指针event_cb指向你传入的那个回调函数的地址,然后在程序内用这个函数指针进行函数调用操作。

5.通过结构体 + 函数指针 实现C语言中的面向对象(成员函数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
typedef struct
{
int m_age;
void (*fun)();
}student;

void bark1()
{
cout << "LRQQQQQ" << endl;
}

void bark2()
{
cout << "V 50 " << endl;
}

int main()
{
student s1;
s1.fun = bark1;
s1.fun();

student s2;
s2.fun = bark2;
s2.fun();
}

lambda表达式

Lambda 表达式(也称为匿名函数)是 C++11 引入的一种便捷的语法,它允许你在需要一个函数对象的地方快速定义一个函数,而不需要显式地定义一个完整的函数或函数对象类。Lambda 表达式通常用于简短的、临时的函数,它们可以捕获并包含其外部作用域中的变量

Lambda 表达式的基本语法如下:

1
[capture] (parameters) -> return_type { function_body }
  • capture:捕获子句,用于指定可以从 lambda 表达式外部捕获哪些变量。可以使用 [](不捕获任何变量)、[&](通过引用捕获所有外部变量)、[=](通过捕获所有外部变量)、或者通过名称显式捕获变量。
  • parameters:参数列表,定义了 lambda 表达式可以接受的参数。
  • return_type:返回类型(非必须),如果你希望明确指定 lambda 表达式的返回类型,可以在这里指定。如果不指定,默认情况下,编译器会根据函数体推断返回类型。
  • function_body:函数体,即 lambda 表达式要执行的代码。

一个简单的例子:

1
2
3
4
int value = 5;
auto lambda = [&] (int x) -> int {
return x + value;
};

std::function

std::function 是 C++11 标准库中的一个模板类,定义在 <functional> 头文件中。它是一个通用的多态函数封装器,可以存储、调用和传递任何可调用对象,包括普通函数、成员函数、Lambda 表达式、函数对象以及成员函数指针。

基本用法:

1
2
3
4
5
6
7
int add(int a, int b) {
return a + b;
}

std::function<int(int, int)> func; //声明一个函数封装器,并指定其可以绑定函数的类型

func = add; //绑定函数

使用示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <functional>

int add(int a, int b) {
return a + b;
}

int main() {
// 存储一个普通函数
std::function<int(int, int)> f = add;
std::cout << f(1, 2) << std::endl; // 输出 3

// 存储一个 Lambda 表达式
f = [](int a, int b) -> int { return a * b; };
std::cout << f(2, 3) << std::endl; // 输出 6

// 存储一个函数对象
struct Multiplier {
int operator()(int a, int b) { return a / b; }
};
Multiplier m;
f = m;
std::cout << f(6, 3) << std::endl; // 输出 2
}

std::function也可以作为函数的形参,以此实现回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
#include <vector>
#include <functional>

// 被回调的函数
void print(int a, int b) {
std::cout << "111" << std::endl;
}

// 如果std::function封装的函数还需要别的形参,得放在std::function的后边传入,而不是写在泛型里
void fun(std::function<void(int, int)> f, int a, int b) {
f(a, b);
}

// std::function作为形参且需要绑定一个成员函数
void callMemberFunction(std::function<void(MyClass*)> func, MyClass* obj) {
obj->fun();
}

int main() {
fun(print, 1, 2);

fun([](int a, int b) -> void {
std::cout << "222" << std::endl;
},1,2);
}

如果std::function作为函数的形参,同时我们想传入一个类的成员函数,也就是我们想让回调函数是一个类的成员函数,有2个实现方式:

1.使用lambda表达式

1
2
3
4
5
6
7
8
9
void fun(std::function<void()> f) {
f();
}

// 这里把成员函数和类的实例当成lambda表达式的内容传入
Myclass obj;
fun([&]()-> void {
obj.fun1();
});

2.使用std::bind

std::bind是C++标准库中的一个函数模板,它用于创建一个新的可调用对象(函数、Lambda表达式、函数对象或成员函数),这个新的可调用对象在被调用时会以特定的参数序列调用原函数。

std::bind的声明如下:

1
2
template< class F, class... Args >
unspecified bind( F&& f, Args&&... args );

这里的F是可调用对象的类型,Args...是参数列表

使用std::bind的基本步骤:

  1. 选择要绑定的函数:这可以是普通函数、成员函数、Lambda表达式或函数对象。
  2. **调用std::bind**:传递函数和要绑定的参数。
  3. 获取结果std::bind返回一个std::function<ReturnType(Args...)>类型的对象,你可以像调用普通函数一样调用它。

下面给一些例子:

1
2
3
4
5
6
7
8
9
10
int add(int a, int b) {
return a + b;
}

int main() {
// bind普通函数
std::function<void(int)> boundAdd = std::bind(add, 5, std::placeholders::_1);
std::cout << boundAdd(10); // 输出 15
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MyClass {
public:
void setValue(int value) {
this->value = value;
}
int value;
};

int main() {
MyClass obj;
//绑定成员函数
std::function<void(int)> boundSetValue = std::bind(&MyClass::setValue, &obj, 42);
boundSetValue(); // 设置 obj.value 为 42
std::cout << obj.value; // 输出 42
return 0;
}

指针函数

1.定义:它本身是一个函数,但是返回值是一个指针。

2.语法:

<返回值类型>* <函数指针名>(<形参列表>) ,如果形参列表为void,则说明没有参数。

1
2
int* fun(int a);
//int*是一个整体,说明fun的返回值类型是一个int型指针
1
2
3
4
5
6
7
8
9
10
11
12
int* func(int n)
{
static int sum;
sum = n * 100;
return
}

int main()
{
int* a = func(2);
cout << *a << endl;
}

使用时要注意,不能局部变量的地址,因为局部变量存在stack区,函数结束后就被销毁了,这样a就成了野指针。

回调函数

1.定义:回调函数就是一个一个形参列表中带有函数指针的函数调用的函数。 回调函数并不是由实现方直接调用,而是在特定的事件或条件发生时由另外一方来调用的。目的类似于面向对象中的多态,为了减少代码量。但又有区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int func(int a,int b,int (*p)(int,int));
int __add(int a,int b);
int __sub(int a,int b);
int __mul(int a,int b);

int main(){
int a = 2,b = 3,re;
re = callback(a,b,_add);
printf("%d\n",re);
return 0;

int func(int a,int b,int (*p)(int,int))
{
return p(a,b);
}

/*这个函数是回调函数,*/
int __add(int a,int b){
return a+b;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int callback(int a)
{
return a * 10;
}

void fun(int (*p)(int))//调用回调的那个函数,形参必须有函数指针
{
cout << p(10) << endl;
}

typedef int (*cb_t)(int);
void fun1(cb_t event_cb)//调用回调的那个函数,形参必须有函数指针
{
cout << event_cb(20);
}

//2种不同的方法使用回调函数
int main()
{
fun(callback);
fun1(callback);
}
  • STM32 HAL库中的回调函数不是真正意义上的回调函数,因为他并没有函数指针,但是从使用场景看,确实是回调函数。