函数指针

​ 我们经常看到"面向对象"、“万物皆对象”。

​ 人,有双手、双脚、会使用工具。

​ 狗,有四条腿,会咬人。

​ 以上两个例子进行抽象,代码中就可以出现people、dog这些“类”、实例化出“一个人、一只狗”。

​ 借助生活中看得见摸得着的事物,很容易理解什么是对象,我们很容易理解什么是"抽象。

​ 我们通常理解的对象是:有属性(变量)、有行为(方法)的集合体。

​ 这次内容聚焦在行为、”方法“上。

如何理解”函数“也是一个对象,如何把函数作为对象来使用?

多数初学者在刚开始接触编码的时候,潜意识里对于函数指针都有点抵触。

#include <iostream>
using namespace std;

void printHello() {cout << "Hello!" << endl;}

// 定义一个函数指针类型
typedef void (*FuncPtr)();

// 定义一个函数,它接受一个函数指针参数
void invoke(FuncPtr func) {func();}

int main() {
    // 声明一个函数指针变量,并将其初始化为printHello函数的地址
    FuncPtr funcPtr = printHello;
    // 将函数指针变量传递给invoke函数,invoke函数会调用该函数指针所指向的函数
    invoke(funcPtr);
    return 0;
}

在上面的代码中,我们首先定义了一个名为printHello的函数,该函数用于输出字符串"Hello!"。

接着,我们定义了一个FuncPtr类型的函数指针,它指向一个无返回值、无参数的函数。

然后,我们定义了一个invoke函数,该函数接受一个函数指针参数,并在函数体内调用该函数指针所指向的函数。

最后,在main函数中,我们声明了一个函数指针变量funcPtr,并将它初始化为printHello函数的地址。然后,我们将funcPtr作为参数传递给invoke函数,从而实现对printHello函数的调用。

可以看到,在这个示例中,我们使用函数指针来代表函数,同时也将函数指针作为普通对象来使用,使得程序的执行更加灵活和具有定制性。

函数指针,它具有以下几个方面的特征:

  1. 函数指针可以被传递和赋值:与普通的变量一样,函数指针可以被传递给其他函数或者赋值给其他变量。
  2. 函数指针可以作为参数传递:函数指针可以作为参数传递给其他函数,这使得程序的结构更加清晰,也便于扩展和维护。
  3. 函数指针可以定义类型:函数指针本身可以定义为一种数据类型,并且可以像其他变量一样进行声明和定义。
  4. 函数指针可以作为返回值:函数指针可以作为函数的返回值,这在实现多态性等特定场景下非常有用(面向对象中的虚函数是实现多态常见的方式,但实现多态的方式不局限在虚函数).

它其实就是一个特殊的指针,它用于指向函数被加载到的内存首地址,可用于实现函数调用。

普通的函数指针

申明

函数指针就是一种特殊的指针。

如果你要声明一个变量:

int a ;

而一个指针呢:

int *a;

那一个函数指针,就是在一个变量指针的写法基础上加一个括号,告诉他这是一个指向函数的指针就可以:

int (*a)();

这样,a就是一个函数指针了。

这时候呢,int (*a)();就声明了一个函数指针变量a,它可以指向一个返回int,参数列表为空的函数。

前面的int,就是这个函数指针的返回值,a是变量名,最后一个()是参数列表。

这个括号(*a)一定要加,否则就成了int *a();编译器会认为这是一个 返回int *的函数a;

赋值

直接将一个已经定义的函数名,赋值给函数指针就可以:a = function;

当然,直接把声明定义和初始化写在一起也可以,只是平常不多见这么写:int (*a)() = function;

和上面先声明再赋值是等价的。

调用

函数指针的变量,可以当做函数名一样被调用,所以直接:a();就相当于调用了函数。

注意这是声明的一个函数指针的变量,和函数的声明有所区别。

因此你不能像定义一个函数一样定义一个函数指针,你只能声明出这个指针,然后给他赋值一个函数签名匹配的已经定义好的函数名:

int function()  // 正确的函数声明
{
    return 0;
}
 
int (*a)()      // 错误:这是一个变量,不能当函数一样定义
{
    return 0;
}

