1. 初始化
1.1 花括号 {} 列表初始化
struct Point
{
int _x;
int _y;
};
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
private:
int _year;
int _month;
int _day;
};
void test_1()
{
// C++98支持花括号{ }对数组和结构体元素进行列表初始化
int array1[] = { 1, 2, 3, 4, 5 };
int array2[5] = { 0 };
// Point p = { 1, 2 };
// C++11扩大适用范围,支持内置类型和自定义类型,使用初始化列表时,‘=’ 可加可不加
int x1 = 1;
int x2{ 2 };
int array3[]{ 1, 2, 3, 4, 5 };
int array4[5]{ 0 };
Point p{ 1, 2 };
int* pa = new int[4] {0}; // 同样可以用于new表达式
// C++11支持的列表初始化,这里会调用构造函数初始化
Date d1{ 2022, 1, 2 };
Date d2 = { 2022, 1, 3 };
}
-
C++98 中,花括号只能用于数组和 POD 结构体的聚合初始化。
-
C++11 将其扩展为通用列表初始化 ,内置类型、自定义类型均可使用,
=可省略。 -
对于类类型,
{...}会调用对应的构造函数进行初始化。 -
new int[4]{0}等动态分配也支持该语法,能有效防止未初始化值。
1.2 std::initializer_list
void test_2()
{
auto il = { 10, 20, 30 };
// 当使用赋值形式的列表初始化 auto var = { ... } 且花括号内元素类型一致时,
// auto 会推导为 std::initializer_list<T>
cout << typeid(il).name() << endl; // class std::initializer_list<int>
// 标准容器都有接受 initializer_list 的构造函数
vector<int> v = { 1, 2, 3 };
map<std::string, int> m = { {"a", 1}, {"b", 2} };
}
-
std::initializer_list<T>是 C++11 引入的轻量级容器,专门用于接收花括号列表。 -
auto对{...}的推导规则:若所有元素类型相同,推导为initializer_list<T>;否则编译错误(C++17 略有变化)。 -
标准库容器均提供接受
initializer_list的构造函数,这使得可以直接用{1,2,3}或{"a",1}, {"b",2}初始化容器。
2. 声明与类型推导:decltype
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
//decltype推导出对象的类型,可用于再定义或模板实参
decltype(t1 * t2) ret; // 推导表达式结果的类型
cout << typeid(ret).name() << endl;
}
void test_3()
{
int i = 10;
auto p = &i; // auto 推导为 int*
auto p1 = p;
decltype(p) p2; // decltype 推导 p 的类型为 int*
cout << typeid(p).name() << " " << typeid(p1).name() << " " << typeid(p2).name() << endl;
F(1, 'a'); // 输出 int
}
-
auto根据初始化表达式推导变量类型 ;decltype(exp)则仅推导表达式本身的类型而不求值。 -
decltype常用于模板编程中获取未知表达式的返回类型。 -
本例中
F(1, 'a')内部t1 * t2的结果类型为int(char 被提升为 int),所以ret为int。
3. 新 STL 容器
3.1 std::array 和 std::forward_list
void test_4()
{
// array 固定大小数组
array<int, 4> arr{ 1, 2, 3, 4 };
arr[0] = 10; // 不检查边界
arr.at(1) = 20; // 检查边界,越界抛 std::out_of_range
// forward_list 轻量化单向链表
forward_list<int> fl{ 1, 2, 3 };
fl.push_front(0); // 头部插入
auto it = fl.begin();
fl.insert_after(it, 42); // 在 it 之后插入
for (int x : fl) std::cout << x << ' '; // 0 42 1 2 3
fl.erase_after(fl.begin()); // 删除 0 后面的元素,变为 0 1 2 3
}
讲解
-
std::array是一个封装了固定大小数组的容器,支持迭代器和at()安全访问,零开销替代 C 风格数组。 -
std::forward_list是单向链表,只支持从头或已知位置之后 插入/删除,没有size()方法,内存开销极小。所有操作需要前驱迭代器。
3.2 unordered_set 和 unordered_map
void test_5()
{
//unordered_set 无序集合
//去重、快速判断元素是否存在
unordered_set<int> set{ 1, 2, 3, 4, 5 };
set.insert(6);
set.erase(2);
if (set.find(3) != set.end())
cout << "found 3\n";
for (int x : set)
cout << x << ' '; // 无序输出
cout << set.size() << endl;
//unordered_map 无序映射
unordered_map<std::string, int> map{ {"apple", 5}, {"banana", 3} };
map["orange"] = 7; // 插入或修改
map["banana"] = 4;
map.at("apple") = 10; // 修改,不存在则抛异常
auto it = map.find("banana");
if (it != map.end())
cout << it->first << " -> " << it->second << endl;
map.erase("apple");
for (const auto& [key, value] : map) // 结构化绑定遍历(C++17)
cout << key << ": " << value << endl;
}
-
unordered_set/map基于哈希表,查找、插入、删除平均 O(1),元素无序。 -
支持
initializer_list初始化,支持[]、at()、find、erase等操作。 -
使用
find比[]更安全(不会意外插入不存在的键)。 -
C++17 结构化绑定
[key, value]可优雅地遍历键值对。
3.3 新接口 emplace 系列
//emplace_back 利用 可变参数模板 + 完美转发,全过程只有一次构造,没有临时对象,也不要求对象可拷贝或可移动。
template <typename... Args>
void emplace_back(Args&&... args)
{
// 在容器预留的内存上直接调用 T 的构造函数
// new (_mem) T(std::forward<Args>(args)...);
}
讲解
-
emplace_back/emplace利用可变参数模板 + 完美转发,在容器内存地址上直接构造对象,避免临时对象创建和拷贝/移动。 -
例如
vector<MyString> v; v.emplace_back("hello")只调用一次MyString(const char*)构造,效率高于push_back。
4. 右值引用与移动语义(重点)
4.1 左值与左值引用
void test_6()
{
int* p = new int(0);
int b = 1;
const int c = 2;
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
}
-
左值 :可以取地址、有名字的表达式(如变量
b、解引用*p)。const 左值虽然不能赋值,但仍可取其地址,属于左值。 -
左值引用 (
T&):绑定到左值的别名,可修改原对象。 -
const 左值引用 (
const T&) :既可以绑定左值,也可以绑定右值,是"万能引用"(注意与万能转发引用T&&区分)。
4.2 右值与右值引用
int fmin(int a, int b) { return a < b ? a : b; }
void test_7()
{
double x = 1.1, y = 2.2;
10; // 字面常量,右值
// x + y; // 表达式返回值,右值
fmin(x, y); // 函数返回值(非引用返回),右值
// 右值不能作为左操作数
// 10 = 1; // 错误
// x + y = 1; // 错误
"hello world";//常量字符串本身是右值,但是"xxx"表示字符串首地址
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
const int& lv = 10; // const 左值引用可绑定右值
const double& lv2 = x + y;
int a = 10;
int&& ax = move(a); //右值引用左值需要move 将左值强行转为右值
}
讲解
-
右值:不能取地址的临时对象,如字面量、表达式结果、非引用返回的函数结果。
-
右值引用 (
T&&):专门绑定右值的引用,延长临时对象生命周期,并可"窃取"其资源实现移动语义。 -
std::move():本身不做任何移动,只是将左值无条件转换为右值引用,使其能被移动构造函数或移动赋值函数匹配。
4.3 移动构造与移动赋值
class MyString
{
char* _data;
public:
MyString(const char* s = "")
{
_data = new char[strlen(s) + 1];
strcpy(_data, s);
cout << "[构造] " << _data << endl;
}
// 拷贝构造(深拷贝)
MyString(const MyString& other)
{
_data = new char[strlen(other._data) + 1];
strcpy(_data, other._data);
cout << "[拷贝构造] " << _data << endl;
}
// 移动构造(窃取资源)
MyString(MyString&& other) noexcept : _data(other._data)
{
other._data = nullptr;
cout << "[移动构造] " << (_data ? _data : "null") << endl;
}
// 拷贝赋值
MyString& operator=(const MyString& other)
{
if (this != &other) {
delete[] _data;
_data = new char[strlen(other._data) + 1];
strcpy(_data, other._data);
cout << "[拷贝赋值] " << _data << endl;
}
return *this;
}
// 移动赋值
MyString& operator=(MyString&& other) noexcept
{
if (this != &other) {
delete[] _data;
_data = other._data;
other._data = nullptr;
cout << "[移动赋值] " << (_data ? _data : "null") << endl;
}
return *this;
}
~MyString() { delete[] _data; }
};
MyString getString()
{
MyString str("hello world");
return str; // 编译器将局部对象str视为右值,触发移动
}
void test_8()
{
cout << "--- test_8 start ---" << endl;
MyString s = getString(); // ① 移动构造
cout << "next" << endl;
MyString s2;
s2 = getString(); // ② 移动赋值
cout << "s = " << s << ", s2 = " << s2 << endl;
cout << "--- test_8 end ---" << endl;
}
-
移动构造函数:将源对象的资源指针直接"窃取"到新对象,并将源对象指针置空,避免深拷贝的开销。
-
移动赋值运算符:先释放自身资源,再窃取源对象资源,同样将源对象置空。
-
当函数返回局部对象时,编译器会自动将其视为右值,优先匹配移动构造。
-
noexcept对于移动操作很重要,它允许标准库容器在扩容时选择更高效的移动而非拷贝。
4.4 左值引用缺陷与右值引用价值
void process(const MyString& s)
{
cout << "process(const&): 只能读,不能移动: " << s << endl;
MyString copy = s; // 必须拷贝
}
void process(MyString&& s)
{
cout << "process(&&): 可以移动资源: " << s << endl;
MyString moved = std::move(s); // 移动构造,s 变空
}
void test_9()
{
test_8();
cout << "\n=== 演示左值引用缺陷和右值引用优势 ===\n" << endl;
MyString a("I am an lvalue");
const MyString& ref = a;
// 缺陷1:无法区分左/右值,只能提供一种按引用传递的方式
cout << "调用 process(const&) 传入左值: ";
process(a); // const& 版本,内部只能拷贝
cout << "调用 process(const&) 传入右值: ";
process(MyString("temporary")); // 仍是 const&,无法移动资源
// 右值引用意义:通过重载,右值自动匹配移动版本
cout << "调用 process(&&) 传入右值: ";
process(MyString("temporary")); // 调用 && 版本,移动构造
}
-
仅用
const T&无法区分实参是左值还是右值,函数内部一律只能进行拷贝,即使传入的是即将销毁的临时对象也无法"窃取"其资源。 -
通过重载
T&&版本,编译器会在实参为右值时自动选择移动版本,实现零拷贝的资源转移,显著提升性能。 -
移动语义本质:将"拷贝"替换为"资源转移",适用于持有堆内存、文件句柄等昂贵资源的对象。
5. 完美转发
在 C++ 中,当你写一个模板函数,它接收的参数需要原封不动地传递给另一个函数时,我们希望:
1.如果传入的是左值,目标函数收到的也是左值(能够匹配 const T& 或 T& 版本);
2.如果传入的是右值,目标函数收到的也是右值(能够匹配 T&& 版本,从而触发移动语义)。
完美转发就是通过 万能引用(T&&) 和 std::forward 来实现这种**"参数值类别不变"**的传递。
5.1 原理与实现
template<typename T>
void perfect_wrapper(T&& arg) // 万能引用
{
process(std::forward<T>(arg)); // 保持原始值类别
//当传入左值 MyString a; 时,T 被推导为 MyString&,std::forward<T>(arg) 返回左值引用。
//当传入右值 MyString("temp") 时,T 被推导为 MyString,std::forward<T>(arg) 返回右值引用。
}
template<typename T>
void badWrapper(T&& arg)
{
cout << "[badWrapper] 准备转发(错误方式)..." << endl;
process(arg); // 没有 forward
}
void test_10()
{
cout << "\n========== test_10 完美转发演示 ==========" << endl;
MyString a("I am lvalue");
cout << "\n--- Pass lvalue (a) ---" << endl;
perfectForwardWrapper(a);
cout << "\n--- Pass rvalue (temporary) ---" << endl;
perfectForwardWrapper(MyString("temporary rvalue"));
cout << "\n--- Compare: wrong version without forward ---" << endl;
cout << "Pass rvalue but badWrapper uses arg directly:" << endl;
badWrapper(MyString("will be passed wrongly"));
cout << "\n--- Verify move semantics triggered ---" << endl;
MyString source("I have precious data");
cout << "source = " << source << endl;
perfectForwardWrapper(std::move(source));
cout << "After move, source = " << source << " (empty)" << endl;
}
-
万能引用 :
template<typename T> void f(T&& arg)中,T&&不是单纯的右值引用。当传入左值时,T推导为T&,引用折叠使T&&变为T&;传入右值时,T推导为T,保持右值引用。 -
std::forward<T>(arg):条件式地将参数转发,左值转发为左值,右值转发为右值,是实现完美转发的核心。 -
为什么要完美转发 :模板函数中参数变为有名变量后一律为左值,直接传递会丢失右值属性,无法调用移动版本。
forward能"记住"参数的原始值类别并恢复。
5.2 错误做法对比
template<typename T>
void badWrapper(T&& arg)
{
cout << "[badWrapper] 准备转发(错误方式)..." << endl;
process(arg); // arg 是左值,永远调用 process(const&)
}
template<typename T>
void bad_wrapper(T& arg)
{ // 只能接受左值
process(arg);
}
template<typename T>
void bad_wrapper(T&& arg)
{
// 能接受右值,但 arg 在函数体内是左值(有名变量)
process(arg); // 还是调用 process(const&)!
}
说明 :如果不使用 std::forward,即使传入右值,函数体内 arg 仍被视为左值,导致只匹配非移动版本,完美转发失败。
6. Lambda 表达式
6.1 基本语法与使用
[ capture ] ( params ) mutable exception -> ret { body }
[ capture ]:捕获列表,说明 lambda 体内可以访问哪些外部变量、以什么方式访问。不可省略(哪怕为空也写 [])。
(params):参数列表(同普通函数)。如果没有参数可写空(),C++11 中不能省略;C++14 起可省略(若无 mutable / exception / ret)。
mutable:可选,使 lambda 体内能修改按值捕获的副本,并允许调用非 const 成员函数。
exception:可选,异常说明(如 noexcept),几乎很少用。
->ret:尾置返回类型。大多数情况可由编译器自动推导,可省略。但如果 lambda 内包含多个return 且类型不同必须指明。
{ body }:函数体。
struct Goods
{
string _name;
double _price;
int _evaluate;
Goods(const char* str, double price, int evaluate)
:_name(str)
,_price(price)
,_evaluate(evaluate)
{}
};
struct ComparePrice
{
bool operator()(const Goods& gl, const Goods& gr)
{
return gl._price < gr._price;
}
};
//....
void test_11()
{
vector<Goods> vg{ {"苹果",2.1,3},{"香蕉",3.2,4},{"草莓",3.3,5} };
sort(vg.begin(), vg.end(), ComparePrice());
//繁琐,需要不同Compare
auto compare1 = [](int x, int y)->bool {return x > y; };
cout << compare1(1, 2) << endl;
auto CompareGoodsPrice = [](const Goods& x, const Goods& y){return x._price < y._price; };//返回值类型可以省略
//cout << CompareGoodsPrice(vg[0], vg[1]) << endl;
sort(vg.begin(), vg.end(), CompareGoodsPrice);
sort(vg.begin(), vg.end(), [](const Goods& x, const Goods& y) {
return x._evaluate < y._evaluate; });
}
-
Lambda 语法:
[捕获](参数) mutable 异常 -> 返回类型 { 函数体 }。 -
返回类型可省略,由编译器自动推导。
-
本质是生成一个匿名函数对象(闭包),可以像函数一样调用,也可以作为
sort等算法的预测。
6.2 捕获列表
//[] 不捕获任何变量[]{}
//[x] 按值捕获 x(在 lambda 创建时拷贝一份)[x]{ return x; }
//[&x] 按引用捕获 x[&x]{ x = 10; }
//[=] 按值捕获所有外围局部变量(默认行为)[=]{ return a + b; }
//[&] 按引用捕获所有外围局部变量[&]{ a = 1; }
//[this] 捕获当前类的 this 指针(可访问成员)[this]{ return this->x; }
class Foo
{
public:
int member = 100;
void demoThis()
{
// [this] 捕获:lambda 体内可直接访问成员变量和成员函数
auto f = [this]()
{
cout << "member = " << member << endl; // 等价于 this->member
member += 10; // 可以修改成员
};
f();
cout << "after f, member = " << member << endl; //110
}
};
void test_12()
{
int a = 1, b = 2, c = 3;
// ---- 1. [] 空捕获 ----
auto f1 = []() {
return 42; // 只能使用 lambda 内部定义的东西
};
cout << "f1(): " << f1() << endl; // 42
// ---- 2. [x] 按值捕获 ----
auto f2 = [a]() {
return a + 10; // a 是定义时拷贝的副本
};
a = 100;
cout << "f2() (a=1 copied): " << f2() << endl; // 11
// ---- 3. [&x] 按引用捕获 ----
auto f3 = [&b]() {
b = 200; // 直接修改外部 b
};
f3();
cout << "b after f3: " << b << endl; // 200
// ---- 4. [=] 按值捕获所有外围局部变量 ----
auto f4 = [=]() {
// 可以读 a, b, c,但不能修改它们(默认 const)
return a + b + c; // a=100, b=200, c=3 303
};
cout << "f4() sum: " << f4() << endl; // 303
// ---- 5. [&] 按引用捕获所有外围局部变量 ----
auto f5 = [&]() {
a = 0;
b = 0;
c = 0;
};
f5();
cout << "after f5: a=" << a << ", b=" << b << ", c=" << c << endl; // 全0
// ---- 6. [this] 捕获当前类的 this 指针 ----
Foo foo;
foo.demoThis(); // 内部展示 this 捕获
// ---- 补充:混合捕获 ----
int x = 10, y = 20, z = 30;
// 除 y 按引用外,其余变量全部按值捕获
auto f6 = [=, &y]() mutable {
x++; // mutable允许修改的是 lambda 内部的拷贝副本.
y = 999; // 修改外部的 y
// z 可以读,但不能修改(值捕获副本即 const)
return x + y + z; // x=11, y=999, z=30 1040
};
cout << "f6(): " << f6() << endl; // 1040
cout << x << " " << y << " " << z << endl; // 10 999 30
}
-
[]:不捕获;[x]:按值捕获;[&x]:按引用捕获;[=]:按值捕获所有自动变量;[&]:按引用捕获所有;[this]:捕获当前对象指针,可访问成员。 -
按值捕获的变量在 lambda 内默认为
const,加上mutable可取消,但修改的只是副本,不影响外部。 -
混合捕获如
[=, &y]表示除y按引用外,其余全部按值。
7. 可变模板参数
C++11 允许模板接受任意数量、任意类型的参数,语法用 ...
template<typename... Args> // Args 叫“模板参数包”
void funcA(Args... args)// args 叫“函数参数包”
{
//这里的 Args 可以匹配零个或多个类型,args 则对应零个或多个值。
//例如:funcA(1, 2.5, "hello"); → Args 被推导为 int, double, const char* ,args包含这三个实参。
}
//不能直接遍历参数包,必须通过编译期 递归展开 或利用 列表初始化
// 递归终止函数:无参数时什么都不做
void Print()
{
cout << endl;
}
//1.递归展开
template<typename T, typename... Args>
void Print(T first, Args... rest)
{
cout << first << ' ';
Print(rest...); // 递归调用,参数少一个
}
//2.利用初始化列表的展开
template<typename... Args>
void Print_all(Args... args)
{
int dummy[] = { (cout << args << ' ', 0)... };//(expression)... 是将表达式对参数包中的每个参数展开 其中(..., 0) 是为了让每个逗号表达式的结果为 0 以初始化数组。
cout << endl;
(void)dummy; // 避免未使用变量警告
}
void test_13()
{
Print(1, 2.5, "hello", 'c'); // 输出:1 2.5 hello c
Print_all(1, 2.5, "hello", 'c'); // 输出:1 2.5 hello c
}
-
typename... Args为模板参数包,Args... args为函数参数包。 -
不能直接遍历参数包,需通过编译期递归 或列表初始化结合逗号表达式展开。
-
递归版本每次剥离一个参数,直到空参数递归终止。
-
列表初始化展开利用
(expression)...对参数包中的每个参数执行表达式,用逗号表达式确保返回0以初始化数组。
8. std::function 包装器与 std::bind 绑定器
8.1 std::function 统一可调用对象
function是一个通用的多态函数容器,定义在 <functional> 头文件中。它能存储、复制和调用任何可调用目标,只要目标与指定的函数签名兼容。
//普通模板版本
template<class F, class T>
T useF1(F f, T x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double f(double i)
{
return i / 2;
}
struct Functor
{
double operator()(double d)
{
return d / 3;
}
};
//包装器版本
double useF2(const function<double(double)>& f, double x)
{
static int count = 0;
cout << "count:" << ++count << endl;
cout << "count:" << &count << endl;
return f(x);
}
double add(double a, double b) { return a + b; }
double divide(double a, double b) { return a / b; }
struct Computer
{
double value = 1.5;
double compute(double x) const { return x * x + value; }
};
void test_14()
{
//普通模板版本
cout << "===== 模板版本 (useF1) =====" << endl;
// 生成 useF<double(*)(double), double>
cout << useF1(f, 11.11) << endl;
// 生成 useF<Functor, double>
cout << useF1(Functor(), 11.11) << endl;
// 生成 useF<__lambda_123, double>
cout << useF1([](double d)->double { return d / 4; }, 11.11) << endl;
//此时useF被实例化成三份
//function包装器版本
cout << "===== 包装器版本 (useF2) =====" << endl;
cout << useF2(f, 11.11) << endl; // f 被隐式包装
cout << useF2(Functor(), 11.11) << endl; // 仿函数临时对象被包装
cout << useF2([](double d)->double { return d / 4; }, 11.11) << endl; // lambda 被包装
//只生成一份 useF 函数体(所有可调用对象都统一通过 std::function 接口调用)
}
-
std::function<Ret(Args...)>是一个多态函数包装器,可以存储函数指针、函数对象、lambda 等任何可调用实体。 -
模板版本
useF1会根据传入的可调用对象类型生成多个实例(代码膨胀),而useF2只生成一份,可调用对象通过类型擦除统一管理。 -
极大提高了接口的灵活性和代码复用。
8.2 std::bind 参数绑定与占位符
bind 是 C++11 引入的参数绑定器,定义在 <functional>中,它可以将一个可调用对象的部分参数预先绑定固定值,或者调整参数顺序,从而生成一个新的可调用对象,与 std::function 协同工作,实现签名适配。
double add(double a, double b) { return a + b; }
double divide(double a, double b) { return a / b; }
struct Computer {
double value = 1.5;
double compute(double x) const { return x * x + value; }
};
void test_14()
{
cout << "===== bind版本 =====" << endl;
// 1. 绑定二元函数 add,固定第一个参数为 5.0
cout << useF2(bind(add, 5.0, _1), 3.0) << endl; // 8.0
// 2. 绑定 divide,将被除数固定为 1.0,实现倒数
cout << useF2(bind(divide, 1.0, _1), 4.0) << endl; // 0.25
// 3. 绑定成员函数
Computer comp;
auto memFn = bind(&Computer::compute, &comp, _1); // 将对象与 this 绑定
cout << useF2(memFn, 3.0) << endl; // 3*3 + 1.5 = 10.5
// 4. 甚至可通过 bind 重新排列参数顺序(演示翻转除法)
auto flipped = bind(divide, _2, _1); // 二元函数,参数顺序调换
// 但 useF2 只接受一元,这里体现不出来,可以单独调用:
cout << "flipped(10, 2) = " << flipped(10, 2) << endl; // 2/10 = 0.2
// 5. 也可以嵌套 bind(不常用,但可行)
auto complicated = bind(add, bind(divide, _1, 2.0), _1); // (x/2) + x
cout << useF2(complicated, 10.0) << endl; // 10/2 + 10 = 15
}
-
std::bind可以将函数的某些参数预先绑定为固定值,返回一个新的可调用对象,从而适配调用签名。 -
占位符
_1, _2, ...(定义在std::placeholders命名空间)表示新函数的第几个参数。 -
可以绑定成员函数,此时必须提供对象指针或引用作为第二个参数。
-
支持参数重排、嵌套绑定等高级用法,结合
function能灵活构建调用链。