一、可变参数模板:泛型编程的灵活扩展

C++11 之前的模板仅支持固定数量的参数,限制了泛型编程的灵活性。可变参数模板的出现,允许模板接收零个或多个参数,彻底解放了泛型设计的边界,成为 STL 中 emplace 系列接口等高效组件的实现基础。

1.1 基本语法及原理

可变参数模板的核心是参数包,分为模板参数包(表示零或多个模板参数)和函数参数包(表示零或多个函数参数)。语法上通过...标识参数包,编译器会自动实例化对应类型和个数的函数。

核心语法格式
template <class ...Args> void Func(Args... args) {}  // 模板参数包Args,函数参数包args
template <class ...Args> void Func(Args&... args) {}  // 左值引用参数包
template <class ...Args> void Func(Args&&... args) {} // 右值引用参数包(支持完美转发)
关键运算符:sizeof...

sizeof...(args)用于计算参数包中参数的个数,是参数包操作的基础工具。

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

template <class ...Args>
void Print(Args&&... args)
{
    cout << sizeof...(args) << endl;  // 打印参数包中参数的个数
}

int main()
{
    double x = 2.2;
    Print();  // 包里有0个参数,输出0
    Print(1);  // 包里有1个参数,输出1
    Print(1, string("xxxxx"));  // 包里有2个参数,输出2
    Print(1.1, string("xxxxx"), x);  // 包里有3个参数,输出3

    return 0;
}
编译原理

编译器会根据实参类型和个数,结合引用折叠规则,实例化出对应的函数版本:

void Print();  // 无参版本
void Print(int&& arg1);  // 单个int右值参数版本
void Print(int&& arg1, string&& arg2);  // int右值+string右值参数版本
void Print(double&& arg1, string&& arg2, double& arg3);  // double右值+string右值+double左值引用版本

若无可变参数模板,需手动实现多个重载模板,而可变参数模板通过 “类型泛化 + 数量泛化”,大幅简化了代码编写。

1.2 包扩展:参数包的解析方式

参数包无法直接遍历,必须通过包扩展分解为单个元素。C++11 支持两种核心扩展方式:递归展开和逗号表达式展开,核心是通过...触发扩展操作,对每个元素应用指定模式。

方式 1:递归展开(推荐)

通过递归调用模板函数,每次提取参数包的第一个元素,剩余元素继续递归,直至参数包为空(匹配终止函数)。

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

void ShowList()//自己补充空参函数(否则编译报错),因为void ShowList(T x, Args... args)无法生成此函数
{
    // 递归终止条件:参数包为空时调用
    cout << endl;
}

template <class T, class ...Args>
void ShowList(T x, Args... args)
{
    cout << x << " ";  // 处理当前第一个参数
    ShowList(args...);  // 剩余参数包递归展开
}

template <class ...Args>
void Print(Args... args)
{
    ShowList(args...);  // 触发参数包展开
}

int main()
{
    Print();  // 输出空行
    Print(1);  // 输出"1 " + 换行
    Print(1, string("xxxxx"));  // 输出"1 xxxxx " + 换行
    Print(1, string("xxxxx"), 2.2);  // 输出"1 xxxxx 2.2 " + 换行

    return 0;
}
编译原理

编译器会自动推导生成多个重载函数,以Print(1, string("xxxxx"), 2.2)为例,展开过程如下:

// 第一层:提取1,剩余参数包为(string("xxxxx"), 2.2)
void ShowList(int x, string args1, double args2) { cout << x << " "; ShowList(args1, args2); }
// 第二层:提取string("xxxxx"),剩余参数包为(2.2)
void ShowList(string x, double args1) { cout << x << " "; ShowList(args1); }
// 第三层:提取2.2,剩余参数包为空
void ShowList(double x) { cout << x << " "; ShowList(); }
// 终止层:参数包为空(此函数一定要自己补充)
void ShowList() { cout << endl; }

方式 2:逗号表达式展开

利用逗号表达式的特性,将参数包中每个元素传递给某个函数(需函数返回可忽略的值),通过Arguments(GetArg(args)...)触发扩展。

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

template <class T>
const T& GetArg(const T& x)
{
    cout << x << " ";  // 处理单个参数
    return x;
}

template <class ...Args>
void Arguments(Args... args) {}  // 接收展开后的参数,仅用于触发表达式