//你只能这样:
int (*a)();     //声明一个函数指针变量a,
int main()
{
    a = function;   //给函数指针赋值。
    a();            //通过函数指针调用
    
    // 也可以直接把声明和赋值写在一起:这就像是 int i;和int * p = i;的区别
    int (*b)() = function;
    b();

    return 0;
} 

稍微复杂一些的函数指针

// 最简单的函数及其对应的函数指针:
void f();
void (*f_ptr)();
// 复杂点的,带返回值和参数列表,但都是基本类型
int f(double b, int i);
int (*f_ptr)(double b, int i);
// 返回值和参数带上指针,再加上几个const混淆一下
const double * f(const double * b2, int m);
const double * (*f_ptr)(const double * b2, int m);
// 再复杂一点点,参数里加个函数指针 也不是很复杂,基本只要把函数名换成(*函数名) 就可以了
int f(int (*fp)(),int a );
int (*f_ptr)(int (*fp)(),int a ); 
// 稍微再复杂一点点,返回值是一个函数指针:(光是普通函数返回函数指针,语法就有点费劲。我们一步一步来:)
//// 首先搞一个返回void的普通函数:
void f();
//// 假设返回一个函数指针,这个函数指针返回值和参数都为空。我们用一个函数指针替换掉返回值void就可以了
//// 感觉应该写成这样:void (*fp)() f();
//// 但是这个样子显然过不了编译的,得要变一下:
void (* f())();         //这就是一个参数为空,返回函数指针的函数。
void (*(*f_ptr)())();    //把f替换成(*f_ptr),这就成了返回函数指针的函数指针。

使用typedef把函数指针变得优美

#include <iostream>
using namespace std;

void printHello() {
    cout << "Hello!" << endl;
}

void invoke(void (*func)()) {
    func();
}

int main() {
    void (*funcPtr)() = printHello;
    invoke(funcPtr);

    return 0;
}

首先,在代码中我们定义了一个名为printHello的函数,该函数用于输出字符串"Hello!"。

然后,我们定义了一个名为invoke的函数,它接受一个名为func的函数指针参数。在函数体内,我们通过调用func()来间接调用传入的函数指针所指向的函数。

main函数中,我们首先声明了一个名为funcPtr的函数指针变量,它指向无返回值、无参数的函数。然后,我们将printHello函数的地址赋值给funcPtr变量。

最后,我们调用invoke函数,并将funcPtr作为参数传递给它。invoke函数会调用传入的函数指针所指向的函数,从而实现对printHello函数的调用。

以下是使用typedef对函数指针进行美化的C++代码示例:

#include <iostream>
using namespace std;

// 定义一个函数指针类型
typedef void (*FuncPtr)();

// 使用typedef定义一个更易读的别名
typedef FuncPtr Callback;

void printHello() {
    cout << "Hello!" << endl;
}

void invoke(Callback func) {
    func();
}

int main() {
    // 将printHello函数指针赋值给funcPtr变量,然后将它传递给invoke函数
    Callback funcPtr = printHello;
    invoke(funcPtr);

    return 0;
}

虽然不使用typedef也可以实现同样的功能,但是通过使用函数指针类型别名,我们可以更加清晰地表达程序的意图,同时也减少了代码中重复的类型声明,从而提高了代码的可读性和可维护性,使得函数指针看起来更像一个“对象”。

类的函数指针

​ 函数指针是指向函数的指针,而我们上面提到的函数,一直都是面向过程的函数,对于面向对象的函数还只字未提。

类静态成员函数指针

类的静态成员函数没有this指针,它的存储方式和普通的函数是一样的,可以取得的是该函数在内存中的实际地址

所以静态的成员函数指针的声明和调用,和普通函数指针没有任何区别:

  • 声明:void (*static_fptr)();
  • 调用:static_fptr();

唯一有区别的,就是赋值。因为要传的是一个类的静态成员函数的地址,所以赋值的时候,要加上类名限定:

  • void (*static_fptr)() = &Test::staticFunc;

同样,通过typedef把它搞成类型用法和之前也一样,可以使代码更清晰。

类成员函数指针

与静态函数不同,成员函数在被调用时,必须要提供this指针。

因为在它被调用之前,自己也不知道哪个对象的此函数被调用。所以通过&拿到的不是实际的内存地址。

