什么是 C++11 标准?
C++11 是 C++ 语言的一个重大更新标准,它于 2011 年发布,因此被命名为 C++11。这个标准为 C++ 语言引入了许多新的语言特性和库改进,使得程序设计更加简洁、高效,并且更易于维护。C++11 的设计目标是改进 C++ 的可用性、性能和多样性,同时确保向后兼容 C++98/03 标准。
C++11 标准中的主要新特性
C++11 标准引入了以下几大类的新特性:
-
语言扩展
- 类型推导 (
auto
) - 匿名函数(Lambda 表达式)
- 右值引用和移动语义
- 新的循环语法(基于范围的 for 循环)
- 列表初始化
- 强类型枚举 (enum class)
- 静态断言(static_assert)
- 委托构造函数
- 继承构造函数
- 变长模板参数
- 类型推导 (
-
标准库扩展
- 智能指针(
std::unique_ptr
、std::shared_ptr
) - 多线程支持 (
std::thread
、std::mutex
) - 原始字面量
- long long 类型
- 空指针 nullptr
- constexpr 关键字
- final 和 override 关键字
- 模板的优化
- 自动推导 decltype
- using 的使用
- 智能指针(
-
编译器功能改进
- 静态断言 (
static_assert
) - 强制枚举 (
enum class
) noexcept
规范
- 静态断言 (
接下来将全面深入讲解 C++11 标准中的主要新特性,并通过代码示例来帮助理解。
语言扩展
1. 类型推导 (auto)
概念auto
关键字允许编译器根据初始化表达式自动推导变量的类型,减少显式类型声明的冗余,提升代码的可读性。
示例
#include <iostream>
#include <vector>int main() {auto x = 42; // x 被推导为 int 类型auto y = 3.14; // y 被推导为 double 类型std::vector<int> vec = {1, 2, 3, 4, 5};for (auto num : vec) {std::cout << num << " "; // 输出 1 2 3 4 5}return 0;
}
说明
auto
减少了冗余的类型声明,编译器根据初始值的类型推导出变量类型。特别适用于复杂的 STL 容器类型。
2. Lambda 表达式
概念
Lambda 表达式是 C++11 引入的一种匿名函数机制,允许在代码中定义简洁的内联函数,常用于回调函数或 STL 算法中。
示例
#include <iostream>
#include <algorithm>
#include <vector>int main() {std::vector<int> nums = {1, 2, 3, 4, 5};// 使用 Lambda 表达式打印每个元素std::for_each(nums.begin(), nums.end(), [](int num) {std::cout << num << " "; // 输出 1 2 3 4 5});// Lambda 表达式作为比较函数auto sum = [](int a, int b) { return a + b; };std::cout << "\nSum: " << sum(5, 3) << std::endl; // 输出 8return 0;
}
说明
Lambda 表达式语法:捕获列表 -> 返回类型 { 函数体 }
[] 是捕获列表,可以用来捕获外部变量,[] 表示不捕获任何变量,[&] 表示按引用捕获所有外部变量,[=] 表示按值捕获所有外部变量。Lambda 表达式常用于回调、函数式编程和 STL 算法。
3. 右值引用与移动语义
概念
C++11 引入了右值引用 (&&) 和移动语义,使得对象的资源可以通过移动操作高效地转移,而不是复制,从而显著提升性能。
示例
#include <iostream>
#include <vector>class Resource {
public:int* data;Resource() {data = new int[1000]; // 动态分配资源std::cout << "Resource acquired" << std::endl;}~Resource() {delete[] data; // 释放资源std::cout << "Resource destroyed" << std::endl;}// 移动构造函数Resource(Resource&& other) noexcept : data(other.data) {other.data = nullptr; // 让源对象不再拥有资源std::cout << "Resource moved" << std::endl;}// 禁用复制构造函数Resource(const Resource&) = delete;
};int main() {std::vector<Resource> vec;vec.push_back(Resource()); // 触发移动构造函数return 0;
}
说明
右值引用用于标识临时对象(右值)的引用,允许我们“移动”资源而不是复制。在例子中,Resource
类的移动构造函数允许高效地转移资源所有权,而无需重新分配和复制数据。noexcept
关键字表示该操作不会抛出异常,有助于优化移动操作。
4. 基于范围的 for 循环
概念
基于范围的 for 循环使得对容器元素的迭代更加简洁和直观。
示例
#include <iostream>
#include <vector>int main() {std::vector<int> nums = {1, 2, 3, 4, 5};// 使用基于范围的 for 循环迭代for (int num : nums) {std::cout << num << " "; // 输出 1 2 3 4 5}return 0;
}
说明
基于范围的 for 循环使得迭代容器更加简洁,无需显式使用迭代器。也可以使用auto
来自动推导元素类型,进一步简化代码。
5. 列表初始化
概念
C++11 引入了统一的列表初始化语法,可以用 {}
进行初始化。这种初始化方式更加简洁、类型安全,适用于普通类型、容器、结构体和类对象的初始化。
示例
#include <iostream>
#include <vector>struct Point {int x, y;
};int main() {// 用列表初始化基本类型int arr[3] = {1, 2, 3};// 初始化容器std::vector<int> vec = {1, 2, 3, 4};// 初始化结构体Point p = {10, 20};// 输出std::cout << "Point: (" << p.x << ", " << p.y << ")" << std::endl;return 0;
}
说明
列表初始化可以防止窄化转换(即防止不安全的类型转换),例如从double
转换为int
。列表初始化统一了不同类型的初始化方式,使得代码更加整洁。
6. 强类型枚举 (enum class)
概念
C++11 引入了强类型枚举 enum class
,与传统枚举相比,它具有更好的类型安全性。传统的 enum
会将枚举常量提升为整数,而 enum class
则不会自动转换为整数,避免了类型不安全的隐患。
示例
#include <iostream>enum class Color { Red, Green, Blue };
enum class Fruit { Apple, Orange, Banana };int main() {Color color = Color::Red;Fruit fruit = Fruit::Apple;// 错误:不能隐式转换 enum class 类型// int colorValue = color;// 正确:显式转换为整数int colorValue = static_cast<int>(color);std::cout << "Color value: " << colorValue << std::endl; // 输出 0return 0;
}
说明
enum class
的枚举常量必须使用作用域限定符(如Color::Red
)来访问,避免了命名冲突。强类型枚举不隐式转换为整数,增加了类型安全性。
7. 静态断言 (static_assert)
概念static_assert
是 C++11 引入的编译时断言机制,用于在编译时验证某些条件。它主要用于模板编程或编译期常量表达式验证。
示例
#include <iostream>template<typename T>
void checkType() {static_assert(sizeof(T) == 4, "Type size must be 4 bytes");
}int main() {checkType<int>(); // 正确:int 通常是 4 字节// checkType<double>(); // 错误:double 通常不是 4 字节,编译期断言失败return 0;
}
说明
static_assert
用于编译时检查条件,如果条件不满足,编译器会抛出错误并输出自定义的错误信息。它常用于模板编程中,确保模板参数符合某些要求。
8. 委托构造函数
概念
C++11 允许在一个构造函数中调用另一个构造函数,减少代码重复,简化构造函数的编写。
示例
#include <iostream>class MyClass {
public:MyClass(int value) : value_(value) {std::cout << "Constructor with int" << std::endl;}MyClass() : MyClass(0) { // 委托构造函数std::cout << "Default constructor" << std::endl;}private:int value_;
};int main() {MyClass obj1; // 调用默认构造函数MyClass obj2(10); // 调用带参数的构造函数return 0;
}
说明
通过委托构造函数,可以在一个构造函数中调用其他构造函数,从而减少代码重复,提高代码质量和可维护性。
9. 继承构造函数
概念
派生类可以直接使用基类的构造函数,简化了派生类构造函数的编写。
示例
#include <iostream>class Base {
public:Base(int value) : value_(value) {std::cout << "Base constructor" << std::endl;}private:int value_;
};class Derived : public Base {
public:using Base::Base; // 继承基类构造函数
};int main() {Derived obj(10); // 调用基类构造函数return 0;
}
说明
通过使用using Base::Base;
,派生类可以继承基类的构造函数,从而简化构造函数的编写,避免代码冗余。
10. 变长模板参数
概念
C++11 支持变长模板参数(variadic templates),使得模板可以接受任意数量和类型的参数。这极大地增强了模板的灵活性和通用性。
示例
#include <iostream>template<typename T>
void print(T t) {std::cout << t << std::endl;
}template<typename T, typename... Args>
void print(T t, Args... args) {std::cout << t << " ";print(args...); // 递归调用,继续处理剩余的参数
}int main() {print(1, 2.5, "Hello", 'A'); // 输出 1 2.5 Hello Areturn 0;
}
说明
...
是变长模板的语法,用于接收不定数量的参数。可以通过递归调用展开参数包,每次处理一个参数。这种特性使得模板编程更加灵活,适用于需要处理多个类型和数量参数的场景。
标准库扩展
1. 智能指针
C++11 引入了智能指针来自动管理资源,避免内存泄漏和手动管理动态内存的复杂性。
1.1 std::unique_ptr
概念std::unique_ptr
是独占所有权的智能指针,确保一个指针对象只有一个所有者,当它超出作用域时自动释放资源。
示例
#include <iostream>
#include <memory>int main() {std::unique_ptr<int> p1(new int(10)); // 创建 unique_ptrstd::cout << *p1 << std::endl; // 输出 10// 转移所有权std::unique_ptr<int> p2 = std::move(p1);std::cout << (p1 ? "p1 not null" : "p1 null") << std::endl; // 输出 "p1 null"return 0;
}
说明
std::unique_ptr
不允许拷贝,但可以通过std::move
转移所有权。离开作用域时会自动释放资源,确保内存不会泄漏。
1.2 std::shared_ptr
概念std::shared_ptr
是共享所有权的智能指针,多个指针可以指向相同的对象,当所有指针都不再指向该对象时,资源才会被释放。
示例
#include <iostream>
#include <memory>int main() {std::shared_ptr<int> sp1(new int(20));std::shared_ptr<int> sp2 = sp1; // 共享所有权std::cout << "sp1 use count: " << sp1.use_count() << std::endl; // 输出 2std::cout << "sp2 use count: " << sp2.use_count() << std::endl; // 输出 2return 0;
}
说明
std::shared_ptr
通过引用计数来管理对象的生命周期,当引用计数为 0 时,资源会被释放。use_count()
函数返回当前有多少个shared_ptr
指向同一个对象。
2. 多线程支持
C++11 提供了原生的多线程库,使得编写跨平台的多线程程序变得更加简洁。主要包括 std::thread
用于创建线程,std::mutex
用于保护共享数据,以及 std::lock_guard
和 std::unique_lock
提供简化的锁管理。
2.1 std::thread
概念std::thread
是 C++11 引入的用于创建和管理线程的类。它允许并行执行任务,提升程序的性能。
示例
#include <iostream>
#include <thread>// 线程执行的函数
void threadFunction() {std::cout << "Hello from thread!" << std::endl;
}int main() {// 创建线程并执行std::thread t(threadFunction);// 等待线程完成t.join();return 0;
}
说明
std::thread
构造函数接受一个可调用对象(如函数指针、Lambda 表达式、函数对象等)。join()
用于等待线程结束。如果不调用join()
或detach()
,则程序在析构std::thread
对象时会崩溃。
2.2 std::mutex 和 std::lock_guard
概念std::mutex
用于多线程环境中保护共享数据,防止多个线程同时访问同一个资源而导致数据竞争。std::lock_guard
是一种用于管理 mutex
锁的 RAII 风格的工具,简化了加锁和解锁的过程。
示例
#include <iostream>
#include <thread>
#include <mutex>std::mutex mtx; // 互斥量用于保护共享数据void print_message(const std::string& message) {std::lock_guard<std::mutex> lock(mtx); // 自动加锁,作用域结束时自动解锁std::cout << message << std::endl;
}int main() {std::thread t1(print_message, "Hello from thread 1");std::thread t2(print_message, "Hello from thread 2");t1.join();t2.join();return 0;
}
说明
std::mutex
可以保护临界区,防止多个线程同时访问同一数据。std::lock_guard
自动管理锁的获取和释放,避免手动lock()
和unlock()
时出错。
3. 原始字面量 (Raw String Literals)
概念
C++11 引入了原始字符串字面量,可以让开发者更加方便地表示包含特殊字符的字符串,避免转义的复杂性。
示例
#include <iostream>int main() {const char* raw_str = R"(Line 1
Line 2
Line 3)";std::cout << raw_str << std::endl;return 0;
}
说明
原始字符串字面量使用R"(
开头和)"
结尾,可以包含换行符和特殊字符而无需转义,方便表示复杂字符串。
4. long long 类型
概念
C++11 确保 long long
至少为 64 位,支持 LL
和 ll
后缀用于定义长整型,支持 unsigned long long
及其后缀变体。
示例
#include <iostream>int main() {long long big_num = 9223372036854775807LL;unsigned long long ubig_num = 18446744073709551615ULL;std::cout << "Long long: " << big_num << std::endl;std::cout << "Unsigned long long: " << ubig_num << std::endl;return 0;
}
说明
long long
类型用于表示大整数,确保其至少为 64 位。LL
和ULL
后缀用于声明long long
和unsigned long long
常量。
5. 空指针 nullptr
概念
引入 nullptr
取代 NULL
,提供了一种类型安全的方式来表示空指针,避免了重载函数时的模糊性。
示例
#include <iostream>void f(int) {std::cout << "Function with int parameter" << std::endl;
}void f(char*) {std::cout << "Function with char* parameter" << std::endl;
}int main() {f(0); // 调用 f(int)f(nullptr); // 调用 f(char*)return 0;
}
说明
nullptr
是类型安全的空指针常量,避免了NULL
在重载函数时的模糊性。使用nullptr
可以让代码更加清晰和安全。
6. constexpr 关键字
概念constexpr
用于定义常量表达式,在编译时计算值,提高了性能。与 const
不同,constexpr
确保表达式在编译时求值。
示例
#include <iostream>// constexpr 函数必须简单,且能在编译时计算结果
constexpr int square(int x) {return x * x;
}int main() {constexpr int val = square(5); // 在编译时计算出 val = 25int arr[val]; // 数组大小为 25std::cout << "Square of 5 is: " << val << std::endl; // 输出 Square of 5 is: 25return 0;
}
说明
constexpr
可以用于函数和变量。它要求函数体必须非常简单,以确保在编译时能完全执行。在编译时计算的值可以用于数组大小、模板参数等。
7. final 和 override 关键字
概念final
用于防止类被继承或虚函数被重写,override
用于确保派生类的方法确实是重写基类的虚函数。
示例
#include <iostream>class Base {
public:virtual void show() const {std::cout << "Base show" << std::endl;}
};class Derived : public Base {
public:void show() const override { // 确保是重写基类的方法std::cout << "Derived show" << std::endl;}void display() final { // 防止进一步重写std::cout << "Derived display" << std::endl;}
};class FurtherDerived : public Derived {
public:// void display() override; // 编译错误:不能重写 final 方法
};int main() {Base* b = new Derived();b->show(); // 调用 Derived 的 show 方法delete b;return 0;
}
说明
override
关键字确保派生类的方法确实是重写基类的虚函数,否则编译器会报错。final
关键字防止类被继承或虚函数被进一步重写。
8. 模板的优化
概念
C++11 对模板的使用进行了许多优化,使得模板编程更加灵活高效。例如,变长模板参数、decltype
、auto
等新特性都大大增强了模板的表达能力。
示例
#include <iostream>template<typename T, typename U>
auto add(T t, U u) -> decltype(t + u) {return t + u;
}int main() {std::cout << add(1, 2.5) << std::endl; // 输出 3.5std::cout << add(std::string("Hello "), "World!") << std::endl; // 输出 Hello World!return 0;
}
说明
通过使用decltype
和auto
,模板函数可以更加灵活地处理不同类型的参数,并自动推导返回类型,提高了代码的通用性和可维护性。
9. 自动推导 decltype
概念decltype
用于在编译时获取表达式的类型,不会计算表达式的值。
示例
#include <iostream>int main() {int x = 10;decltype(x) y = x + 5; // y 的类型是 intstd::cout << "y = " << y << std::endl; // 输出 y = 15return 0;
}
说明
decltype
关键字在编译时推导出表达式的类型,可以用于声明变量的类型,而不需要显式指定类型。
10. using 的使用
概念using
关键字提供了比 typedef
更加清晰的别名声明方式,尤其在定义模板时尤为方便。
示例
#include <iostream>
#include <vector>// 使用 typedef 定义别名
typedef std::vector<int> IntVector;// 使用 using 定义别名
using IntList = std::vector<int>;int main() {IntVector vec = {1, 2, 3};IntList lst = {4, 5, 6};for (auto i : vec) {std::cout << i << " "; // 输出 1 2 3}std::cout << std::endl;for (auto i : lst) {std::cout << i << " "; // 输出 4 5 6}std::cout << std::endl;return 0;
}
说明
using
关键字可以替代typedef
,定义类型别名时更加直观和清晰,特别是在定义模板别名时非常有用。
编译器功能改进
1. 静态断言 (static_assert)
概念static_assert
是 C++11 引入的编译时断言机制,用于在编译时验证某些条件。它主要用于模板编程或编译期常量表达式验证。
示例
#include <iostream>template<typename T>
void checkType() {static_assert(sizeof(T) == 4, "Type size must be 4 bytes");
}int main() {checkType<int>(); // 正确:int 通常是 4 字节// checkType<double>(); // 错误:double 通常不是 4 字节,编译期断言失败return 0;
}
说明
static_assert
用于编译时检查条件,如果条件不满足,编译器会抛出错误并输出自定义的错误信息。它常用于模板编程中,确保模板参数符合某些要求。
2. 强制枚举 (enum class)
概念
C++11 引入了强类型枚举 enum class
,与传统枚举相比,它具有更好的类型安全性。传统的 enum
会将枚举常量提升为整数,而 enum class
则不会自动转换为整数,避免了类型不安全的隐患。
示例
#include <iostream>enum class Color { Red, Green, Blue };
enum class Fruit { Apple, Orange, Banana };int main() {Color color = Color::Red;Fruit fruit = Fruit::Apple;// 错误:不能隐式转换 enum class 类型// int colorValue = color;// 正确:显式转换为整数int colorValue = static_cast<int>(color);std::cout << "Color value: " << colorValue << std::endl; // 输出 0return 0;
}
说明
enum class
的枚举常量必须使用作用域限定符(如Color::Red
)来访问,避免了命名冲突。强类型枚举不隐式转换为整数,增加了类型安全性。
3. noexcept 规范
概念noexcept
关键字用于指明一个函数不会抛出异常,有助于编译器进行优化。
示例
#include <iostream>void func() noexcept {std::cout << "This function is noexcept" << std::endl;
}int main() {func();return 0;
}
说明
使用noexcept
关键字指明函数不会抛出异常,可以帮助编译器进行优化,并提供更多的安全保障。
小结
C++11 标准是对 C++ 语言的一个重要改进,带来了大量的新特性,提升了语言的可用性、表达能力和效率。通过对这些新特性的深入学习与实践,能够编写出更加简洁、高效、安全的代码。
- 类型推导 (auto) 简化了变量声明,减少了冗余。
- Lambda 表达式 提供了简洁的函数表达方式,提升了代码的灵活性。
- 右值引用与移动语义 优化了资源管理,提高了性能。
- 基于范围的 for 循环 提供了简洁的容器迭代方式。
- 列表初始化 提供了统一而简洁的初始化语法。
- 智能指针 提供了安全的内存管理机制,防止内存泄漏。
- 多线程支持 提供了跨平台的多线程编程能力。
- 强类型枚举 (enum class) 提供了更好的类型安全性。
- 静态断言 (static_assert) 提供了编译时的条件检查。
- constexpr 提供了编译时的常量表达式计算。
- final 和 override 提供了更好的代码控制和安全性。
- 变长模板参数 增强了模板的灵活性。
- decltype 和 using 提高了代码的可读性和可维护性。
通过学习和掌握这些新特性,开发者可以更好地利用 C++11 的强大功能编写出更加高效和稳定的代码。