template <class ...Args>
void Print(Args... args)
{
    // 包扩展:对每个args元素调用GetArg,结果组成参数包传给Arguments
    Arguments(GetArg(args)...);
    cout << endl;
}

int main()
{
    Print(1, string("xxxxx"), 2.2);  // 输出"1 xxxxx 2.2 " + 换行
    return 0;
}
编译原理

编译器将Arguments(GetArg(args)...)展开为Arguments(GetArg(1), GetArg(string("xxxxx")), GetArg(2.2)),间接实现参数包的遍历处理。

疑问解答:为何一定要有void Arguments(Args... args) {}

核心原因是:参数包的...扩展需要一个 “能容纳多个元素的语法结构” 作为载体

Arguments函数的核心作用是提供一个合法的 “参数包扩展上下文”,

换而言之就是为展开后形成的 “逗号表达式序列”(例如GetArg(1), GetArg(string("xxxxx")), GetArg(2.2);)提供一个承载扩展结果的结构,让编译器能够正确解析GetArg(args)...这个可变参数表达式。

1.3 emplace 系列接口:基于可变参数模板的高效插入

C++11 后 STL 容器新增emplace_backemplace等接口,基于可变参数模板实现,功能兼容push_back/insert,但支持直接传递对象构造参数,在容器空间中直接构造对象,避免临时对象拷贝,效率更高。

核心优势
  • push_back:需先构造对象(或临时对象),再通过拷贝 / 移动构造传入容器;
  • emplace_back:直接将构造参数传入容器,在容器节点中就地构造对象,减少一次拷贝 / 移动开销。
#include<iostream>
#include<list>
#include<string>
using namespace std;

namespace zephyr {
    // 模拟实现string类(简化版,含构造、拷贝构造、移动构造)
    class string {
    public:
        string(const char* str = "") {
            cout << "string(char* str)-构造" << endl;
            _size = strlen(str);
            _capacity = _size;
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }
        string(const string& s) {
            cout << "string(const string& s) -- 拷贝构造" << endl;
            _size = s._size;
            _capacity = s._capacity;
            _str = new char[_capacity + 1];
            strcpy(_str, s._str);
        }
        string(string&& s) noexcept {
            cout << "string(string&& s) -- 移动构造" << endl;
            swap(_str, s._str);
            swap(_size, s._size);
            swap(_capacity, s._capacity);
        }
        ~string() {
            cout << "~string() -- 析构" << endl;
            delete[] _str;
            _str = nullptr;
        }
    private:
        char* _str = nullptr;
        size_t _size = 0;
        size_t _capacity = 0;
    };

    // 模拟实现list容器(含emplace_back接口)
    template<class T>
    struct ListNode {
        ListNode<T>* _next = nullptr;
        ListNode<T>* _prev = nullptr;
        T _data;

        // 可变参数构造函数:直接接收T的构造参数,就地构造_data
        template <class... Args>
        ListNode(Args&&... args) : _data(std::forward<Args>(args)...) {}
    };

    template<class T>
    class list {
    public:
        typedef ListNode<T> Node;
        list() {
            _head = new Node();
            _head->_next = _head;
            _head->_prev = _head;
        }

        // emplace_back:接收T的构造参数包,转发给insert
        template <class... Args>
        void emplace_back(Args&&... args) {
            insert(end(), std::forward<Args>(args)...);
        }

        // insert:基于可变参数模板构造节点,就地构造T对象
        template <class... Args>
        void insert(Node* pos, Args&&... args) {
            Node* prev = pos->_prev;
            Node* newnode = new Node(std::forward<Args>(args)...);  // 就地构造T对象
            prev->_next = newnode;
            newnode->_prev = prev;
            newnode->_next = pos;
            pos->_prev = newnode;
        }

        Node* end() { return _head; }
    private:
        Node* _head;
    };
}

int main()
{
    zephyr::list<zephyr::string> lt;
    zephyr::string s1("111111");

    // 1. 传左值:走拷贝构造(与push_back一致)
    cout << "--- 传左值 ---" << endl;
    lt.emplace_back(s1);
    cout << endl;

    // 2. 传右值:走移动构造(与push_back一致)
    cout << "--- 传右值 ---" << endl;
    lt.emplace_back(move(s1));
    cout << endl;

    // 3. 直接传构造参数:就地构造(push_back无法实现)
    cout << "--- 传构造参数 ---" << endl;
    lt.emplace_back("222222");  // 直接用"222222"构造string,无临时对象
    cout << endl;

    // 4. 构造pair对象:直接传pair的构造参数
    zephyr::list<pair<zephyr::string, int>> lt1;
    cout << "--- 传pair构造参数 ---" << endl;
    lt1.emplace_back("苹果", 1);  // 直接构造pair,无需先创建pair对象
    cout << endl;

    return 0;
}
输出结果分析
--- 传左值 ---
string(const string& s) -- 拷贝构造

