一、新增类型
新增的数据类型有:long long 、unsigned long long、char16_t、char32_t。
|------------------------------|-------------------------------------|------------------------------------------------------------------|
| 数据类型 | 描述 | 作用与优势 |
| long long unsigned long long | 至少 64 位的整数类型。 | 解决大整数运算:在 C++98 中这通常是编译器扩展,C++11 将其标准化。用于处理超过 int 或 long 范围的大数值。 |
| char16_t | 16 位字符类型。 | Unicode 支持 (UTF-16):用于存储中文、日文等 Unicode 字符,配合 u"..." 字符串字面量使用。 |
| char32_t | 32 位字符类型。 | Unicode 支持 (UTF-32):用于存储更广泛的 Unicode 字符,配合 U"..." 字符串字面量使用。 |
| std::nullptr_t | nullptr 关键字所属的类型,定义在 <cstddef> 中。 | 类型安全:解决了旧标准中 NULL(通常定义为 0)在函数重载时产生的歧义,明确表示空指针。 |
二、数据初始化的变化
|--------------------------|----------------------------------------------------------|----------------------------------------------------------------|
| 改动 | 作用 | 解决的问题 |
| 引入 {} 统一语法 | 允许使用统一的 {} 语法初始化几乎所有类型的对象,包括内置类型、数组、结构体、类和 STL 容器。 | 解决了 C++98 中初始化方式不一致的问题(如数组用 {},类用 (),STL 容器初始化繁琐),使代码风格统一,更易读。 |
| 引入 std::initializer_list | 允许类和容器通过接受 std::initializer_list 类型的构造函数,来使用 {} 进行批量初始化。 | 解决了 STL 容器(如 std::vector)无法像数组一样用一组值直接初始化的问题。 |
| 禁止窄化转换 | 在使用 {} 初始化时,编译器会禁止可能导致数据丢失的隐式类型转换(如 double 到 int)。 | 解决了 C++98 中隐式窄化转换可能导致的、难以察觉的数据丢失和逻辑错误,提升了类型安全性。 |
| 解决"最令人烦恼的解析" | 使用 Type name{}; 语法可以明确地声明并初始化一个对象。 | 解决了 Type name(); 被编译器解析为函数声明而非对象定义的歧义问题。 |
三、数据声明的变化
|--------------|-----------------------------|---------------------------------------------|
| 改动 | 作用 | 解决的问题 |
| auto 关键字 | 允许编译器根据初始化表达式自动推导变量的类型。 | 解决了在泛型编程或处理复杂类型(如迭代器)时,类型声明冗长繁琐的问题,使代码更简洁。 |
| decltype 关键字 | 允许查询表达式的类型,但不进行求值。 | 解决了在模板编程中,需要根据表达式类型来声明另一个变量,但又无法直接写出该类型的场景。 |
| 尾置返回类型 | 将函数的返回类型放在参数列表之后,使用 -> 符号。 | 解决了当函数返回类型依赖于参数类型时(如模板),传统声明语法无法表达的问题。 |
|----------------------|-----------------------------------------------|-----------------------------------------------------------------------------|
| 改动 | 作用 | 解决的问题 |
| nullptr 关键字 | 引入了一个专门表示空指针的字面量,其类型为 std::nullptr_t。 | 解决了 C++98 中使用 NULL(通常定义为 0)在函数重载时,无法区分是调用 func(int) 还是 func(pointer) 的歧义问题。 |
| = default 与 = delete | 允许程序员显式地要求编译器生成默认的特殊成员函数(如构造函数),或禁止编译器生成某些函数。 | 提供了对类默认行为的更精细控制。例如,可以明确禁止拷贝构造函数来防止资源被错误复制。 |
| using 别名声明 | 提供了比 typedef 更灵活、可读性更强的类型别名定义方式。 | 解决了 typedef 在定义函数指针或模板别名时语法复杂、不易读的问题。 |
四、C++11 对指针的修改
C++11 对指针体系进行了"史诗级"的重构,主要可以分为两大方向:
1.内存管理模式的革命(智能指针);
2.空指针常量的修正(nullptr);
1、C++11 对指针最大的改动。C++11 引入了 <memory> 头文件,提供了三种主要的智能指针模板类,利用 RAII(资源获取即初始化)机制来自动管理堆内存。
|-----------------|-------------------------------|----------------------------------------------------------------------------------------------|
| 智能指针类型 | 核心特性 | 解决的痛点 |
| std::unique_ptr | 独占所有权。禁止拷贝,只能移动(std::move)。 | 内存泄漏与手动 delete:彻底告别了 new 和 delete 的成对出现。当 unique_ptr 离开作用域时,它会自动释放所管理的内存,杜绝了因异常或忘记释放导致的内存泄漏。 |
| std::shared_ptr | 共享所有权。内部维护引用计数,计数归零时释放。 | 复杂的所有权管理:解决了多个对象需要共享同一个资源时的生命周期管理难题。程序员不再需要手动维护引用计数,避免了"过早释放"或"重复释放"的错误。 |
| std::weak_ptr | 弱引用。配合 shared_ptr 使用,不增加引用计数。 | 循环引用死锁:解决了两个 shared_ptr 互相指向对方,导致引用计数永远无法归零、内存无法释放的死锁问题。 |
C++11 正式将 C++98 中的 std::auto_ptr 标记为"弃用"(Deprecated)。
|-------------|----------------------------|----------------------------------------------------------------------------------------------------------------------|
| 改动点 | 核心特性 | 解决的痛点 |
| 弃用 auto_ptr | 建议开发者停止使用,转而使用 unique_ptr。 | 所有权转移的隐蔽性:auto_ptr 在拷贝时(a = b)会偷偷转移所有权,导致原指针 b 变为空(悬空)。这种行为非常隐蔽且危险,容易导致程序崩溃。unique_ptr 强制要求使用 std::move 显式转移,让意图更清晰。 |
2、C++11 引入了一个新的关键字 nullptr(类型为 std::nullptr_t)来专门表示空指针,替代了传统的 NULL 宏。
|---------|------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|
| 改动点 | 核心特性 | 解决的痛点 |
| 替代 NULL | nullptr 是一个纯指针字面量,不能隐式转换为整数。 | 函数重载歧义:在 C++98 中,NULL 通常被定义为 0。如果同时存在 func(int) 和 func(int*) 两个重载函数,调用 func(NULL) 时,编译器会优先匹配 func(int),导致逻辑错误。使用 func(nullptr) 则能准确匹配指针版本。 |
| 类型安全 | 只能赋值给指针类型。 | 类型混淆:防止了将空指针误当作整数 0 进行算术运算或比较,提高了代码的类型安全性。 |
五、作用域的优化修改
C++11 标准对作用域(Scope)的优化主要体现在更精细的命名控制、更灵活的代码组织以及更安全的资源管理上。虽然 C++11 没有改变块作用域({})的基本定义,但它引入的新特性极大地优化了我们在作用域内定义和使用变量的方式。
1.强作用域枚举 (enum class)
|---------------|--------------------------------------------|----------------------------------------------------------------------|
| 改动 | 作用 | 解决的问题 |
| 引入 enum class | 将枚举成员限制在枚举类型的作用域内,访问时必须使用 EnumType::Value。 | 解决命名冲突(作用域污染):防止不同枚举类型中相同的成员名(如 Color::Red 和 TrafficLight::Red)发生冲突。 |
| 强类型限制 | 枚举值不会隐式转换为整数。 | 解决类型安全问题:防止将整数意外赋值给枚举变量,或错误地比较不同枚举类型的值。 |
六、C++11中的类的变化有哪些
|--------|-------------------------------------------|---------------------------------------------------------------------------------|
| 修改 | 作用 | 解决的问题 |
| 委托构造函数 | 允许一个构造函数在其初始化列表中调用同一个类中的另一个构造函数。 | 解决代码重复:在 C++98 中,多个构造函数之间常常需要重复编写相同的初始化逻辑。委托构造让代码更 DRY (Don't Repeat Yourself)。 |
| 继承构造函数 | 允许派生类通过 using Base::Base; 语法,直接继承基类的构造函数。 | 简化派生类定义:当派生类不需要额外的初始化逻辑时,无需再手动为每个基类构造函数编写一个对应的"透传"构造函数。 |
|---------|-------------------------------------|----------------------------------------------------------|
| 修改 | 作用 | 解决的问题 |
| 类内成员初始化 | 允许在声明成员变量时直接赋予默认值(如 int _age = 0;)。 | 简化构造函数:为成员变量提供默认值不再强制依赖构造函数的初始化列表,使默认值一目了然,并减少了构造函数的代码量。 |
|-----------|-----------------------------|------------------------------------------------------------------------|
| 修改 | 作用 | 解决的问题 |
| = default | 显式要求编译器生成某个默认成员函数(如默认构造函数)。 | 恢复默认行为:当用户定义了其他构造函数(如带参构造)后,编译器将不再生成默认构造函数。使用 = default 可以手动将其"加回来"。 |
| = delete | 显式禁止编译器生成某个函数,或禁止使用某个函数。 | 禁用特定功能:比 C++98 中将函数声明为 private 且不实现的方法更直观、更安全。常用于禁止拷贝(如单例模式)或禁用某些类型转换。 |
|----------|------------------------------------------|-------------------------------------------------------------------------|
| 修改 | 作用 | 解决的问题 |
| override | 在派生类成员函数后使用,显式声明该函数旨在重写基类的虚函数。 | 防止意外未重写:编译器会检查基类是否存在匹配的虚函数。如果函数签名不一致或基类函数被修改,编译器会报错,避免了因拼写错误等导致的逻辑 bug。 |
| final | 可用于修饰类或虚函数。修饰类表示该类不可被继承;修饰虚函数表示该函数不可被重写。 | 明确设计意图:防止关键类被意外继承,或核心虚函数被意外重写,使类的设计意图更加清晰,增强了代码的健壮性。 |
|---------|---------------------------------------|--------------------------------------------------------------------------|
| 修改 | 作用 | 解决的问题 |
| 移动构造函数 | 允许将一个临时对象(右值)的资源"转移"给新对象,而不是进行昂贵的深拷贝。 | 提升性能:解决了 C++98 中不必要的深拷贝开销,对于管理动态资源(如 std::vector, std::string)的类,性能提升巨大。 |
| 移动赋值运算符 | 与移动构造类似,但用于已存在的对象。 | 提升赋值效率:避免了在赋值操作中先析构再拷贝的开销,直接接管资源。 |
|-------------------|-------------------------|-------------------------------------------------------|
| 修改 | 作用 | 解决的问题 |
| alignas / alignof | 提供了标准化的方式来查询和指定类型的对齐要求。 | 底层控制:为需要特定内存对齐的场景(如 SIMD 指令、硬件交互)提供了可移植的解决方案。 |
| 友元模板 | 简化了将类模板的所有实例声明为友元的语法。 | 简化模板友元:在 C++98 中,声明一个模板类的所有实例为友元非常繁琐,C++11 的语法更加简洁直观。 |
七、模版和STL的优化
|----------------|--------------------------------------------------------------------------------|---------------------------------------------------------------|
| 修改特性 | 描述 | 解决的问题/优势 |
| 右尖括号 >> 自动识别 | 编译器现在能自动识别嵌套模板结束符 >>,不再需要写成 > >。 | 解决了 C++98 中必须手动加空格的繁琐和易错问题,代码更整洁。 |
| 模板别名 (using) | 允许使用 using 关键字为模板定义别名(如 template<typename T> using Vec = std::vector<T>;)。 | 解决了 typedef 无法直接对模板进行重命名的限制,极大地简化了复杂类型的声明。 |
| 函数模板默认参数 | 允许为函数模板的参数指定默认类型(如 template<typename T=int>)。 | 增加了模板定义的灵活性,使调用更加简洁。 |
| 可变参数模板 | 允许模板接受任意数量、任意类型的参数(使用 ... 语法)。 | 实现了类型安全的变长参数函数(如 printf 的替代品),是 std::tuple 和 std::bind 的实现基础。 |
|-----------------------------------------|-----------------------------|---------------------------------------|
| 修改特性 | 描述 | 解决的问题/优势 |
| 新增 std::array | 封装了固定大小的原生数组,支持迭代器和 STL 算法。 | 提供了比原生数组更安全、更现代化的替代品,且不会退化为指针。 |
| 新增 std::forward_list | 单向链表容器。 | 相比双向链表 std::list,节省了一个指针的空间,内存效率更高。 |
| 新增哈希容器 (unordered_map, unordered_set 等) | 基于哈希表实现的关联容器。 | 提供了 O(1) 的平均查找复杂度,比基于红黑树的 map/set 更快。 |
| std::vector 的 data() 成员 | 增加了 data() 成员函数,直接返回底层数组指针。 | 方便与需要原生指针的 C 风格 API 进行交互,无需手动取地址。 |
|----------------------------------------------|-----------------------------------------------------|-----------------------------------------------|
| 修改特性 | 描述 | 解决的问题/优势 |
| emplace 系列方法 (如 emplace_back, emplace_front) | 允许在容器内部原地构造对象,直接传递参数给构造函数。 | 避免了 push_back 或 insert 产生的临时对象拷贝或移动开销,显著提升性能。 |
| 移动语义支持 | 容器元素现在可以利用移动构造函数(如 std::vector<std::unique_ptr>)。 | 使得容器可以存储"不可拷贝但可移动"的类型,极大地扩展了容器的适用范围。 |
| 列表初始化 (std::initializer_list) | 支持使用花括号 {} 直接初始化容器(如 v = {1, 2, 3};)。 | 使得容器的初始化像数组一样直观、简洁,代码可读性更高。 |
八、移动语义
C++11 引入的移动语义(Move Semantics),是其最重要、最核心的特性之一。它从根本上改变了 C++ 处理资源(尤其是堆内存)的方式,旨在通过"转移"而非"复制"来大幅提升程序性能。
简单来说,移动语义的核心思想是"资源窃取"。当一个对象(通常是临时对象或即将被销毁的对象)的资源不再需要时,我们可以直接"拿走"它的资源,而不是花费高昂的代价去复制一份。
🤔 为什么需要移动语义?
在 C++11 之前,C++ 主要依赖拷贝语义。当你将一个对象赋值给另一个对象时,会调用拷贝构造函数,执行一次"深拷贝"。
⚙️ 移动语义的核心机制
移动语义的实现依赖于三个关键要素:右值引用、移动构造函数/赋值运算符和 std::move。
1. 右值引用 (R-value Reference)
右值引用是移动语义的语法基础,用 && 表示。
左值 (lvalue):有名字、可以取地址的持久化对象,如变量 int a = 10; 中的 a。
右值 (rvalue):通常是临时对象、字面量或表达式结果,没有名字,用完即销毁,如 a + 10 的结果。
右值引用 T&& 可以绑定到一个右值(临时对象),从而让我们有机会"窃取"它的资源。
2. 移动构造函数与移动赋值运算符
为了让类支持移动,你需要为它定义这两个特殊的成员函数。
移动构造函数:当一个新对象从一个右值(临时对象)构造时调用。
移动赋值运算符:当一个已存在的对象被一个右值(临时对象)赋值时调用。
3 、拷贝和移动的对比
|----------|----------------------------|------------|
| 操作类型 | 资源行为 | 性能开销 |
| 拷贝构造/赋值 | 分配新内存,并复制所有数据(深拷贝) | 高(与数据量成正比) |
| 移动构造/赋值 | 转移资源所有权(通常是拷贝几个指针),并将源对象置空 | 极低(常数时间) |
九、Lamba表达式
|----------|----------------------------------------|----------------------------------------|
| 特性维度 | 函数指针 | Lambda 表达式 |
| 本质 | 指向代码内存地址的指针(C 语言遗产)。 | 编译器生成的匿名类对象(闭包),通常重载了 operator()。 |
| 状态保持 | 无状态。无法直接携带上下文数据,只能依赖全局变量或静态变量。 | 有状态。可以通过"捕获列表"[] 捕获外部作用域的变量(按值或按引用)。 |
| 性能优化 | 间接调用,编译器通常难以进行内联优化,有函数调用开销。 | 编译器可以直接看到代码实现,极易进行内联优化,性能通常等同于手写代码。 |
| 类型系统 | 类型由函数签名决定(如 int(*)(int)),相同签名的指针类型兼容。 | 每个 Lambda 都有一个唯一的、编译器生成的闭包类型,互不兼容。 |
| 语法便捷性 | 声明繁琐(如 void (*func)(int)),定义与使用分离。 | 定义即用,语法紧凑,逻辑可以写在调用点附近。 |
Lambda 表达式解决的问题
上下文丢失(闭包问题):
痛点:在 C++98 中,如果想让一个回调函数使用局部变量,必须将其设为全局变量或通过复杂的 void* 用户数据指针传递,既不安全也不直观。
解决:Lambda 通过捕获列表(如 [=] 或 [&]),让函数逻辑可以直接"看见"并使用定义位置周围的变量,实现了真正的闭包。
代码碎片化:
痛点:为了一个简单的排序逻辑或回调,不得不去写一个独立的 bool Compare(...) 函数,导致代码阅读时需要上下跳转。
解决:Lambda 允许将短小的逻辑直接写在算法调用处(如 std::sort 内部),极大地提高了代码的可读性和内聚性。
函数指针解决的问题(
十、包装器
在 C++11 中,所谓的"包装器"主要指的是定义在 <functional> 头文件中的工具,其中最核心的两个是 std::function 和 std::bind。
|---------------|-----------|-----------------------------------|-------------------------------------------|
| 包装器 | 核心关键词 | 主要功能 | 典型应用场景 |
| std::function | 统一类型 | 把各种可调用的东西(函数、Lambda、仿函数)装进同一个盒子里。 | 定义回调函数接口、在容器中存储不同的策略函数、作为类成员变量。 |
| std::bind | 绑定参数 | 把函数的某些参数固定住,或者换个位置,生成一个新的函数。 | 将成员函数当作普通函数使用、为通用函数预设默认参数(如绑定事件处理器时的上下文)。 |
十一、可变参数
C++11 引入的可变参数模板(Variadic Templates)是泛型编程领域的一次重大飞跃。
简单来说,它允许模板接受任意数量、任意类型的参数。在 C++11 之前,模板通常只能处理固定数量的参数(比如 template<typename T1, typename T2> 只能传两个类型),而可变参数模板打破了这个限制。
它的核心作用可以概括为:实现了类型安全的"变长参数"泛型编程。
可变参数模板完美解决了这两个问题:
- 类型安全:编译器会推导每一个参数的具体类型,类型不匹配会直接报错。
- 代码简洁:只需写一套模板,即可处理 0 到 N 个参数。
- 支持任意类型:无论是 int、std::string 还是自定义类,统统支持。
十二、并行编程
C++11 对并发编程进行了里程碑式的重构。在此之前,C++ 标准并未定义多线程模型,开发者不得不依赖平台特定的 API(如 POSIX 的 pthread 或 Windows API),这导致了代码难以移植且极易出错。
C++11 将多线程支持直接纳入了语言标准库(<thread>, <mutex>, <atomic>, <future> 等),解决了跨平台兼容性差、资源管理困难(如死锁、资源泄漏)以及多核利用率低等核心问题。
以下是 C++11 在并发编程方面的核心修改及其解决的问题列表:
-
线程管理的标准化 (std::thread)
-
互斥与锁的 RAII 化 (std::mutex & Lock Guards)
-
原子操作与内存模型 (std::atomic)
-
线程间同步与通信 (std::future, std::async, std::promise)
|----------------------------------|-----------------------|-----------------------|
| 核心组件 | 关键特性 | 解决的核心痛点 |
| std::thread | 统一线程接口、强制 Join/Detach | 平台依赖性强、线程资源泄漏、参数传递困难 |
| std::mutex | 基础互斥锁 | 多线程数据竞争 |
| std::lock_guard std::unique_lock | RAII 锁机制 (自动加解锁) | 死锁、忘记解锁、异常导致锁未释放 |
| std::atomic | 硬件级原子指令、内存序控制 | 锁的开销大(性能问题)、数据竞争、指令重排 |
| std::future std::async | 异步任务、未来结果获取 | 获取线程返回值繁琐、异步任务管理复杂 |
| std::condition_variable | 等待/通知机制 | 线程间通信困难、忙等待浪费 CPU |
十三、新增的库
- 并发与多线程支持 (最重磅的更新)
|------------------------|-----------------------------------------------|-------------------------------------------|
| 新增头文件 | 核心组件 | 作用与解决的问题 |
| <thread> | std::thread | 线程管理:提供了创建和管理线程的标准类,解决了跨平台线程开发难题。 |
| <mutex> | std::mutex, std::lock_guard, std::unique_lock | 互斥与锁:提供了互斥量和 RAII 风格的锁包装器,防止死锁和数据竞争。 |
| <atomic> | std::atomic | 原子操作:提供无需锁的原子数据类型,利用硬件指令保证多线程安全,性能极高。 |
| <future> | std::future, std::promise, std::async | 异步任务:用于获取异步操作的结果,简化了多线程间的数据传递和同步。 |
| <condition_variable> | std::condition_variable | 条件变量:用于线程间的"等待-通知"机制,避免忙等待(Busy Waiting)。 |
- 新容器与数据结构
|-------------------------------------|---------------------------------------|------------------------------------------------|
| 新增头文件 | 核心组件 | 作用与解决的问题 |
| <array> | std::array | 固定数组:封装了原生数组,支持迭代器和 STL 算法,且不会退化为指针,更安全。 |
| <forward_list> | std::forward_list | 单向链表:相比 std::list(双向链表),少存一个指针,内存占用更小,插入删除更高效。 |
| <unordered_map> <unordered_set> | std::unordered_map std::unordered_set | 哈希容器:基于哈希表实现,查找复杂度为 O(1),比基于红黑树的 map/set 更快。 |
| <tuple> | std::tuple | 元组:可以存储不同类型元素的固定大小集合,是泛型编程的强力工具。 |
- 智能指针与内存管理
|-------------------|-----------------------------------------------|-----------------------------------------|
| 新增头文件 | 核心组件 | 作用与解决的问题 |
| <memory> (新增部分) | std::unique_ptr std::shared_ptr std::weak_ptr | 自动内存管理:彻底解决了内存泄漏和悬挂指针问题,实现了资源所有权的自动化管理。 |
- 通用工具与函数对象
|-----------------------|--------------------------------|--------------------------------------------------------------|
| 新增头文件 | 核心组件 | 作用与解决的问题 |
| <functional> (新增部分) | std::function std::bind | 可调用对象包装:function 统一了所有可调用对象(函数、Lambda、仿函数)的类型;bind 用于绑定函数参数。 |
| <initializer_list> | std::initializer_list | 列表初始化:支持使用花括号 {} 进行统一的初始化语法(如 vector v = {1, 2, 3};)。 |
| <type_traits> | std::is_same, std::enable_if 等 | 类型元编程:在编译期查询和修改类型属性,是编写高级模板库的基础。 |
| <ratio> | std::ratio | 编译期有理数运算:主要用于支持 <chrono> 的时间计算,防止溢出。 |
- 时间与随机数
|------------|--------------------------------------------------------|--------------------------------------------------------|
| 新增头文件 | 核心组件 | 作用与解决的问题 |
| <chrono> | std::chrono | 时间库:提供了高精度的时间度量、时钟(系统时钟、稳时钟)和时间点计算,替代了 C 语言粗糙的 time_t。 |
| <random> | std::mt19937 (随机引擎) std::uniform_int_distribution (分布) | 高质量随机数:解决了 rand() 质量差、周期短、分布不均匀的问题,提供了专业的随机数生成机制。 |
- 字符串与正则
|-------------------|-------------------------------|-------------------------------------------------|
| 新增头文件 | 核心组件 | 作用与解决的问题 |
| <regex> | std::regex, std::regex_search | 正则表达式:C++ 标准库终于原生支持了正则表达式,无需依赖 Boost 或第三方库。 |
| <string> (新增部分) | std::to_string, std::stoi | 数值转换:提供了字符串与数值互转的便捷接口,替代了 C 语言的 sprintf 或 atoi。 |