只有调用的时候,C++才会结合this指针通过固定的偏移量找到函数的真实地址调用。

为了支持这种调用方式,这里C++给专门提供了特殊的几个操作符:::* .* ->*

  • 声明:void (Test::*fptr)();,类成员函数指针的声明,就必须加上类名限定,这就声明了一个函数指针变量fptr,他只能指向Test类的成员函数。
  • 赋值:fptr = &Test::function
  • 调用:类的成员函数是无法直接调用的,必须要使用对象或者对象指针调用(这样函数才能通过对象获取到this指针)。
    • (t.*fptr)();,t是Test类的一个实例,通过对象调用。
    • (pt->*fptr)();,pt是一个指向Test类对象的指针,通过指针调用。

C++成员函数的调用需要至少3个要素:

  1. this指针;(提供运行环境)
  2. 函数参数(也许为空);(提供运行条件)
  3. 函数地址。(提供运行目标)

上面的调用中,

->*.*运算符之前的对象指针提供了this(和真正使用this并不完全一致)

​ 参数在括号内提供

​ fptr`则提供了函数地址。

应用场景

C++函数指针具有以下几个好的应用场景:

  1. 回调函数:函数指针可以作为回调函数来实现,当某个事件发生时,程序可以通过调用该函数指针所指向的函数来通知注册了该函数指针的其他函数。
#include <iostream>
using namespace std;

typedef void (*Callback)();

static Callback callback;

// 注册回调函数
void registerCallback(Callback func) {
    cout << "Registering callback function..." << endl;
    // ...
    callback=func;
}

// 触发回调函数
void triggerCallback() {
    cout << "Triggering callback function..." << endl;
    callback();
}

// 回调函数
void onCallback() {
    cout << "Callback function invoked!" << endl;
}

int main() {
    // 注册回调函数
    registerCallback(onCallback);

    // 触发回调函数
    triggerCallback();

    return 0;
}

在上面的示例中,我们使用函数指针作为回调函数来实现程序的事件处理。首先,我们定义了一个名为registerCallback的函数,它接受一个Callback类型的参数,并将该函数指针保存在某个注册表中。然后,我们定义了一个名为triggerCallback的函数,并在函数体内调用传入的函数指针所指向的函数。

  1. 函数对象:函数指针可以被看作函数对象,它可以被传递和赋值,从而实现更加灵活和具有定制性的程序处理。
#include <iostream>
using namespace std;

class Calculator {
public:
    int add(int x, int y) { return x + y; }
    int sub(int x, int y) { return x - y; }
};

typedef int (Calculator::*FuncPtr)(int, int);

int main() {
    Calculator calc;
    FuncPtr funcPtr = &Calculator::add;
    cout << (calc.*funcPtr)(1, 2) << endl;
    funcPtr = &Calculator::sub;
    cout << (calc.*funcPtr)(3, 2) << endl;
    return 0;
}

在上面的示例中,我们定义了一个名为Calculator的类,它包含两个成员函数addsub,这两个函数分别实现加法和减法操作。

然后,我们定义了一个名为FuncPtr的函数指针类型,并将其初始化为Calculator类中的成员函数指针类型。接着,在main函数中,我们声明了一个名为calcCalculator类对象,并通过函数指针调用addsub成员函数来执行加法和减法操作。

总之,通过使用C++函数指针,我们可以实现函数的动态绑定、回调函数、函数对象等功能,从而使得程序更加灵活和具有可扩展性。

Lambda表达式

C++ lambda是一种匿名函数,它可以在代码中内联定义并使用。

Lambda表达式提供了一种便捷的方式,用于在不需要定义完整函数时编写短小的代码块。

Lambda表达式可以存储在变量中,并作为参数传递给其他函数。

Lambda表达式的语法如下:

[capture-list] (params) -> return-type { body }

其中,

capture-list指定lambda表达式所需的外部变量。

params指定lambda表达式的输入参数。

return-type指定lambda表达式的返回类型。

body指定lambda表达式的执行体。

以下是一个简单的示例,展示了如何使用lambda表达式来定义一个加法运算:

#include <iostream>
using namespace std;

int main() {
    // 使用lambda表达式实现加法
    int abc=0;
    auto add = [](int x, int y)->int { return x + y; };

    // 调用lambda表达式
    cout << "3 + 4 = " << add(3, 4) << endl;

    return 0;
}

在上面的代码中,我们使用lambda表达式定义了一个名为add的函数对象,该函数对象接受两个int类型的参数,并返回它们的和。然后,我们调用add函数对象,以计算3 + 4的值并输出结果。

外部变量

C++ lambda表达式访问外部变量的形式有以下两种:

值捕获

  1. 值捕获:使用=符号来指定lambda表达式对外部变量的访问方式。在执行lambda表达式时,将会拷贝一份外部变量的值到lambda表达式内部,并在执行期间对该拷贝进行操作。这种方式适用于只需要读取外部变量值,不需要修改外部变量的情况。

例如,以下lambda表达式采用值捕获方式,将外部变量x的值拷贝到其内部,并输出它的值:

int x = 10;
auto func = [=]() { cout << "x = " << x << endl; };
func(); // 输出:x = 10

引用捕获

引用捕获:使用&符号来指定lambda表达式对外部变量的访问方式。在执行lambda表达式时,将会将外部变量的引用传递给lambda表达式内部,并在执行期间直接操作外部变量本身。这种方式适用于需要修改外部变量的情况。

例如,以下lambda表达式采用引用捕获方式,将外部变量x的引用传递给其内部,并修改它的值:

int x = 10;
auto func = [&]() { x++; };
func();
cout << "x = " << x << endl; // 输出:x = 11

需要注意的是,当使用引用捕获方式时,要确保外部变量在lambda表达式执行期间仍然有效。如果在lambda表达式执行后,外部变量已经超出生命周期,则访问它将会导致未定义行为。

注意事项

以下是使用C++代码帮您讲解lambda外部变量的作用、区别和注意事项:

#include <iostream>
using namespace std;

int main() {
    // 定义外部变量
    int x = 10;

    // 使用lambda表达式访问外部变量
    auto func1 = [=]() { cout << "x = " << x << endl; };
    auto func1Ref = [&]() { cout << "x = " << x << endl; };
    func1(); // 输出:x = 10
    func1Ref();// 输出:x = 10

    // 更新外部变量值
    x = 20;   
    func1(); // 输出:x = 10
    func1Ref();// 输出:x = 20
    
    // 再次调用lambda表达式,访问更新后的外部变量值
    auto func2 = [=]() { cout << "x = " << x << endl; };
    func2(); // 输出:x = 20

    return 0;
}

在上面的示例中,我们定义了一个名为x的外部变量,并使用lambda表达式来访问它。我们可以使用=&符号来指定lambda表达式对外部变量的访问方式。

需要注意的是,当使用=符号时,lambda表达式只能读取外部变量的值,不能修改它们。如果需要修改外部变量,则需要使用&符号。

另外,需要注意以下几点:

  • 当lambda表达式访问外部变量时,要确保这些变量在lambda表达式执行期间仍然有效。如果在lambda表达式执行后,外部变量已经超出生命周期,则访问它们将会导致未定义行为。
  • 当使用=符号时,lambda表达式只能拷贝外部变量的值,不能拷贝外部变量的引用。因此,在lambda表达式执行期间,如果外部变量被修改,则不会影响到其他代码中对外部变量的访问。
  • 当使用&符号时,lambda表达式会直接操作外部变量的引用。因此,在lambda表达式执行期间,如果外部变量被修改,则可能会影响到其他代码中对外部变量的访问。

返回值

以下是将上面的乘法lambda表达式改为加法lambda表达式的示例:

#include <iostream>
using namespace std;

int main() {
    // 定义lambda表达式,计算两个数的和并返回结果
    auto add = [](int x, int y)  { return x + y; };

    // 调用lambda表达式
    cout << "3 + 4 = " << add(3, 4) << endl;

    return 0;
}

在本示例中,我们仍然使用箭头运算符(->)来指定返回值类型为int

最后,我们调用add lambda表达式以计算3和4的和,并输出结果。

自动推导

C++ lambda表达式可以根据上下文自动推导返回值类型,无需显式指定返回类型。

以下是一个使用C++ lambda表达式自动推导返回值类型的示例,->int 删除即可 :

#include <iostream>
using namespace std;

int main() {
    // 定义lambda表达式,计算两个数的乘积并返回结果
    auto add = [](int x, int y) { return x + y; };

    // 调用lambda表达式
    cout << "3 * 4 = " << add(3, 4) << endl;

    return 0;
}

应用场景

以下是使用C++代码来展示Lambda表达式在不同场景下的应用示例:

作为函数对象传递

#include <iostream>
#include <thread>

using namespace std;

int main() {
    // 创建一个线程,使用Lambda表达式作为线程函数
    thread t([]() { cout << "Thread started!" << endl; });

    // 等待线程执行完成
    t.join();

    return 0;C++
}

在上述示例代码中,我们创建了一个线程,并将其作为参数传递给std::thread()构造函数。这里使用了Lambda表达式作为线程函数,其中Lambda表达式输出一条消息"Thread started!"。

简化代码

#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    int x = 10, y = 20;

    // 使用lambda表达式简化代码实现
    auto max_num = [](int a, int b) { return a > b ? a : b; };

    // 调用Lambda表达式
    cout << "The max number is: " << max_num(x, y) << endl;

    return 0;
}

在上面的示例中,我们使用Lambda表达式来简化代码实现,省去定义一个函数的过程。我们定义了一个Lambda表达式[](int a, int b) { return a > b ? a : b; },它可以获取两个整数参数,并返回一个较大的数。

与STL配合

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int main() {
    vector<int> nums = {5, 3, 1, 4, 2};

    // 使用lambda表达式作为排序方法
    sort(nums.begin(), nums.end(), [](int a, int b) { return a < b; });

    // 输出排序结果
    for (auto num : nums) {
        cout << num << " ";
    }
    cout << endl;

    return 0;
}

在上面的示例中,我们使用Lambda表达式作为排序方法,将一个无名函数作为函数对象传递给了std::sort()算法。这个Lambda表达式接受两个整数参数,并返回一个bool类型的值,在sort()算法中用于确定元素之间的顺序。

事件驱动编程

#include <iostream>
#include <functional>

using namespace std;

int main() {
    function<void()> callback = []() { cout << "Button Clicked!" << endl; };

    // 模拟按钮点击事件,调用回调函数
    callback();

    return 0;
}

在上面的示例中,我们使用Lambda表达式作为回调函数来处理按钮点击事件。我们定义了一个名为callback的函数对象,它封装了一个Lambda表达式[]() { cout << "Button Clicked!" << endl; },该表达式在调用时输出字符串"Button Clicked!"。随后,我们模拟按钮点击事件,并使用回调函数处理此事件,最终输出结果。

总之,Lambda表达式是C++11引入的一个非常强大的新特性,它可以帮助我们编写更加整洁、高效、可维护的代码。在不同的场景下,Lambda表达式有着广泛的应用,包括函数对象传递、简化代码、与STL配合、事件驱动编程等。

std::function

std::function是C++11标准库中的一个通用函数包装器,它可以存储和调用任何可调用对象,例如函数、函数指针、lambda表达式等。

函数指针赋值

#include <iostream>
#include <functional>

using namespace std;

void printHello() {
    cout << "Hello" << endl;
}

void printName(const string& name) {
    cout << "My name is " << name << endl;
}

int add(int x, int y) {
    return x + y;
}

int main() {
    // 定义一个std::function,没有输入参数,没有返回值
    function<void()> f1 = printHello;

    // 调用std::function对象
    f1();

    // 定义一个std::function,一个输入参数为string类型,没有返回值
    function<void(const string&)> f2 = printName;

    // 调用std::function对象
    f2("Tom");

    // 定义一个std::function,两个输入参数为int类型,一个返回值为int类型
    function<int(int, int)> f3 = add;

    // 调用std::function对象
    cout << "3 + 4 = " << f3(3, 4) << endl;

    return 0;
}

​ 在上面的示例中,我们将前面三个示例合并到了一个程序中,分别定义了三个不同的std::function对象,分别封装了无输入参数无返回值、有输入参数无返回值和有输入参数有返回值的函数。

​ 通过使用std::function,我们可以把这些函数都装进一个函数对象里面,方便进行存储和调用。最后,我们调用这三个std::function对象,分别输出对应的结果。

lmabda表达式赋值

​ 上面的示例亦可使用lmabda形式替换

#include <iostream>

using namespace std;

int main() {
    // 使用lambda表达式定义无输入参数无返回值的函数对象
   function<void()> f1 = []() { cout << "Hello" << endl; };

    // 调用lambda表达式对应的函数对象
    f1();

    // 使用lambda表达式定义有输入参数无返回值的函数对象
     function<void(const string&)> f2 = [](const string& name) { cout << "My name is " << name << endl; };

    // 调用lambda表达式对应的函数对象
    f2("Tom");

    // 使用lambda表达式定义有输入参数有返回值的函数对象
    function<int(int, int)> f3 = [](int x, int y) -> int { return x + y; };

    // 调用lambda表达式对应的函数对象
    cout << "3 + 4 = " << f3(3, 4) << endl;

    return 0;
}

std::function提供了一种通用的方式,用于存储和调用各种类型的可调用对象,从而提高代码的灵活性和可维护性。

通过实例化std::function对象,我们可以将函数、函数指针、lambda表达式等各种可调用对象封装为一个统一的函数对象,并方便地进行存储和调用。

案例示例

函数模板与std::funciotn结合示例

这是一个模板函数GroupBy(),它接受一个vector和一个函数对象作为输入参数,并将vector中的元素按照函数结果进行分组。下面是对该函数的注释:

/**
 * 分组函数
 *
 * @param vs    输入的vector容器
 * @param func  接受Type类型参数并返回Fn类型结果的函数对象
 * @return      按照func()函数结果分组后得到的std::map容器
 */
template <class Fn, class Type>
std::map<typename std::result_of<Fn(Type)>::type, std::vector<Type>>
GroupBy(const std::vector<Type>& vs, Fn&& func)
{
    // 使用typename关键字定义keyType类型
    using keyType = std::result_of<Fn(Type)>::type;

    // 定义用于存储分组结果的std::map容器
    std::map<keyType, std::vector<Type>> maps;

    // 遍历输入的vector容器中的所有元素
    for (const auto& item : vs)
    {
        // 使用传入的函数对象计算当前元素对应的keyType类型的值
        keyType result = func(item);

        // 如果当前keyType类型的值还未在maps中出现过,则添加一个新的key-value对
        if (maps.find(result) == maps.end())
        {
            maps[result] = std::vector<Type>();
        }

        // 将当前元素添加到与其对应的key对应的vector容器中
        maps[result].push_back(item);
    }

    // 返回最终分组结果
    return maps;
}

使用上面的函数使用学生成绩进行分组,分组包含三个情况,可以按照班级分组,按照分数段分组,按照男女分组

#include <iostream>
#include <vector>
#include <string>
#include <map>

using namespace std;

// 学生信息结构体
struct Student {
    string name;    // 姓名
    int age;        // 年龄
    char gender;    // 性别
    int score;      // 成绩
};

int main() {
    // 创建学生信息容器
    vector<Student> students = {
        {"张三", 18, 'M', 85},
        {"李四", 17, 'F', 92},
        {"王五", 18, 'M', 73},
        {"赵六", 17, 'F', 88},
        {"小明", 18, 'M', 95},
        {"小红", 17, 'F', 90},
        {"小刚", 18, 'M', 80},
        {"小丽", 17, 'F', 87},
        {"小强", 18, 'M', 90},
        {"小芳", 17, 'F', 83}
    };

    // 定义班级名称映射表
    map<string, string> class_map = {
        {"A", "一年级一班"},
        {"B", "一年级二班"},
        {"C", "二年级一班"},
        {"D", "二年级二班"},
        {"E", "三年级一班"}
    };

    // 按照班级分组
    map<string,vector<Student>> by_class = GroupBy(students, [](const Student& s)  {
        return s.name.substr(0, 1);
    });
    cout << "按照班级分组:" << endl;
    for (auto& group : by_class) {
        cout << "\t" << class_map[group.first] << ":" << endl;
        for (auto& s : group.second) {
            cout << "\t\t姓名:" << s.name << ",年龄:" << s.age << ",成绩:" << s.score << endl;
        }
    }

    // 按照分数段分组
    auto by_score = GroupBy(students, [](const Student& s)  {
        if (s.score >= 90) return "A";
        else if (s.score >= 80) return "B";
        else if (s.score >= 70) return "C";
        else return "D";
    });
    cout << "按照分数段分组:" << endl;
    for (auto& group : by_score) {
        cout << "\t" << group.first << " 分:" << endl;
        for (auto& s : group.second) {
            cout << "\t\t姓名:" << s.name << ",年龄:" << s.age << ",成绩:" << s.score << endl;
        }
    }

    // 按照性别分组
    map<char,vector<Student>> by_gender = GroupBy(students, [](const Student& s)  {
        return s.gender;
    });
    cout << "按照性别分组:" << endl;
    for (auto& group : by_gender) {
        cout << "\t" << (group.first == 'M' ? "男生" : "女生") << ":" << endl;
        for (auto& s : group.second) {
            cout << "\t\t姓名:" << s.name << ",年龄:" << s.age << ",成绩:" << s.score << endl;
        }
    }

    // 按照年龄分组
    map<int,vector<Student>> by_age = GroupBy(students, [](const Student& s)  {
        return s.age;
    });
    cout << "按照年龄分组:" << endl;
    for (auto& group : by_age) {
        cout << "\t" << "年龄为" << group.first << "岁的学生:" << endl;
        for (auto& s : group.second) {
            cout << "\t\t姓名:" << s.name << ",班级:" << class_map[s.name.substr(0, 1)] << ",成绩:" << s.score << endl;
        }
    }

    return 0;
}

输出结果如下:

按照班级分组:
    一年级一班:
        姓名:张三,年龄:18,成绩:85
    一年级二班:
        姓名:李四,年龄:17,成绩:92
    二年级一班:
        姓名:王五,年龄:18,成绩:73
    二年级二班:
        姓名:赵六,年龄:17,成绩:88
    三年级一班:
按照分数段分组:
    A 分:
        姓名:小明,年龄:18,成绩:95
        姓名:小强,年龄:18,成绩:90
    B 分:
        姓名:张三,年龄:18,成绩:85
        姓名:小红,年龄:17,成绩:90
        姓名:小刚,年龄:18,成绩:80
    C 分:
        姓名:赵六,年龄:17,成绩:88
        姓名:小丽,年龄:17,成绩:87
    D 分:
        姓名:李四,年龄:17,成绩:92
        姓名:王五,年龄:18,成绩:73
        姓名:小芳,年龄:17,成绩:83
按照性别分组:
    男生:
        姓名:张三,年龄:18,成绩:85
        姓名:王五,年龄:18,成绩:73
        姓名:小明,年龄:18,成绩:95
        姓名:小刚,年龄:18,成绩:80
        姓名:小强,年龄:18,成绩:90
    女生:
        姓名:李四,年龄:17,成绩:92
        姓名:赵六,年龄:17,成绩:88
        姓名:小红,年龄:17,成绩:90
        姓名:小丽,年龄:17,成绩:87
        姓名:小芳,年龄:17,成绩:83
按照年龄分组:
    年龄为17岁的学生:
        姓名:李四,班级:一年级二班,成绩:92
        姓名:赵六,班级:二年级二班,成绩:88
        姓名:小红,班级:一年级一班,成绩:90
        姓名:小丽,班级:二年级二班,成绩:87
        姓名:小芳,班级:三年级一班,成绩:83
    年龄为18岁的学生:
        姓名:张三,班级:一年级一班,成绩:85
        姓名:王五,班级:二年级一班,成绩:73
        姓名:小明,班级:一年级一班,成绩:95
        姓名:小刚,班级:二年级一班,成绩:80
        姓名:小强,班级:二年级一班,成绩:90

#include <iostream>

void foo(void (*fn)())
{
    fn();
}
using voidfun = void(*)();
template <typename F>
voidfun lambda2func(F lambda)
{
    static auto lambdabak = lambda;
    return []()
    {
        lambdabak();
    };
}
int main()
{
    int num = 111; // 初始化为“111”
    void (*f)() = lambda2func([&num]()
        { 
        num = 1234; 
        });

    foo(f);
    printf("%d",num); // 成功修改为“1234
    return 0;
}