--- 传右值 ---
string(string&& s) -- 移动构造

--- 传构造参数 ---
string(char* str)-构造  // 就地构造,无拷贝/移动

--- 传pair构造参数 ---
string(char* str)-构造  // 直接构造pair中的string,无临时对象

可见,emplace_back直接传递构造参数时,无需创建临时对象,效率显著高于push_back,故而推荐优先使用emplace系列替代push/insert系列。

二、新的类功能:C++11 对类设计的增强

C++11 扩展了类的默认成员函数体系,新增了成员变量缺省值、default/delete关键字等特性,让开发者能更精细地控制类的行为,减少冗余代码。

2.1 默认移动构造和移动赋值

C++98 中类的默认成员函数有 6 个(构造、析构、拷贝构造、拷贝赋值、取地址、const 取地址),C++11 新增移动构造函数移动赋值运算符重载,编译器在满足条件时会自动生成。

自动生成条件
  • 未手动实现移动构造 / 移动赋值;
  • 未手动实现析构、拷贝构造、拷贝赋值中的任意一个。
默认行为
  • 内置类型成员:逐字节拷贝(与拷贝构造一致);
  • 自定义类型成员:若该成员实现了移动构造 / 移动赋值,则调用之;否则调用拷贝构造 / 拷贝赋值。
#include<iostream>
#include<string>
using namespace std;

namespace zephyr {
    // 复用前面的string类(含移动构造/移动赋值)
    class string { /* 实现同上 */ };
}

class Person {
public:
    Person(const char* name = "", int age = 0)
        : _name(name)
        , _age(age)
    {}

    // 注释掉所有手动实现的析构、拷贝构造、拷贝赋值
    /*Person(const Person& p)
        : _name(p._name)
        , _age(p._age)
    {}*/

    /*Person& operator=(const Person& p) {
        if (this != &p) {
            _name = p._name;
            _age = p._age;
        }
        return *this;
    }*/

    /*~Person() {}*/

private:
    zephyr::string _name;  // 自定义类型(含移动构造)
    int _age;           // 内置类型
};

int main()
{
    Person s1("张三", 20);
    Person s2 = s1;                // 拷贝构造(编译器未生成默认移动构造,因s1是左值)
    Person s3 = std::move(s1);     // 移动构造(编译器自动生成)
    Person s4;
    s4 = std::move(s2);            // 移动赋值(编译器自动生成)

    return 0;
}
关键说明
  • 若手动实现了拷贝构造,则编译器不会自动生成移动构造,此时std::move(s1)会匹配拷贝构造;
  • 移动构造的核心价值是 “窃取” 右值对象的资源(如 string 的堆内存),避免深拷贝,提升效率。

2.2 成员变量声明时给缺省值

C++11 允许在类中声明成员变量时直接指定缺省值,该缺省值用于初始化列表 —— 若初始化列表未显式初始化该成员,则使用缺省值初始化。

class Person {
public:
    // 初始化列表未显式初始化_name和_age时,使用缺省值
    Person() {}
    Person(const char* name) : _name(name) {}  // _age使用缺省值18

private:
    zephyr::string _name = "未知";  // 缺省值"未知"
    int _age = 18;               // 缺省值18
};

int main()
{
    Person p1;          // _name="未知",_age=18
    Person p2("李四");  // _name="李四",_age=18
    return 0;
}

2.3 default 和 delete 关键字

C++11 提供defaultdelete关键字,让开发者更灵活地控制默认成员函数的生成:

  • default:显式要求编译器生成默认版本的成员函数;
  • delete:显式禁止编译器生成默认版本的成员函数(删除函数)。
class Person {
public:
    Person(const char* name = "", int age = 0)
        : _name(name)
        , _age(age)
    {}

    // 手动实现拷贝构造,编译器不会自动生成移动构造
    Person(const Person& p)
        : _name(p._name)
        , _age(p._age)
    {}

