C++11 核心特性深度解析:可变参数模板、新类功能、lambda 与包装器
/ 名字// 价格// 评价{}int main(){"苹果", 2.1, 5},{"香蕉", 3.0, 4},{"橙子", 2.2, 3},{"菠萝", 1.5, 4}// 按价格升序排序(lambda替代仿函数)});// 按价格降序排序});// 按评价升序排序});return 0;

一、可变参数模板:泛型编程的灵活扩展
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_back、emplace等接口,基于可变参数模板实现,功能兼容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 提供default和delete关键字,让开发者更灵活地控制默认成员函数的生成:
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 的核心优势是 “即用即走”,无需定义单独的函数或仿函数,尤其适合作为算法的回调函数(如sort、find_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;
}
对比传统方式
传统排序需定义多个仿函数(如ComparePriceLess、ComparePriceGreater),而 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::function、std::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包装,进一步统一类型。
更多推荐



所有评论(0)