CPP学习笔记—现代C++的特性
C++11 被认为是 C++ 语言的一次重生,标志着“现代 C++”的开端。后续的标准则在这个基础上不断完善、增强和现代化。
一、 C++11:现代 C++ 的基石
C++11 是一次巨大的飞跃,引入了大量深刻影响 C++ 编程范式的特性。
1. 语言核心增强 (提升易用性和表达力)
auto关键字:- 是什么:自动类型推导。编译器可以根据变量的初始化表达式自动推导出其类型。
- 为什么需要:避免编写冗长、复杂的类型名,特别是对于 STL 迭代器和模板类型。
- 示例:
1
2
3
4// 旧式
std::vector<int>::iterator it = my_vector.begin();
// C++11
auto it_new = my_vector.begin(); // 自动推导为 std::vector<int>::iterator
nullptr:- 是什么:一个类型安全的空指针常量,类型为
std::nullptr_t。 - 为什么需要:解决了旧
NULL(通常是0或(void*)0) 的类型不明确问题,后者在函数重载时可能导致歧义。 - 示例:
1
2
3
4void foo(int);
void foo(char*);
foo(NULL); // 可能会调用 foo(int),不符合预期
foo(nullptr); // 明确调用 foo(char*)
- 是什么:一个类型安全的空指针常量,类型为
基于范围的
for循环 (Range-based for loop):- 是什么:一种更简洁的遍历容器或序列的语法。
- 为什么需要:简化循环代码,减少因迭代器操作而出错的可能。
- 示例:
1
2
3
4
5
6
7
8
9std::vector<int> nums = {1, 2, 3, 4, 5};
// 旧式
for (auto it = nums.begin(); it != nums.end(); ++it) {
std::cout << *it << " ";
}
// C++11
for (int num : nums) {
std::cout << num << " ";
}
统一初始化 (Uniform Initialization) & 初始化列表 (
std::initializer_list):- 是什么:使用花括号
{}对变量、对象、数组、容器等进行统一的初始化。 - 为什么需要:提供一种通用的、无歧义的初始化语法,防止“最令人烦恼的解析”(Most Vexing Parse)。
- 示例:
1
2
3int x{0};
std::vector<int> v{1, 2, 3};
MyClass obj{arg1, arg2}; // 调用构造函数
- 是什么:使用花括号
Lambda 表达式:
- 是什么:一种在代码中就地定义匿名函数的方式。
- 为什么需要:极大地简化了向 STL 算法等传递“可调用对象”(callable)的过程。
- 语法:
[捕获列表](参数列表) -> 返回类型 { 函数体 } - 示例:
1
2
3
4std::vector<int> nums = {5, 2, 8, 3, 1};
std::sort(nums.begin(), nums.end(), [](int a, int b) {
return a < b; // 就地定义一个比较函数
});
类型别名 (
using):- 是什么:
using提供了比typedef更清晰、更强大的类型别名语法,并且可以用于模板。 - 示例:
1
2
3
4
5
6typedef std::map<std::string, int> OldMap; // 旧式
using NewMap = std::map<std::string, int>; // C++11
template<typename T>
using Vec = std::vector<T>; // 别名模板
Vec<int> v;
- 是什么:
2. 性能与资源管理
右值引用 (
&&) 与移动语义 (Move Semantics):- 是什么:引入了新的引用类型“右值引用”,专门用于绑定到临时对象(右值)。“移动语义”允许将资源(如动态分配的内存)从一个对象“转移”到另一个对象,而不是进行昂贵的拷贝。
- 为什么需要:大幅提升性能,避免对临时对象进行不必要的深拷贝。
- 核心:通过定义移动构造函数和移动赋值运算符来实现。
std::move函数用于将一个左值强制转换为右值引用。 - 示例:
1
2
3
4
5
6
7
8
9
10class ResourceHolder {
public:
// 移动构造函数
ResourceHolder(ResourceHolder&& other) noexcept {
data = other.data; // "窃取"资源
other.data = nullptr; // 将源对象置于有效但空的状态
}
private:
int* data;
};
智能指针 (Smart Pointers):
- 是什么:在
<memory>头文件中引入了std::unique_ptr,std::shared_ptr,std::weak_ptr。 - 为什么需要:利用 RAII(资源获取即初始化)原则,自动化内存管理,极大地减少了内存泄漏和悬挂指针的风险。
std::unique_ptr:独占所有权,轻量级,无法拷贝,但可以移动。std::shared_ptr:共享所有权,通过引用计数管理资源生命周期。std::weak_ptr:shared_ptr的观察者,不增加引用计数,用于打破循环引用。
- 是什么:在
constexpr:- 是什么:编译时常量表达式。
constexpr函数可以在编译期求值(当其参数是编译期常量时),constexpr变量则必须是编译期常量。 - 为什么需要:将计算从运行时提前到编译时,提升性能,并允许这些值用于需要编译期常量的地方(如数组大小、模板参数)。
- 示例:
1
2
3
4constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int arr[factorial(5)]; // 在编译时计算数组大小
- 是什么:编译时常量表达式。
3. 并发编程
std::thread:提供了标准化的线程库,用于创建和管理线程。std::mutex,std::lock_guard,std::unique_lock:提供了互斥锁机制,用于保护共享数据,防止数据竞争。std::atomic:提供了原子类型和操作,用于无锁编程。std::future,std::promise,std::async:提供了高级异步编程工具,用于管理异步任务的结果。
4. 其他重要特性
- 变长参数模板 (Variadic Templates):允许模板接受任意数量的参数,是实现
std::tuple,std::function等的基础。 enum class(强类型枚举):解决了传统enum的作用域污染和隐式类型转换问题。final和override:override确保派生类函数确实重写了基类的虚函数(否则编译错误),final阻止类被继承或虚函数被再次重写。
二、 C++14:对 C++11 的完善和优化
C++14 是一个小的修订版本,主要目标是修复 C++11 的一些问题并提供便利性改进。
泛型 Lambda (Generic Lambdas):
- 是什么:允许在 Lambda 的参数中使用
auto,使其可以接受任意类型的参数。 - 示例:
1
2
3auto add = [](auto a, auto b) { return a + b; };
add(1, 2); // a, b 是 int
add(1.5, 2.5); // a, b 是 double
- 是什么:允许在 Lambda 的参数中使用
函数返回类型推导:
- 普通函数也可以使用
auto作为返回类型,编译器会自动推导。 - 示例:
1
auto add(int a, int b) { return a + b; } // 返回类型被推导为 int
- 普通函数也可以使用
std::make_unique:- C++11 提供了
std::make_shared,但漏掉了std::make_unique。C++14 补上了它。 - 为什么需要:提供异常安全保证,并使代码更简洁。
- 示例:
1
2
3
4// 旧式
std::unique_ptr<MyClass> p(new MyClass());
// C++14
auto p_new = std::make_unique<MyClass>();
- C++11 提供了
二进制字面量和数字分隔符:
- 示例:
1
2int binary_val = 0b101010; // 二进制
long long large_num = 1'000'000'000; // 数字分隔符,提高可读性
- 示例:
constexpr的扩展:放宽了对constexpr函数的限制,允许在其中使用局部变量、循环和if语句等。
三、 C++17:现代化的又一次飞跃
C++17 带来了大量实用的库和语言特性,显著提升了日常编程的效率和代码的清晰度。
结构化绑定 (Structured Bindings):
- 是什么:允许用一条语句将
struct,pair,tuple, 数组等对象的成员或元素解构到独立的变量中。 - 为什么需要:极大地简化了对复合类型成员的访问。
- 示例:
1
2
3
4
5
6
7
8
9std::map<std::string, int> my_map;
// 旧式
for(const auto& pair : my_map) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
// C++17
for(const auto& [key, value] : my_map) {
std::cout << key << ": " << value << std::endl;
}
- 是什么:允许用一条语句将
if和switch的初始化语句:- 是什么:允许在
if或switch语句中直接声明和初始化一个仅在该语句块内有效的变量。 - 示例:
1
2
3
4if (auto it = my_map.find("key"); it != my_map.end()) {
// 'it' 只在 if 和 else 块中可见
std::cout << "Found: " << it->second << std::endl;
}
- 是什么:允许在
if constexpr:- 是什么:一种在编译期进行条件判断的
if语句。如果条件为假,对应的代码块将不会被编译。 - 为什么需要:在模板元编程中,可以根据类型特性选择性地编译代码,避免了复杂的 SFINAE 技术。
- 示例:
1
2
3
4
5
6
7
8template <typename T>
auto get_value(T t) {
if constexpr (std::is_pointer_v<T>) {
return *t; // 只有当 T 是指针类型时,这行代码才会被编译
} else {
return t;
}
}
- 是什么:一种在编译期进行条件判断的
库特性:
std::optional:表示一个可能存在或不存在的值,用于替代使用魔法值(如-1)或空指针来表示“无值”的情况。std::variant:一个类型安全的联合体(union),可以在其生命周期内存储不同类型的值,但同一时间只能存储一种。std::any:一个可以存储任意可拷贝类型值的类型安全容器。std::filesystem:提供了标准化的文件系统操作库,可以跨平台地处理路径、文件和目录。- 并行算法 (Parallel Algorithms):STL 的许多算法(如
sort,for_each)增加了并行执行策略,可以轻松利用多核 CPU。
折叠表达式 (Fold Expressions):
- 是什么:一种简化对变长参数模板参数包进行操作的语法。
- 示例:
1
2
3
4template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 将所有参数相加
}
四、 C++20:革命性的新时代
C++20 是继 C++11 之后最大的一次更新,引入了被称为“四大特性”的革命性功能。
Concepts (概念):
- 是什么:对模板参数的约束。它允许我们明确指定模板参数必须满足哪些要求(如必须支持
+运算,必须是可迭代的等)。 - 为什么需要:解决了模板长期以来因 SFINAE 导致的极其晦涩难懂的编译错误信息。Concepts 提供了清晰、简洁的错误报告,并使模板接口的意图更加明确。
- 示例:
1
2
3
4
5template<typename T>
concept Integral = std::is_integral_v<T>;
template<Integral T> // T 必须满足 Integral 概念
void process(T val) { /* ... */ }
- 是什么:对模板参数的约束。它允许我们明确指定模板参数必须满足哪些要求(如必须支持
Modules (模块):
- 是什么:一种现代化的源码组织方式,旨在替代传统的头文件
#include机制。 - 为什么需要:
- 极大提升编译速度:模块只会被编译一次。
- 避免宏污染:宏的作用域被限制在模块内部。
- 明确的接口:通过
export关键字明确指定对外暴露的内容。
- 是什么:一种现代化的源码组织方式,旨在替代传统的头文件
Coroutines (协程):
- 是什么:一种可以被挂起(suspend)和恢复(resume)的函数,用于简化异步编程。
- 为什么需要:让异步代码(如网络I/O、事件循环)写起来像同步代码一样直观,避免了“回调地狱”(callback hell)。
- 关键字:
co_await,co_yield,co_return。
Ranges (范围库):
- 是什么:一套全新的处理数据序列的组件和思想。它将算法和容器解耦,并支持惰性求值和函数式编程风格的管道操作。
- 为什么需要:使代码更具表现力和组合性。
- 示例:
1
2
3
4
5
6
7std::vector<int> nums = {1, 2, 3, 4, 5, 6, 7, 8};
auto results = nums | std::views::filter([](int n){ return n % 2 == 0; })
| std::views::transform([](int n){ return n * n; });
// results 是一个视图,只有在迭代时才会计算
for (int n : results) {
std::cout << n << " "; // 输出 4 16 36 64
}
其他重要特性:
- 三路比较运算符 (
<=>,宇宙飞船运算符):自动生成所有六个比较运算符(==,!=,<,>,<=,>=)。 consteval和constinit:更严格的编译期求值和初始化控制。std::format:一个现代化、类型安全且高性能的文本格式化库。
- 三路比较运算符 (
五、 展望 C++23 及未来
C++ 标准委员会继续以每三年一个版本的节奏推进语言发展。
- C++23 已正式发布,主要特性包括:
std::expected:一个表示“期望的值或错误”的类型,是std::optional和异常处理的强大补充。std::mdspan:一个非拥有所有权的多维数组视图,是科学计算和高性能计算领域的重要工具。- 对 Ranges 的进一步增强:如
std::views::zip,std::ranges::to等。 import std;:将整个标准库作为命名模块导入,极大地简化了模块的使用。if consteval:更精细的编译期求值判断。
总结
从 C++11 到 C++23,C++ 的演进路径非常清晰:
- 提升易用性和安全性:通过
auto, 智能指针,nullptr,enum class等特性降低了语言的学习曲线和出错率。 - 增强表达能力和简洁性:Lambda, 范围 for, 结构化绑定, Ranges 等让代码更贴近问题本身,而非底层实现。
- 拥抱现代化编程范式:全面支持并发、异步(协程)、函数式编程(Ranges)和元编程(Concepts,
if constexpr)。 - 解决历史遗留问题:Modules 旨在解决头文件系统带来的编译效率和代码隔离问题。
学习现代 C++,通常意味着从 C++11/14 开始,并逐步掌握 C++17 和 C++20 的核心特性。这些新标准共同构建了一个功能更强大、使用更安全、代码更优雅的 C++ 语言。