    // 显式要求编译器生成默认移动构造
    Person(Person&& p) = default;

    // 显式禁止拷贝赋值(删除函数)
    Person& operator=(const Person& p) = delete;

private:
    zephyr::string _name;
    int _age;
};

int main()
{
    Person s1("张三", 20);
    Person s2 = s1;                // 允许:调用手动实现的拷贝构造
    Person s3 = std::move(s1);     // 允许:调用default生成的移动构造
    Person s4;
    // s4 = s2;                    // 错误:拷贝赋值被delete禁止
    return 0;
}
与 C++98 的对比

C++98 中禁止默认函数需将其声明为private且不实现,而delete更简洁直观,且支持所有默认成员函数。

2.4 final 与 override

  • final:修饰类时,禁止该类被继承;修饰虚函数时,禁止子类重写该虚函数;
  • override:修饰子类虚函数时,检查是否与父类虚函数原型一致(即检查是否重写父类虚函数),避免重写错误。
// final修饰类:禁止继承
class Base final {
public:
	virtual void func() {}
};

// class Derived : public Base {};  // 错误:Base被final修饰,无法继承

class Base2 {
public:
	// final修饰虚函数:禁止子类重写
	virtual void func() final {}
	//virtual void func(int x) {}
};

class Derived2 : public Base2 { 
public:
	// void func() {}  // 错误:func被final修饰,无法重写
	virtual void func(int x)override {}  // override的核心是 “检查是否重写父类虚函数”,故而重载会报错
};

三、lambda 表达式:简洁的匿名函数对象

lambda 表达式本质是一个匿名函数对象,可定义在函数内部,无需显式声明类或函数指针,就能快速创建可调用对象,大幅简化代码,尤其适合短期使用的简单逻辑(如排序、算法回调)。

3.1 lambda 表达式语法

lambda 的完整语法格式为:

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

各部分说明:

  • [capture-list]:捕捉列表,用于捕捉上下文中的变量供函数体使用,不可省略(空捕捉需写[]);
  • (parameters):参数列表,与普通函数一致,无参数时可省略;
  • -> return type:返回值类型,可省略(编译器自动推导);
  • { function body }:函数体,不可省略,可使用参数和捕捉的变量。
简化语法示例
#include<iostream>
using namespace std;

int main()
{
    // 完整语法:有参数、有返回值
    auto add1 = [](int x, int y)->int { return x + y; };
    cout << add1(1, 2) << endl;  // 输出3

    // 简化语法1:无参数、无返回值(参数列表省略)
    auto func1 = [] { cout << "hello world" << endl; };
    func1();  // 输出"hello world"

    // 简化语法2:有参数、返回值自动推导
    int a = 0, b = 1;
    auto swap1 = [](int& x, int& y) {
        int tmp = x;
        x = y;
        y = tmp;
    };
    swap1(a, b);
    cout << a << ":" << b << endl;  // 输出"1:0"

    return 0;
}

3.2 捕捉列表:lambda 与外部变量的交互

lambda 默认只能使用自身参数和函数体变量,若需使用外部变量,需通过捕捉列表捕捉。捕捉方式分为三类:显式捕捉、隐式捕捉、混合捕捉。

1. 显式捕捉

明确指定捕捉的变量,用逗号分隔,支持值捕捉([x])和引用捕捉([&y])。

int a = 0, b = 1;
// 值捕捉a,引用捕捉b
auto func1 = [a, &b] {
    // a++;  // 错误:值捕捉的变量默认是const,不可修改
    b++;    // 正确:引用捕捉的变量可修改
    return a + b;
};
cout << func1() << endl;  // 输出0+2=2
cout << a << ":" << b << endl;  // 输出"0:2"(a未变,b被修改)
2. 隐式捕捉

无需指定变量名,通过=(隐式值捕捉)或&(隐式引用捕捉),编译器自动捕捉函数体中使用的外部变量。

int a = 0, b = 1, c = 2, d = 3;
// 隐式值捕捉:函数体中使用的a、b、c均为值捕捉
auto func2 = [=] {
    return a + b + c;
};
cout << func2() << endl;  // 输出0+1+2=3

// 隐式引用捕捉:函数体中使用的c、d均为引用捕捉
auto func3 = [&] {
    c++;
    d++;
};
func3();
cout << c << ":" << d << endl;  // 输出"3:4"
3. 混合捕捉

