目录
[1.1 C++98传统的初始化方式](#1.1 C++98传统的初始化方式)
[1.2 C++11的列表初始化](#1.2 C++11的列表初始化)
[1.3 std::initializer_list](#1.3 std::initializer_list)
二、可变参数模板
[2.1 基本语法及原理](#2.1 基本语法及原理)
[2.2 包扩展](#2.2 包扩展)
[2.3 emplace系列接口](#2.3 emplace系列接口)
三、新的类功能
[3.1 默认的移动构造和移动赋值](#3.1 默认的移动构造和移动赋值)
[3.2 成员变量声明时给缺省值](#3.2 成员变量声明时给缺省值)
[3.3 default和delete](#3.3 default和delete)
[3.4 final与override](#3.4 final与override)
四、STL中一些变化
五、lambda表达式
[5.1 lambda表达式语法](#5.1 lambda表达式语法)
[5.2 捕捉列表](#5.2 捕捉列表)
[5.3 lambda的应用](#5.3 lambda的应用)
[5.4 lambda的原理](#5.4 lambda的原理)
六、包装器
[6.1 function](#6.1 function)
[6.2 bind](#6.2 bind)
七、总结
一、列表初始化
1.1 C++98传统的初始化方式
在C++98中,主要支持以下几种初始化方式:
cpp
struct Point
{
int _x;
int _y;
};
int main()
{
// 数组初始化
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
// 结构体初始化
Point p = { 1, 2 };
return 0;
}
C++98的初始化方式存在局限性:
- 只能用于数组和简单的结构体
- 对于类对象的初始化不够灵活
- 初始化方式不统一
1.2 C++11的列表初始化
C++11引入了列表初始化 (也称为统一初始化),试图实现一切对象皆可用{}初始化。
cpp
#include<iostream>
#include<vector>
using namespace std;
struct Point
{
int _x;
int _y;
};
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
Date(const Date& d)
:_year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date(const Date& d)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
// C++11支持的列表初始化
// 内置类型支持
int x1 = { 2 };
// 自定义类型支持
// 本质是用{2025, 1, 1}构造临时对象,编译器优化后直接构造d1
Date d1 = { 2025, 1, 1 };
// 引用临时对象
const Date& d2 = { 2024, 7, 25 };
// 单参数时的类型转换
Date d3 = { 2025 };
Date d4 = 2025; // C++98也支持
// 省略=的列表初始化
Point p1 { 1, 2 };
int x2 { 2 };
Date d6 { 2024, 7, 25 };
const Date& d7 { 2024, 7, 25 };
// 不支持省略=的传统初始化
// Date d8 2025; // 错误!
// 在容器中使用列表初始化的便利性
vector<Date> v;
v.push_back(d1);
v.push_back(Date(2025, 1, 1));
// 使用列表初始化更简洁
v.push_back({ 2025, 1, 1 });
return 0;
}
列表初始化的优势:
- 统一的初始化语法
- 支持省略
= - 在容器操作中更加方便
- 防止窄化转换(如
int x{3.14};会编译错误)
1.3 std::initializer_list
C++11库中提出了一个 std::initializer_list 的类,用于支持任意数量参数的初始化。这个类的本质是底层开一个数组,将数据拷贝过来,内部有两个指针分别指向数组的开始和结束。
cpp
#include<vector>
#include<string>
#include<map>
#include<iostream>
using namespace std;
int main()
{
// initializer_list的基本使用
std::initializer_list<int> myList;
myList = { 10, 20, 30 };
cout << sizeof(myList) << endl; // 通常为2个指针的大小
// 验证initializer_list的存储位置
int i = 0;
cout << myList.begin() << endl;
cout << myList.end() << endl;
cout << &i << endl;
// 容器使用initializer_list初始化
vector<int> v1({ 1, 2, 3, 4, 5 }); // 直接构造
vector<int> v2 = { 1, 2, 3, 4, 5 }; // 构造+优化
const vector<int>& v3 = { 1, 2, 3, 4, 5 };
// map使用initializer_list初始化
map<string, string> dict = { {"sort", "排序"}, {"string", "字符串"} };
// initializer_list版本的赋值
v1 = { 10, 20, 30, 40, 50 };
return 0;
}
若容器支持一个 std::initializer_list 的构造函数,也就支持任意多个值构成的 {x1, x2, x3, ...}进行初始化。STL 中的容器支持任意多个值构成的 {x1, x2, x3, ...}进行初始化,就是通过 std::initializer_list 的构造函数支持的。
cpp
// STL容器都增加了initializer_list构造函数
vector(initializer_list<value_type> il, const allocator_type& alloc = allocator_type());
list(initializer_list<value_type> il, const allocator_type& alloc = allocator_type());
map(initializer_list<value_type> il, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type());
// 以及对应的赋值运算符重载
vector& operator=(initializer_list<value_type> il);
map& operator=(initializer_list<value_type> il);
实现原理:
cpp
template<class T>
class vector {
public:
typedef T* iterator;
vector(initializer_list<T> L)
{
for (auto e : L)
push_back(e);
}
private:
iterator _start = nullptr;
iterator _finish = nullptr;
iterator _endofstorage = nullptr;
};
二、可变参数模板
2.1 基本语法及原理
可变参数模板支持任意数量和类型的参数。
cpp
#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个参数
Print(1); // 1个参数
Print(1, string("xxxxx")); // 2个参数
Print(1.1, string("xxxxx"), x); // 3个参数
return 0;
}
2.2 包扩展
- 对于一个参数包,我们除了能计算他的参数个数,我们能做的唯一的事情就是扩展它,当扩展一个包时,我们还要提供用于每个扩展元素的模式,扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。我们通过在模式的右边放一个省略号(...)来触发扩展操作。底层的实现细节如下图所示。
- C++11支持直接将参数包依次展开依次作为实参给一个函数去处理。

cpp
#include <iostream>
#include <string>
using namespace std;
// 递归终止函数
void ShowList() {
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);
Print(1, string("xxxxx"));
Print(1, string("xxxxx"), 2.2);
return 0;
}
2.3 emplace系列接口
emplace系列接口支持直接在容器中构造对象,避免不必要的拷贝。
cpp
#include <list>
#include <string>
#include <utility>
using namespace std;
int main()
{
list<pair<string, int>> lt;
// 传统方式 - 构造pair + 拷贝/移动构造
pair<string, int> kv("苹果", 1);
lt.push_back(kv); // 拷贝构造
lt.push_back(move(kv)); // 移动构造
lt.push_back({"香蕉", 2}); // 移动构造
// emplace方式 - 直接构造
lt.emplace_back("橙子", 3); // 直接构造,最高效
lt.emplace_back(string("菠萝"), 4); // 移动构造
return 0;
}
三、新的类功能
3.1 默认的移动构造和移动赋值
C++11新增了两个默认成员函数:移动构造函数和移动赋值运算符。
cpp
#include<iostream>
#include<string>
using namespace std;
namespace codebyv {
// 简化的string实现
class string {
public:
string(const char* str = "") {}
string(const string& s) { cout << "拷贝构造" << endl; }
string(string&& s) { cout << "移动构造" << endl; }
string& operator=(const string& s) { cout << "拷贝赋值" << endl; return *this; }
string& operator=(string&& s) { cout << "移动赋值" << endl; return *this; }
};
}
class Person {
public:
Person(const char* name = "", int age = 0)
: _name(name)
, _age(age)
{}
// 如果注释掉拷贝构造/拷贝赋值/析构函数,编译器会自动生成移动构造和移动赋值
private:
codebyv::string _name;
int _age;
};
int main()
{
Person s1;
Person s2 = s1; // 拷贝构造
Person s3 = std::move(s1); // 移动构造(如果生成)
Person s4;
s4 = std::move(s2); // 移动赋值(如果生成)
return 0;
}
默认移动操作的生成规则:
- 如果没有用户声明的拷贝构造、拷贝赋值、析构函数,编译器会自动生成移动构造和移动赋值
- 对于内置类型成员:按字节拷贝
- 对于自定义类型成员:如果该类型有移动构造/移动赋值,则调用移动版本;否则调用拷贝版本
3.2 成员变量声明时给缺省值
C++11支持在类定义中直接给成员变量缺省值。
cpp
class Student {
private:
string _name = "Unknown"; // 声明时给缺省值
int _age = 0; // 声明时给缺省值
vector<int> _scores{90, 85, 95}; // 列表初始化
public:
Student() = default; // 使用默认构造函数
Student(const string& name, int age)
: _name(name) // 初始化列表会覆盖缺省值
, _age(age)
{}
};
- 如果没有在初始化列表中显式初始化,就会使用缺省值
- 初始化列表的初始化会覆盖缺省值
3.3 default和delete
C++11提供了更好的控制默认函数生成的机制。
cpp
class Person {
public:
Person(const char* name = "", int age = 0)
:_name(name)
, _age(age)
{}
// 用户定义了拷贝构造,默认不会生成移动构造
Person(const Person& p)
:_name(p._name)
,_age(p._age)
{}
// 使用default强制生成移动构造
Person(Person&& p) = default;
// 使用delete禁止拷贝构造
Person(const Person& p) = delete;
private:
string _name;
int _age;
};
应用场景:
= default:显式要求编译器生成默认版本= delete:禁止编译器生成特定函数,用于实现不可拷贝、不可移动等语义
3.4 final与override
cpp
class Base {
public:
virtual void func() final {} // final禁止派生类重写
};
class Derived : public Base {
public:
virtual void func() override {} // override显式指明重写,编译检查
};
四、STL中一些变化
C++11为STL带来了许多新容器和接口改进:
新增容器:
std::array:固定大小数组std::forward_list:单向链表std::unordered_map/set:哈希表实现的map和set
重要接口改进:
- 支持
initializer_list的构造函数和赋值运算符 - 增加了移动语义相关的
push/insert/emplace系列接口 - 增加了
cbegin/cend等const迭代器
cpp
#include<vector>
#include<array>
#include<unordered_map>
using namespace std;
int main()
{
// 使用initializer_list初始化
vector<int> v = {1, 2, 3, 4, 5};
array<int, 5> arr = {1, 2, 3, 4, 5};
unordered_map<string, int> dict = {{"apple", 1}, {"banana", 2}};
// 移动语义接口
vector<string> vec;
string s = "hello";
vec.push_back(move(s)); // 移动而非拷贝
// emplace系列接口
vec.emplace_back("world"); // 直接构造
return 0;
}
五、lambda表达式
5.1 lambda表达式语法
- lambda 表达式本质是一个匿名函数对象,跟普通函数不同的是他可以定义在函数内部。
- lambda 表达式语法使用层而言没有类型,所以我们一般是用auto或者模板参数定义的对象去接收 lambda 对象。
lambda表达式的格式:
cpp
[capture-list] (parameters) -> return-type { function-body }
[capture-list]:捕捉列表,该列表总是出现在 lambda 函数的开始位置,编译器根据[]来判断接下来的代码是否为 lambda 函数,捕捉列表能够捕捉上下文中的变量供 lambda 函数使用,捕捉列表可以传值和传引用捕捉。捕捉列表为空也不能省略。(parameters):参数列表,与普通函数的参数列表功能类似,如果不需要参数传递,则可以连同()一起省略->return-type:返回值类型,用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。一般返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。{function-body}:函数体,函数体内的实现跟普通函数完全类似,在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量,函数体为空也不能省略。
cpp
int main()
{
// 完整的lambda表达式
auto add1 = [](int x, int y) -> int { return x + y; };
cout << add1(1, 2) << endl;
// 省略参数列表
auto func1 = [] { cout << "hello bit" << endl; };
func1();
// 省略返回类型(编译器推导)
auto add2 = [](int x, int y) { return x + y; };
// 带引用参数的lambda
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;
return 0;
}
5.2 捕捉列表
lambda 表达式中默认只能用 lambda 函数体和参数中的变量,如果想用外层作用域中的变量就需要进行捕捉。
三种捕捉方式:
- 第一种捕捉方式是在捕捉列表中显示的传值捕捉和传引用捕捉 ,捕捉的多个变量用逗号分割。
[x, y, &z]表示x和y值捕捉,z引用捕捉。 - 第二种捕捉方式是在捕捉列表中隐式捕捉 ,我们在捕捉列表写一个
=表示隐式值捕捉,在捕捉列表写一个&表示隐式引用捕捉,这样我们 lambda 表达式中用了那些变量,编译器就会自动捕捉那些变量。 - 第三种捕捉方式是在捕捉列表中混合使用隐式捕捉和显示捕捉 。
[=, &x]表示其他变量隐式值捕捉,x引用捕捉;[&, x, y]表示其他变量引用捕捉,x和y值捕捉。
捕捉规则:
- 全局变量和静态局部变量不需要捕捉
- 值捕捉的变量默认有const属性,使用mutable可以取消
- 混合捕捉时,第一个元素必须是
&或=,后续显示捕捉方式必须相反
cpp
int main()
{
int a = 0, b = 1, c = 2, d = 3;
// 1. 显式值捕捉
auto func1 = [a, &b] { // a值捕捉,b引用捕捉
// a++; // 错误:值捕捉的变量默认const
b++; // 正确:引用捕捉可以修改
return a + b;
};
// 2. 隐式值捕捉
auto func2 = [=] { // 隐式值捕捉所有使用的变量
return a + b + c;
};
// 3. 隐式引用捕捉
auto func3 = [&] { // 隐式引用捕捉所有使用的变量
a++; b++; c++; d++;
};
// 4. 混合捕捉
auto func4 = [&, a, b] { // 其他引用捕捉,a,b值捕捉
c++; d++;
return a + b + c + d;
};
auto func5 = [=, &a, &b] { // 其他值捕捉,a,b引用捕捉
a++; b++;
return a + b + c + d;
};
// 5. mutable取消常量性
auto func6 = [=]() mutable { // 去掉const,可以修改值捕捉的变量
a++; b++; // 修改的是副本,不影响外部变量
return a + b;
};
return 0;
}
5.3 lambda的应用
lambda表达式大大简化了可调用对象的定义。
cpp
#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, 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;
}
其他应用场景:
- 回调函数
- 智能指针的删除器
- STL算法中的比较参数
5.4 lambda的原理
lambda表达式的底层实现是仿函数,在编译时会被转换为一个匿名类,这个类重载了operator()。
cpp
// 原始的lambda表达式
auto lambda = [](int x, int y) { return x + y; };
// 编译器生成的等价代码
class __anonymous_lambda_class {
public:
auto operator()(int x, int y) const -> int {
return x + y;
}
};
__anonymous_lambda_class lambda;
六、包装器
6.1 function
cpp
template <class T>
class function; // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
std::function是一个类模板,也是一个包装器。std::function的实例对象可以包装存储其他的可以调用对象,包括函数指针、仿函数、lambda、bind 表达式等,存储的可调用对象被称为std::function的目标。若std::function不含目标,则称它为空。调用空std::function的目标导致抛出std::bad_function_call异常。- 函数指针、仿函数、lambda 等可调用对象的类型各不相同,
std::function的优势就是统一类型,对他们都可以进行包装,这样在很多地方就方便声明可调用对象的类型。
cpp
#include<functional>
#include<iostream>
using namespace std;
int f(int a, int b) { return a + b; }
struct Functor {
int operator()(int a, int b) { return a + b; }
};
class Plus {
public:
Plus(int n = 10) : _n(n) {}
static int plusi(int a, int b) { return a + b; }
double plusd(double a, double b) { return (a + b) * _n; }
private:
int _n;
};
int main()
{
// 包装各种可调用对象
function<int(int, int)> f1 = f; // 函数指针
function<int(int, int)> f2 = Functor(); // 函数对象
function<int(int, int)> f3 = [](int a, int b) { return a + b; }; // lambda
cout << f1(1, 1) << endl;
cout << f2(1, 1) << endl;
cout << f3(1, 1) << endl;
// 包装静态成员函数
function<int(int, int)> f4 = &Plus::plusi;
cout << f4(1, 1) << endl;
// 包装普通成员函数
function<double(Plus*, double, double)> f5 = &Plus::plusd;
Plus pd;
cout << f5(&pd, 1.1, 1.1) << endl;
// 使用bind简化成员函数调用
function<double(double, double)> f6 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);
cout << f6(1.1, 1.1) << endl;
return 0;
}
逆波兰表达式求值
cpp
#include<stack>
#include<map>
#include<functional>
#include<vector>
#include<string>
using namespace std;
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
// 使用map映射操作符和对应的可调用对象
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();
int ret = opFuncMap[str](left, right);
st.push(ret);
} else { // 操作数
st.push(stoi(str));
}
}
return st.top();
}
};
6.2 bind
std::bind用于创建新的可调用对象,可以调整参数顺序和个数。
cpp
#include<functional>
using namespace std::placeholders; // 用于_1, _2, _3...
int Sub(int a, int b) { return (a - b) * 10; }
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()
{
// 基本绑定
auto sub1 = bind(Sub, _1, _2);
cout << sub1(10, 5) << endl; // 输出:50
// 调整参数顺序
auto sub2 = bind(Sub, _2, _1);
cout << sub2(10, 5) << endl; // 输出:-50
// 绑死部分参数
auto sub3 = bind(Sub, 100, _1);
cout << sub3(5) << endl; // 输出:950
// 多参数函数的绑定
auto sub5 = bind(SubX, 100, _1, _2);
cout << sub5(5, 1) << endl; // 输出:940
// 绑定成员函数
function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);
cout << f7(1.1, 1.1) << endl;
// 实际应用:计算复利
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);
function<double(double)> func5_1_5 = bind(func1, 0.015, _1, 5);
cout << func3_1_5(1000000) << endl; // 3年期1.5%利率的利息
cout << func5_1_5(1000000) << endl; // 5年期1.5%利率的利息
return 0;
}
七、总结
主要特性回顾
- 列表初始化 :统一的初始化语法,支持一切对象使用
{}初始化 - 可变参数模板:支持类型和数量可变的模板参数,为泛型编程带来极大灵活性
- 新的类功能 :
- 默认移动操作
- 成员变量缺省值
- default/delete控制函数生成
- lambda表达式:简洁的匿名函数,替代复杂的函数对象
- 包装器 :
function:统一可调用对象类型bind:参数绑定和适配
实际开发建议
-
优先使用现代C++特性:
cpp// 推荐 vector<int> v{1, 2, 3, 4, 5}; auto lambda = [](auto x) { return x * 2; }; function<int(int)> f = lambda; // 而不是传统方式 vector<int> v; v.push_back(1); v.push_back(2); // ... -
利用emplace系列接口:
cppvector<complex_type> vec; vec.emplace_back(arg1, arg2); // 直接构造,避免拷贝 -
善用lambda简化代码:
cpp// 替代复杂的仿函数 sort(v.begin(), v.end(), [](const auto& a, const auto& b) { return a.value < b.value; }); -
使用function统一回调接口:
cpp// 定义统一的回调接口 class EventHandler { private: function<void(int, const string&)> callback_; public: void setCallback(function<void(int, const string&)> cb) { callback_ = move(cb); } void triggerEvent(int id, const string& msg) { if (callback_) { callback_(id, msg); } } }; // 使用lambda作为回调 EventHandler handler; handler.setCallback([](int id, const string& msg) { cout << "Event " << id << ": " << msg << endl; }); // 也可以使用普通函数 void logEvent(int id, const string& msg) { cout << "Log: Event " << id << " - " << msg << endl; } handler.setCallback(logEvent); // 或者使用成员函数 class Logger { public: void memberCallback(int id, const string& msg) { cout << "Member: Event " << id << " - " << msg << endl; } }; Logger logger; handler.setCallback(bind(&Logger::memberCallback, &logger, placeholders::_1, placeholders::_2));