一、C++11:现代C++的里程碑
C++11是C++语言的重大更新,从C++98到C++11经历了13年之久。这次的更新为C++带来了现代化编程范式,让C++在保持高性能的同时,写起来更加优雅和安全。
cpp
// C++98的"古早味"
std::vector<int> v;
for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it) {
// 繁琐的类型声明
}
// C++11的"现代风"
std::vector<int> v;
for (auto& num : v) {
// 简洁明了!
}
二、列表初始化:一切皆可{}初始化
2.1 从C++98到C++11的进化
cpp
// C++98:只支持数组和结构体
int arr[] = {1, 2, 3};
Point p = {1, 2};
// C++11:一切皆可{}初始化
int x{42}; // 基本类型
Date d{2024, 7, 25}; // 自定义类型
std::vector<int> v{1, 2, 3}; // 容器初始化
2.2 std::initializer_list:容器初始化的利器
cpp
// 想象一下:你去超市购物清单
std::vector<std::string> shoppingList = {
"苹果", "牛奶", "面包", "鸡蛋"
};
// 底层原理:initializer_list包装了这些值
std::initializer_list<int> il = {10, 20, 30};
// il.begin() -> 指向10
// il.end() -> 指向30之后
三、右值引用与移动语义:告别不必要的拷贝
3.1 左值 vs 右值:一个形象的比喻
cpp
// 左值:有名字的变量,像有"身份证"的人
std::string name = "张三"; // name是左值,可以取地址
// 右值:临时值,像"外卖小哥"送来的餐
std::string getTemp() {
return "临时字符串"; // 返回值是右值
}
// 外卖小哥(右值)把餐送到你家门口就走了
// 你不需要记住他的名字,只需要接过餐盒
右值 (临时快递)
字面值
不能取地址
表达式结果
一次性使用
函数返回值
生命周期短
左值 (有身份的人)
变量名
可以取地址
持久存在
可以多次使用
3.2 移动语义:像搬家而不是复制房子
cpp
class String {
public:
// 拷贝构造:像复印整本书
String(const String& other) {
data = new char[strlen(other.data) + 1];
strcpy(data, other.data); // 深拷贝,代价高!
}
// 移动构造:像直接拿走别人的书
String(String&& other) noexcept {
data = other.data; // "偷"别人的资源
other.data = nullptr; // 原主人不再拥有
}
private:
char* data;
};
// 使用场景对比
String s1 = createString(); // 移动构造,高效!
String s2 = s1; // 拷贝构造,代价高
String s3 = std::move(s1); // 移动构造,s1被"掏空"
函数返回临时对象
接收方式
传值接收
移动构造接收
深拷贝整个资源
性能开销大
转移资源所有权
近乎零开销
两份完整资源
一份资源
原对象被置空
3.3 完美转发:保持值的"原汁原味"
cpp
// 普通转发:会丢失右值属性
template<typename T>
void relay(T arg) {
process(arg); // arg总是左值!
}
// 完美转发:保持值的原始类型
template<typename T>
void perfectRelay(T&& arg) {
process(std::forward<T>(arg)); // 保持左值/右值属性
}
// 使用示例
std::string str = "hello";
perfectRelay(str); // 传递左值
perfectRelay(std::string("world")); // 传递右值
perfectRelay(std::move(str)); // 传递将亡值
四、可变参数模板:告别重复代码
4.1 可变参数函数模板
cpp
// 传统方式:需要多个重载
void print() {}
void print(int a) { cout << a; }
void print(int a, int b) { cout << a << b; }
// ... 需要N个重载!
// C++11方式:一个模板搞定
template<typename... Args>
void print(Args... args) {
// sizeof...(args) 获取参数个数
// 递归展开参数包
((cout << args << " "), ...); // C++17折叠表达式
}
// 使用:像Python一样灵活
print(1, 2.5, "hello", 'A'); // 输出:1 2.5 hello A
4.2 emplace系列:直接在容器中构造
cpp
std::vector<std::pair<std::string, int>> v;
// 传统方式:构造+拷贝
v.push_back(std::make_pair("苹果", 3)); // 构造临时对象,再拷贝
// C++11方式:直接构造
v.emplace_back("苹果", 3); // 直接在vector内存中构造pair
// 优势对比:
// push_back: 构造pair -> 拷贝/移动到容器
// emplace_back: 直接在容器中构造pair
emplace_back 流程
传递构造参数
直接在容器内存中
构造对象
push_back 流程
构造临时对象
调用移动构造
拷贝到容器
析构临时对象
五、Lambda表达式:匿名函数的优雅写法
5.1 Lambda语法:简洁的匿名函数
cpp
// 传统函数指针:繁琐
int add(int a, int b) { return a + b; }
int (*func)(int, int) = add;
// 仿函数:需要定义类
struct Add {
int operator()(int a, int b) { return a + b; }
};
// Lambda:简洁直观
auto add = [](int a, int b) { return a + b; };
cout << add(1, 2); // 输出3
// 更多Lambda示例
auto greet = [] { cout << "Hello!" << endl; };
auto square = [](int x) -> int { return x * x; };
5.2 捕捉列表:访问外部变量
cpp
int x = 10, y = 20;
// 值捕捉 [x, y]:拷贝变量,不能修改原值
auto func1 = [x, y] { return x + y; };
// 引用捕捉 [&x, &y]:引用变量,可以修改原值
auto func2 = [&x, &y] { x++; y++; };
// 隐式捕捉
auto func3 = [=] { return x + y; }; // 隐式值捕捉所有变量
auto func4 = [&] { x++; y++; }; // 隐式引用捕捉所有变量
// 混合捕捉 [=, &x]:x引用捕捉,其他值捕捉
auto func5 = [=, &x] { return x + y; }; // 可以修改x,不能修改y
六、智能指针:自动化的内存管理
cpp
// 原始指针:手动管理内存
int* p = new int(10);
// ... 使用p
delete p; // 容易忘记!
// C++11智能指针:自动管理
std::unique_ptr<int> p1(new int(10)); // 独占所有权
std::shared_ptr<int> p2 = std::make_shared<int>(20); // 共享所有权
std::weak_ptr<int> p3 = p2; // 弱引用,不增加引用计数
// 离开作用域时自动释放内存,无需手动delete
七、其他重要特性
7.1 auto类型推导
cpp
// 不再需要冗长的类型声明
std::vector<std::map<std::string, std::pair<int, double>>>::iterator it;
// 变成:
auto it = complexContainer.begin();
7.2 范围for循环
cpp
std::vector<int> v = {1, 2, 3, 4, 5};
// 传统方式
for (size_t i = 0; i < v.size(); ++i) {
cout << v[i] << " ";
}
// 现代方式
for (auto& num : v) {
cout << num << " ";
}
7.3 nullptr替代NULL
cpp
void func(int) { cout << "int版本"; }
void func(int*) { cout << "指针版本"; }
func(NULL); // 可能调用int版本(NULL通常是0)
func(nullptr); // 明确调用指针版本
八、实战应用示例
8.1 线程池中的Lambda使用
cpp
// 创建线程执行复杂计算
std::thread t([data = std::move(data)]() {
// 这里可以安全地使用data
auto result = process(data);
return result;
});
// Lambda捕捉外部变量,在线程中安全使用
8.2 算法自定义比较器
cpp
struct Product {
std::string name;
double price;
int rating;
};
std::vector<Product> products = {
{"手机", 2999.0, 5},
{"电脑", 6999.0, 4},
{"平板", 3999.0, 4}
};
// 按价格排序
std::sort(products.begin(), products.end(),
[](const Product& a, const Product& b) {
return a.price < b.price;
});
// 按评分排序
std::sort(products.begin(), products.end(),
[](const Product& a, const Product& b) {
return a.rating > b.rating;
});
九、总结:为什么学习C++11?
9.1 性能提升
- 移动语义:减少不必要的拷贝,提升程序性能
- 完美转发:避免额外的构造和析构
- emplace系列:直接在容器中构造对象
9.2 代码简洁
- auto:减少冗长的类型声明
- Lambda:简化回调函数的编写
- 范围for:简化容器遍历
9.3 安全性增强
- 智能指针:自动管理内存,避免内存泄漏
- nullptr:避免指针类型混淆
- override/final:明确继承关系
9.4 表达能力提升
- 可变参数模板:支持任意数量和类型的参数
- 右值引用:精确控制对象生命周期
- Lambda捕捉列表:灵活访问外部变量
C++11不是对C++的小修补,而是一次彻底的现代化改造。掌握这些特性,你将能编写出更高效、更安全、更优雅的C++代码。虽然学习曲线较陡,但投资回报率极高------这些是现代C++程序员的必备技能!
在下面两期咱们会对Lambda表达式和智能指针进行详细理解,期待与你下期相见~~~