结合隐式捕捉和显式捕捉,规则如下:

  • 第一个元素必须是=&
  • [=, &x]:默认隐式值捕捉,显式引用捕捉 x;
  • [&, x]:默认隐式引用捕捉,显式值捕捉 x。
int a = 0, b = 1, c = 2, d = 3;
// 混合捕捉1:默认值捕捉,显式引用捕捉a、b
auto func5 = [=, &a, &b] {
    a++;
    b++;
    // c++;  // 错误:c是值捕捉,默认const不可修改
    return a + b + c + d;
};
func5();
cout << a << ":" << b << endl;  // 输出"1:2"

// 混合捕捉2:默认引用捕捉,显式值捕捉a、b
auto func4 = [&, a, b] {
    // a++;  // 错误:a是显式值捕捉,不可修改
    c++;
    d++;
    return a + b + c + d;
};
func4();
cout << c << ":" << d << endl;  // 输出"4:5"
4. mutable 关键字

值捕捉的变量默认被const修饰,不可修改。mutable关键字可取消其常量性,允许修改值捕捉的变量(但修改的是拷贝,不影响外部变量)。注意:使用 mutable 后,参数列表不可省略(即使为空)

int a = 0, b = 1;
auto func7 = [=]()mutable {
    a++;
    b++;
    return a + b;
};
cout << func7() << endl;  // 输出1+2=3
cout << a << ":" << b << endl;  // 输出"0:1"(外部变量未变)
5. 捕捉限制
  • 全局变量、静态局部变量无需捕捉,可直接在 lambda 中使用;
  • lambda 定义在全局作用域时,捕捉列表必须为空;
  • 局部域中的 lambda 只能捕捉其定义之前的局部变量。

3.3 lambda 的应用场景

lambda 的核心优势是 “即用即走”,无需定义单独的函数或仿函数,尤其适合作为算法的回调函数(如sortfind_if)。

代码示例:排序自定义类型
#include<iostream>
#include<vector>
#include<algorithm>
#include<string>
using namespace std;

struct Goods {
    string _name;    // 名字
    double _price;   // 价格
    int _evaluate;   // 评价
    Goods(const char* str, double price, int evaluate)
        : _name(str)
        , _price(price)
        , _evaluate(evaluate)
    {}
};

int main()
{
    vector<Goods> v = { 
        {"苹果", 2.1, 5}, 
        {"香蕉", 3.0, 4}, 
        {"橙子", 2.2, 3}, 
        {"菠萝", 1.5, 4} 
    };

    // 按价格升序排序(lambda替代仿函数)
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._price < g2._price;
    });

    // 按价格降序排序
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._price > g2._price;
    });

    // 按评价升序排序
    sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {
        return g1._evaluate < g2._evaluate;
    });

    return 0;
}
对比传统方式

传统排序需定义多个仿函数(如ComparePriceLessComparePriceGreater),而 lambda 可直接在sort参数中定义排序逻辑,代码更简洁、可读性更高。

3.4 lambda 的底层原理

lambda 的底层本质是仿函数(函数对象),编译器会为每个 lambda 表达式生成一个唯一的匿名类:

  • lambda 的捕捉列表对应匿名类的成员变量;
  • lambda 的参数列表、返回值、函数体对应匿名类的operator()方法;
  • 匿名类的类名由编译器自动生成,确保唯一性。
原理验证:lambda 与仿函数的对比
#include<iostream>
using namespace std;

// 仿函数类
class Rate {
public:
    Rate(double rate) : _rate(rate) {}
    double operator()(double money, int year) {
        return money * _rate * year;
    }
private:
    double _rate;
};

int main()
{
    double rate = 0.49;

    // lambda表达式
    auto r2 = [rate](double money, int year) {
        return money * rate * year;
    };

    // 仿函数对象
    Rate r1(rate);

    // 调用方式完全一致
    r1(10000, 2);  // 仿函数调用:operator()
    r2(10000, 2);  // lambda调用:本质是匿名类的operator()

    return 0;
}
汇编层面验证

通过汇编代码可看到,lambda 对象r2的构造过程与仿函数r1一致,均是将捕捉的变量(rate)作为构造函数参数传入,调用时均是调用operator()方法:

