【学习笔记04】C++11新特性学习总结(上)
·
📝 说明:本文为C++学习笔记,AI辅助系统整理
📅 整理时间:2025年9-10月
🎯 目的:系统梳理C++11核心特性,建立完整知识体系
💡 适用场景:面试复习、技术查阅
📚 本文内容
本篇整理的知识点(上半部分)
本篇整理了C++11的前4个核心特性:
- auto和decltype(类型推导)
- 列表初始化
- 移动语义(右值引用)
- lambda表达式
这是上半部分,下半部分继续学习其他特性。
知识点1:auto和decltype
auto类型推导机制
auto的作用
让编译器自动推导变量类型,简化复杂类型声明。
典型示例:
// 复杂的STL迭代器类型
std::map<std::string, std::vector<int>> complex_map;
// 传统写法(太长了)
std::map<std::string, std::vector<int>>::iterator it1 = complex_map.begin();
// 用auto简化
auto it2 = complex_map.begin(); // 编译器自动推导
auto的推导规则
核心推导规则:
// 1. const和引用的处理
const int ci = 42;
auto a = ci; // a是int(丢失const)
const auto& b = ci; // b是const int&(保留const)
int x = 10;
int& rx = x;
auto c = rx; // c是int(丢失引用)
auto& d = rx; // d是int&(保留引用)
// 2. 数组退化
int arr[5];
auto g = arr; // int*(数组退化为指针)
Mermaid图解:auto推导规则
decltype的精确推导
典型示例:
int x = 5;
const int& cr = x;
decltype(x) y = 10; // y是int
decltype(cr) z = x; // z是const int&(完全保留)
decltype((x)) w = x; // w是int&(括号表达式!)
重要规则:带括号的表达式会变成引用!
decltype在模板中的应用
典型示例:
// 后置返回类型语法
template<typename T1, typename T2>
auto add(T1 a, T2 b) -> decltype(a + b) {
return a + b;
}
// 编译器会根据a+b的类型推导返回类型
auto result1 = add(3.14, 2); // double
auto result2 = add(1, 2); // int
学习中的疑问1:decltype为什么适合模板?
疑问:decltype和auto有什么区别?为什么说decltype适合模板编程?
解答:
- auto:简化代码,但会丢失const、引用等修饰符
- decltype:完全保留类型信息,包括const、引用等
- 模板场景:返回类型可能依赖参数类型,decltype能精确推导
例子对比:
const int x = 5;
const int& rx = x;
auto a = rx; // a是int(丢了const和引用)
decltype(rx) b = x; // b是const int&(完全保留)
知识点2:列表初始化
统一初始化语法
典型示例:
// 基本类型
int a{42};
double b{3.14};
// 容器初始化
std::vector<int> vec{1, 2, 3, 4, 5};
std::map<std::string, int> ages{{"Alice", 25}, {"Bob", 30}};
// 对象初始化
class Point {
public:
Point(int x, int y) : x_(x), y_(y) {}
private:
int x_, y_;
};
Point p{10, 20};
防止窄化转换
安全性优势:
// 传统初始化(危险)
int x = 3.14; // 允许,但截断小数 → x=3
char c = 256; // 允许,但值溢出
// 列表初始化(安全)
int y{3.14}; // ❌ 编译错误!防止窄化
char d{256}; // ❌ 编译错误!防止溢出
我的理解:列表初始化在编译期就能发现类型转换问题,更安全。
initializer_list
典型示例:
class MyContainer {
public:
MyContainer(std::initializer_list<int> list) {
for (auto item : list) {
data_.push_back(item);
}
}
private:
std::vector<int> data_;
};
MyContainer container{1, 2, 3, 4, 5}; // 直接用{}初始化
知识点3:移动语义
移动语义核心概念
左值和右值
核心概念:
int x = 10; // x是左值(有名字,可取地址)
int y = x + 5; // x+5是右值(临时值,不能取地址)
int z = func(); // func()返回值是右值
右值引用语法
典型示例:
int&& rref = 20; // 绑定到右值
int&& rref2 = std::move(x); // move将左值转为右值
移动构造函数
课上详细讲解的例子:
class Resource {
private:
int* data;
size_t size;
public:
// 构造函数
Resource(size_t s) : size(s) {
data = new int[size];
cout << "构造:分配了" << size << "个int" << endl;
}
// 拷贝构造(深拷贝)
Resource(const Resource& other) : size(other.size) {
data = new int[size];
memcpy(data, other.data, size * sizeof(int));
cout << "拷贝构造:复制了" << size << "个int" << endl;
}
// 移动构造(资源转移)
Resource(Resource&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // "偷"资源
other.size = 0;
cout << "移动构造:转移了资源" << endl;
}
~Resource() {
delete[] data;
cout << "析构" << endl;
}
};
运行效果:
Resource r1(100); // 构造
Resource r2 = r1; // 拷贝构造(深拷贝)
Resource r3 = std::move(r1); // 移动构造(资源转移)
// r1现在被"掏空"了,data=nullptr
移动构造流程图解
学习中的疑问2:移动语义
疑问:移动后的对象还能用吗?
解答:
- 移动后的对象处于"有效但未指定"状态
- 可以给它重新赋值
- 但不要访问它的数据(已经被"偷"走了)
安全做法:
string s1 = "hello";
string s2 = std::move(s1);
// ❌ 危险
// cout << s1 << endl; // s1的内容已被转移
// ✅ 安全
s1 = "new value"; // 重新赋值
cout << s1 << endl; // OK
学习中的疑问3:noexcept的作用
疑问:为什么移动函数要加noexcept?
解答:
- 性能优化:编译器可以进行更激进的优化
- 容器优化:vector扩容时优先用移动而不是拷贝
- 异常安全:告诉编译器不会抛异常
例子:
// 无noexcept:vector扩容可能用拷贝
class WithoutNoexcept {
public:
WithoutNoexcept(WithoutNoexcept&&) { }
};
// 有noexcept:vector扩容优先用移动
class WithNoexcept {
public:
WithNoexcept(WithNoexcept&&) noexcept { }
};
知识点4:lambda表达式
基本语法
核心概念:
// 最简单的lambda
auto add = [](int a, int b) { return a + b; };
int result = add(3, 5); // 8
// 完整语法
[capture](parameters) mutable -> return_type { body }
捕获方式
课上详细讲解的例子:
int x = 10;
int y = 20;
// 1. 值捕获(拷贝)
auto f1 = [x]() {
// x是副本,不能修改
return x;
};
// 2. 引用捕获(引用)
auto f2 = [&x]() {
x++; // 可以修改外部的x
};
// 3. 混合捕获
auto f3 = [x, &y]() {
// x是副本,y是引用
};
// 4. 全部值捕获
auto f4 = [=]() { }; // 捕获所有变量的副本
// 5. 全部引用捕获
auto f5 = [&]() { }; // 捕获所有变量的引用
lambda与STL算法
课上的实用例子:
std::vector<int> vec = {5, 2, 8, 1, 9};
// 降序排序
std::sort(vec.begin(), vec.end(),
[](int a, int b) { return a > b; });
// 查找大于5的元素
int threshold = 5;
auto it = std::find_if(vec.begin(), vec.end(),
[threshold](int x) { return x > threshold; });
// 统计偶数个数
int count = std::count_if(vec.begin(), vec.end(),
[](int x) { return x % 2 == 0; });
学习中的疑问4:lambda捕获
疑问:引用捕获变量在lambda返回时死亡,捕获了有什么用?
解答:
引用捕获主要用于:
- 在同一作用域内使用(最常见,安全)
- 修改外部变量
- 避免大对象的拷贝开销
例子:
void process() {
std::vector<int> data{1, 2, 3, 4, 5};
int sum = 0;
// ✅ 安全:在同一作用域使用
std::for_each(data.begin(), data.end(),
[&sum](int x) { sum += x; });
cout << "总和:" << sum << endl; // 15
}
❌ 危险的用法:
auto makeLambda() {
int x = 10;
return [&x]() { return x; }; // ❌ x的引用会失效!
}
lambda捕获方式对比图
graph LR
A[外部变量x] --> B{捕获方式}
B -->|值捕获 x| C[lambda获得x的副本]
B -->|引用捕获 &x| D[lambda获得x的引用]
C --> E[不能修改x]
C --> F[lambda可以返回]
D --> G[可以修改x]
D --> H[❌ 不能返回lambda]
style E fill:#90EE90
style F fill:#90EE90
style G fill:#90EE90
style H fill:#FFB6C1
学习中的疑问5:mutable关键字
疑问:lambda里值捕获的变量能修改吗?
解答:
- 默认不能修改(值捕获是const的)
- 加上
mutable关键字可以修改
例子:
int count = 0;
// ❌ 编译错误
auto f1 = [count]() {
count++; // 错误:count是const
};
// ✅ 加mutable
auto f2 = [count]() mutable {
count++; // OK,但只修改副本
return count;
};
cout << f2() << endl; // 1
cout << f2() << endl; // 2(副本累加)
cout << count << endl; // 0(外部count没变)
学习收获(上半部分)
- auto能简化复杂类型声明,但要注意丢失const和引用
- decltype保留完整类型信息,适合模板编程
- 列表初始化更安全,能在编译期防止窄化转换
- 移动语义通过"偷"资源避免深拷贝,性能提升明显
- lambda表达式让代码更简洁,特别是配合STL算法
- 值捕获安全但不能修改,引用捕获能修改但要注意生命周期
💡 下一步:继续学习下半部分(nullptr、enum class、delete/default、using别名)
📝 本篇整理完成 - C++11新特性知识体系(上)
更多推荐

所有评论(0)