// lambda对象构造:传入rate作为参数
00D8295C lea eax,[rate]
00D8295F push eax
00D82960 lea ecx,[r2]
00D82963 call `main'::`2'::<lambda_1>::<lambda_1> (0D81F80h)

// 仿函数对象构造:传入rate作为参数
00D82968 sub esp,8
00D8296B movsd xmm0,mmword ptr [rate]
00D82970 movsd mmword ptr [esp],xmm0
00D82975 lea ecx,[r1]
00D82978 call Rate::Rate (0D81438h)

// lambda调用:调用匿名类的operator()
00D82999 push 2
00D8299B sub esp,8
00D8299E movsd xmm0,mmword ptr [__real@40c3880000000000 (0D89B50h)]
00D829A6 movsd mmword ptr [esp],xmm0
00D829AB lea ecx,[r2]
00D829AE call `main'::`2'::<lambda_1>::operator() (0D824C0h)

四、包装器:统一可调用对象的类型

C++ 中的可调用对象包括函数指针、仿函数、lambda、成员函数等,它们的类型各不相同,导致使用时类型声明复杂。C++11 提供的包装器(std::functionstd::bind)可统一这些对象的类型,简化使用并增强灵活性。

4.1 std::function:可调用对象的统一包装

std::function是一个类模板,定义在<functional>头文件中,用于包装各种可调用对象,实现类型统一。其原型为:

template <class Ret, class... Args>
class function<Ret(Args...)>;
  • Ret:可调用对象的返回值类型;
  • Args...:可调用对象的参数类型列表。
核心功能
  • 包装函数指针、仿函数、lambda、bind 表达式等;
  • function调用会抛出std::bad_function_call异常;
  • 统一可调用对象类型,方便作为参数、返回值或容器元素。
#include<iostream>
#include<functional>
#include<string>
using namespace std;

// 1. 普通函数
int f(int a, int b) {
    return a + b;
}

// 2. 仿函数
struct Functor {
    int operator()(int a, int b) {
        return a + b;
    }
};

// 3. 类成员函数
class Plus {
public:
    Plus(int n = 10) : _n(n) {}
    static int plusi(int a, int b) {  // 静态成员函数(无this指针)
        return a + b;
    }
    double plusd(double a, double b) {  // 普通成员函数(隐含this指针)
        return (a + b) * _n;
    }
private:
    int _n;
};

int main()
{
    // 包装普通函数
    function<int(int, int)> f1 = f;
    cout << f1(1, 1) << endl;  // 输出2

    // 包装仿函数
    function<int(int, int)> f2 = Functor();
    cout << f2(1, 1) << endl;  // 输出2

    // 包装lambda表达式
    function<int(int, int)> f3 = [](int a, int b) { return a + b; };
    cout << f3(1, 1) << endl;  // 输出2

    // 包装静态成员函数(需指定类域和&)
    function<int(int, int)> f4 = &Plus::plusi;
    cout << f4(1, 1) << endl;  // 输出2

    // 包装普通成员函数(需包含this指针参数)
    function<double(Plus*, double, double)> f5 = &Plus::plusd;
    Plus pd(2);
    cout << f5(&pd, 1.1, 1.1) << endl;  // 输出(1.1+1.1)*2=4.4

    // 包装普通成员函数(传值方式传递对象)
    function<double(Plus, double, double)> f6 = &Plus::plusd;
    cout << f6(pd, 1.1, 1.1) << endl;  // 输出4.4

    return 0;
}
应用场景:逆波兰表达式求值

利用function统一运算符对应的函数类型,结合map实现字符串到运算符函数的映射,代码扩展更方便。

#include<iostream>
#include<vector>
#include<stack>
#include<map>
#include<functional>
using namespace std;

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        // map映射:string -> 函数(int(int, int))
        map<string, function<int(int, int)>> opFuncMap = {
            {"+", [](int x, int y) { return x + y; }},
            {"-", [](int x, int y) { return x - y; }},
            {"*", [](int x, int y) { return x * y; }},
            {"/", [](int x, int y) { return x / y; }}
        };

        for (auto& str : tokens) {
            if (opFuncMap.count(str)) {  // 若为运算符
                int right = st.top(); st.pop();
                int left = st.top(); st.pop();
                // 调用对应的函数计算
                st.push(opFuncMap[str](left, right));
            } else {  // 若为数字
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

int main()
{
    vector<string> tokens = {"2", "1", "+", "3", "*"};
    Solution s;
    cout << s.evalRPN(tokens) << endl;  // 输出(2+1)*3=9
    return 0;
}

4.2 std::bind:可调用对象的参数适配器

std::bind是一个函数模板,同样定义在<functional>头文件中,用于调整可调用对象的参数个数和顺序,返回一个新的可调用对象。

核心功能
  • 绑定固定参数:将可调用对象的部分参数绑定为固定值,减少参数个数;
  • 调整参数顺序:通过占位符(_1_2等)调整参数传递顺序;
  • 适配成员函数:绑定成员函数的this指针参数。
语法格式
auto newCallable = bind(callable, arg_list);
  • callable:待包装的可调用对象;
  • arg_list:参数列表,可包含固定值或占位符(_n表示新对象的第 n 个参数);
  • newCallable:新的可调用对象,调用时会将参数传递给callable
#include<iostream>
#include<functional>
using namespace std;
using placeholders::_1;  // 占位符:新对象的第1个参数
using placeholders::_2;  // 占位符:新对象的第2个参数

// 普通函数:2个参数
int Sub(int a, int b) {
    return (a - b) * 10;
}

// 普通函数:3个参数
int SubX(int a, int b, int c) {
    return (a - b - c) * 10;
}

// 类成员函数
class Plus {
public:
    double plusd(double a, double b) {
        return a + b;
    }
};

int main()
{
    // 1. 保持参数个数和顺序(无意义,仅演示)
    auto sub1 = bind(Sub, _1, _2);
    cout << sub1(10, 5) << endl;  // 输出(10-5)*10=50

    // 2. 调整参数顺序
    auto sub2 = bind(Sub, _2, _1);  // _2对应新对象第2个参数,_1对应第1个
    cout << sub2(10, 5) << endl;  // 输出(5-10)*10=-50

    // 3. 绑定固定参数(减少参数个数)
    auto sub3 = bind(Sub, 100, _1);  // 第1个参数固定为100
    cout << sub3(5) << endl;  // 输出(100-5)*10=950

    auto sub4 = bind(Sub, _1, 100);  // 第2个参数固定为100
    cout << sub4(5) << endl;  // 输出(5-100)*10=-950

    // 4. 绑定多参数函数的部分参数
    auto sub5 = bind(SubX, 100, _1, _2);  // 第1个参数固定为100
    cout << sub5(5, 1) << endl;  // 输出(100-5-1)*10=940

    // 5. 绑定类成员函数(固定this指针)
    Plus pd;
    auto f7 = bind(&Plus::plusd, pd, _1, _2);  // this指针固定为pd
    cout << f7(1.1, 1.1) << endl;  // 输出2.2

    // 6. 结合lambda绑定固定参数
    auto func1 = [](double rate, double money, int year)->double {
        double ret = money;
        for (int i = 0; i < year; i++) {
            ret += ret * rate;
        }
        return ret - money;  // 计算利息
    };

    // 绑定利率和年限,生成不同的利息计算函数
    function<double(double)> func3_1_5 = bind(func1, 0.015, _1, 3);  // 3年,利率1.5%
    function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);  // 5年,利率1.5%
    cout << func3_1_5(1000000) << endl;  // 输出100万3年的利息
    cout << func5_1_5(1000000) << endl;  // 输出100万5年的利息

    return 0;
}
int main()
{

	function<int(int, int)>f1 = [](int a, int b) {return a + b*10; };
	vector<function<int(int, int)>>fv;//直接可以存储同返回类型,参数列表的:函数,仿函数对象,lambda表达式,以及类中的静态成员函数(特殊,因为没有this指针)
	fv.push_back(f1);
	fv.push_back(&test::Add);

	//有this指针的类非静态成员函数难道不能封装后与上面的存到一起了吗?
	//可以通过bind绑定类非静态成员函数的第一个参数达到目的

	function<int(test,int, int)>f4 = &test::Sub; 
	test ts;
	function<int(int, int)>f5 = bind(f4, ts, placeholders::_1, placeholders::_2);  
	fv.push_back(f5);

}
关键说明
  • 占位符_1_2等定义在std::placeholders命名空间中,需显式引入;
  • 绑定普通成员函数时,需传入对象(或指针)作为this指针参数;
  • bind返回的对象可被function包装,进一步统一类型。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