学懂C++(六十):C++ 11、C++ 14、C++ 17、C++ 20新特性大总结(万字详解大全)
一、引言
随着计算机科学与技术的飞速发展,编程语言也在不断进化以满足日益增长的需求。C++是一门集高性能和灵活性于一身的编程语言,自1983年诞生以来不断演进,逐渐成为了众多领域的主流编程语言。为了进一步提升开发效率和代码质量,C++标准委员会不断引入新的标准和特性。从C++11开始,C++语言进入了一个快速发展的时期,随后推出的C++14、C++17、C++20进一步丰富了语言特性和标准库功能。本文将对各个版本的C++标准中的新特性进行详细总结,帮助开发者了解和掌握这些新特性,从而更好地应用于实际项目中。
二、新特性简介
C++11 标准简介
C++11(原名为C++0x)是C++语言的一次重大更新,被广泛认为是自C++98以来最重要的版本。C++11引入了大量的新特性和改进,使得C++更加现代化和易用。主要特性包括自动类型推导(auto
和 decltype
)、Lambda表达式、右值引用和移动语义、智能指针、多线程支持(std::thread
)以及范围 for
循环等。这些特性显著提升了开发效率、代码的可读性和性能。
C++14 标准简介
C++14是对C++11的一次增量更新,主要在C++11的基础上进行了改进和优化。C++14引入了一些新的语言特性和标准库增强,包括泛型Lambda、decltype(auto)
、变量模板、二进制字面量、展开运算符(...
)的增强以及 std::make_unique
。这些改进使得C++语言更加灵活和高效,同时也修正了一些C++11中的不足之处。
C++17 标准简介
C++17是C++语言的又一次重要更新,带来了许多新特性和标准库的扩展。新特性包括结构化绑定(Structured Bindings)、折叠表达式(Fold Expressions)、if
和 switch
初始化、std::optional
、std::variant
和 std::any
,以及并行STL算法和 std::filesystem
。此外,还引入了嵌套命名空间(Nested Namespaces)和 constexpr
的扩展。C++17显著增强了语言的表达能力和标准库的功能,使得开发更加便捷和高效。
C++20 标准简介
C++20是C++语言的一次重大里程碑,被誉为C++11之后最重要的更新版本。C++20引入了许多全新的语言特性和标准库扩展,包括概念(Concepts)、三方比较运算符(<=>)、范围库(Ranges)、协程(Coroutines)、模块(Modules)、计时器库增强(Calendar and Time Zone Library)、constinit
关键字、[[likely]]
和 [[unlikely]]
属性、以及 std::span
和 std::expected
。这些新特性进一步提升了C++的现代化水平,极大地增强了语言的灵活性和开发效率。
三、C++11 新特性详细总结
1. 自动类型推断 (auto)
auto
关键字允许编译器根据初始化表达式推断变量的类型,这使代码更简洁且更易维护。
示例:
auto x = 10; // x的类型推断为int
auto y = 3.14; // y的类型推断为double
std::vector<int> vec = {1, 2, 3};
for (auto iter = vec.begin(); iter != vec.end(); ++iter) {
std::cout << *iter << std::endl;
}
背后的原理:
编译器在编译时会通过类型推断机制来确定auto
变量的具体类型。auto
通常用于简化复杂类型声明,特别是在模板和迭代器中非常有用。
2. 范围for循环 (Range-based for loop)
范围for循环让数组和容器的迭代更加简单和直观。
示例:
std::vector<int> v = {1, 2, 3, 4, 5};
for (auto i : v) {
std::cout << i << std::endl;
}
背后的原理:
范围for循环其实是一个语法糖,它在编译时会被转换成传统的迭代器循环。它对所有支持begin()
和end()
函数的容器都有效。
3. 智能指针 (Smart Pointers)
智能指针(如 std::shared_ptr
和 std::unique_ptr
)管理资源的生命周期,防止内存泄漏。
示例:
std::shared_ptr<int> p1 = std::make_shared<int>(10);
std::unique_ptr<int> p2 = std::make_unique<int>(20);
背后的原理:
智能指针通过RAII(Resource Acquisition Is Initialization)机制管理资源。std::shared_ptr
使用引用计数来管理资源共享,std::unique_ptr
则是独占资源。
4. lambda表达式 (Lambda Expressions)
lambda表达式使得在局部创建匿名函数变得简单。
示例:
auto add = [](int a, int b) { return a + b; };
std::cout << add(2, 3) << std::endl; // 输出 5
背后的原理:
lambda表达式是内联定义的匿名函数,编译器会生成一个类,该类实现operator()
函数。捕获列表可以捕获外部作用域的变量。
5. 右值引用和移动语义 (Rvalue References and Move Semantics)
右值引用允许开发者实现移动语义,从而显著提高程序性能。
示例:
void foo(std::string&& str) {
std::string local = std::move(str);
}
背后的原理:
右值引用使得对象在内存中的“移动”而不是拷贝成为可能。std::move
函数将左值转换为右值引用,从而启用移动语义。
6. 标准线程库 (Thread Library)
C++11引入了一个跨平台的标准线程库。
示例:
std::thread t([] { std::cout << "Hello from thread!" << std::endl; });
t.join();
背后的原理:
标准线程库提供了跨平台的线程管理接口。std::thread
对象表示一个线程,并提供方法来启动和管理线程生命周期。
7. std::array 和 std::tuple
std::array
是一个定长的数组类,std::tuple
是一种可以存储多个不同类型值的固定大小容器。
示例:
std::array<int, 3> arr = {1, 2, 3};
auto t = std::make_tuple(1, "hello", 3.14);
背后的原理:
std::array
和std::tuple
提供了更安全和更灵活的容器类型。std::tuple
可以存储和访问不同类型的多个值,std::array
则提供了比传统C数组更安全的接口。
8. 强类型枚举(Strongly-typed Enums)
C++11引入了enum class
,提供了强类型枚举,避免了传统枚举的一些问题,如名称冲突和隐式转换。
示例:
enum class Color { Red, Green, Blue };
enum class TrafficLight { Red, Yellow, Green };
Color color = Color::Red;
TrafficLight light = TrafficLight::Green;
// 不会发生隐式转换错误
// if (color == light) { ... } // 编译错误
背后的原理:
enum class
在作用域内创建枚举值,并且不允许隐式转换为整数类型,这样可以提供更好的类型安全性。
9. 静态断言(Static Assert)
静态断言static_assert
用于在编译时检查条件,如果条件不满足则产生编译错误。
示例:
static_assert(sizeof(int) == 4, "Integers must be 4 bytes");
背后的原理:
static_assert
是在编译时进行断言检查,比运行时断言更高效。如果条件不成立,编译器会生成错误信息,从而防止生成无效代码。
10. nullptr
C++11引入了nullptr
,这是一个类型安全的空指针。
示例:
int* p = nullptr; // p是一个空指针
背后的原理:
nullptr
是一个特殊类型的常量,表示空指针。与传统的NULL
相比,nullptr
可以与非指针类型的重载函数进行区分,消除了歧义。
11. 委托构造函数(Delegating Constructors)
构造函数可以委托给同一类的另一个构造函数,从而减少代码重复。
示例:
class MyClass {
public:
MyClass(int a, int b) : MyClass(a) {
// other initialization
}
MyClass(int a) {
// initialization
}
};
背后的原理:
委托构造函数允许一个构造函数调用另一个构造函数,这样可以共享初始化代码,减少代码重复,增强类的可维护性。
12. 继承构造函数(Inherited Constructors)
继承构造函数允许派生类继承基类的构造函数。
示例:
class Base {
public:
Base(int x) {}
};
class Derived : public Base {
using Base::Base; // 继承Base的构造函数
};
背后的原理:
继承构造函数通过using
声明使得派生类可以继承基类的构造函数,从而简化构造函数的编写和维护。
13. 显式虚函数覆盖(Override)
使用override
关键字明确表示派生类的成员函数是覆盖基类的虚函数。
示例:
class Base {
public:
virtual void foo() {}
};
class Derived : public Base {
public:
void foo() override {
// implementation
}
};
背后的原理:
override
关键字是给编译器的一个提示,明确表明此函数是用来覆盖基类的虚函数。如果基类函数的签名改变,编译器会产生错误,防止意外重载。
14. 删除函数(Deleted Functions)
可以使用= delete
语法禁止某些函数的使用。
示例:
class NonCopyable {
public:
NonCopyable(const NonCopyable&) = delete; // 禁止拷贝构造函数
NonCopyable& operator=(const NonCopyable&) = delete; // 禁止拷贝赋值运算符
};
背后的原理:
= delete
语法用于显式禁用某些函数,从而在编译时禁止这些函数的使用,增强类的安全性和可维护性。
15. 默认函数(Defaulted Functions)
可以使用= default
语法显式声明默认的函数实现。
示例:
class DefaultConstructor {
public:
DefaultConstructor() = default; // 使用默认的构造函数
~DefaultConstructor() = default; // 使用默认的析构函数
};
背后的原理:
= default
语法用于显式请求编译器生成默认实现,这样可以避免手动定义默认行为,减少代码量和潜在错误。
16. 变长参数模板(Variadic Templates)
变长参数模板允许模板接受可变数量的模板参数。
示例:
template<typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl; // C++17引入的折叠表达式
}
背后的原理:
变长参数模板通过...
语法接受多个参数,编译器会展开这些参数并生成对应的代码。这在实现泛型函数和类时非常有用。
17. 统一的初始化语法(Uniform Initialization Syntax)
C++11引入了大括号初始化语法,使得初始化更一致。
示例:
int arr[] = {1, 2, 3};
std::vector<int> vec = {1, 2, 3};
MyClass obj = {1, 2, 3}; // 假设MyClass有合适的构造函数
背后的原理:
统一的初始化语法使用大括号进行初始化,支持数组、容器和类对象的初始化,使代码更一致、更易读。
18. 基于时间的随机数库(Random Number Generation)
C++11引入了新的随机数库,提供了更多的随机数生成器和分布类型。
示例:
#include <random>
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(1, 6);
int random_number = dis(gen); // 生成1到6之间的随机数
背后的原理:
新的随机数库提供了更灵活的随机数生成方式,包括多种随机数生成器(如std::mt19937
)和分布类型(如std::uniform_int_distribution
),满足不同场景的需求。
19. noexcept
规范
noexcept
规范用于声明函数不会抛出异常,如果函数抛出了异常会导致程序终止。
示例:
void foo() noexcept {
// this function is guaranteed not to throw exceptions
}
void bar() noexcept(false) {
// this function may throw exceptions
}
背后的原理:
noexcept
规范通过在编译时检查函数是否可能抛出异常,从而优化代码生成。如果 noexcept
函数抛出了异常,std::terminate
会被调用,终止程序执行。
20. 类型别名模板(Alias Templates)
类型别名模板提供了一种更简洁的模板类型定义方式。
示例:
template<typename T>
using Vec = std::vector<T>;
Vec<int> v; // 等价于 std::vector<int> v;
背后的原理:
类型别名模板通过 using
关键字定义新的类型别名,从而简化模板类型的使用,使代码更加简洁和可读。
21. 尾置返回类型(Trailing Return Types)
尾置返回类型使得复杂返回类型的函数声明更易读。
示例:
auto add(int a, int b) -> int {
return a + b;
}
背后的原理:
尾置返回类型将返回类型放在函数参数之后定义,使得复杂返回类型(特别是依赖模板参数的返回类型)的声明更加直观和可读。
22. 常量表达式(constexpr)
constexpr
关键字用于定义在编译时求值的常量表达式。
示例:
constexpr int square(int x) {
return x * x;
}
constexpr int result = square(5); // 在编译时计算结果
背后的原理:
constexpr
函数必须是纯函数(不产生副作用),并且只能调用其他constexpr
函数或操作常量,从而保证在编译时可以求值。
23. 用户自定义字面量(User-defined Literals)
用户自定义字面量允许开发者定义新的字面量语法。
示例:
constexpr long double operator"" _km(long double x) {
return x * 1000;
}
constexpr long double operator"" _m(long double x) {
return x;
}
constexpr long double operator"" _cm(long double x) {
return x / 100;
}
long double distance = 3.4_km + 150.0_m + 13.0_cm; // 距离单位转换
背后的原理:
用户自定义字面量通过定义特定形式的 operator""
函数来处理新的字面量语法,从而扩展语言的字面量支持,增强代码可读性。
24. 右值引用成员函数(Rvalue Reference Member Functions)
右值引用成员函数允许对象在右值上下文中调用特定的成员函数。
示例:
class MyClass {
public:
void foo() & {
std::cout << "Left-value object" << std::endl;
}
void foo() && {
std::cout << "Right-value object" << std::endl;
}
};
MyClass obj;
obj.foo(); // 输出 "Left-value object"
MyClass().foo(); // 输出 "Right-value object"
背后的原理:
右值引用成员函数通过 &
和 &&
修饰符区分左右值上下文,使得类可以在不同上下文中执行不同操作,优化资源管理和性能。
25. 非静态数据成员初始化器(Non-static Data Member Initializers)
非静态数据成员初始化器允许在类定义中直接为成员变量提供初始值。
示例:
class MyClass {
int x = 10; // 直接初始化
int y = 20;
};
背后的原理:
非静态数据成员初始化器使得成员变量的初始化更加简洁,避免了在构造函数中进行重复初始化,增强代码可读性和维护性。
26. 标准库增强(Standard Library Enhancements)
C++11大幅增强了标准库,引入了一些新的容器和算法,改进了现有的库。
示例:
#include <array>
#include <unordered_map>
#include <regex>
#include <chrono>
// std::array
std::array<int, 3> arr = {1, 2, 3};
// std::unordered_map
std::unordered_map<std::string, int> umap;
umap["one"] = 1;
umap["two"] = 2;
// std::regex
std::regex re("\\w+");
std::smatch match;
std::string s = "hello world";
if (std::regex_search(s, match, re)) {
std::cout << match[0] << std::endl; // 输出 "hello"
}
// std::chrono
using namespace std::chrono;
auto start = high_resolution_clock::now();
// ... some operations ...
auto end = high_resolution_clock::now();
auto duration = duration_cast<milliseconds>(end - start).count();
std::cout << "Duration: " << duration << " ms" << std::endl;
背后的原理:
C++11标准库的增强为开发者提供了更多的工具和数据结构,使得开发高效、灵活和可维护的代码更加容易。
四、C++14 新特性详细总结
1. 泛型Lambda表达式(Generic Lambdas)
C++14扩展了lambda表达式的功能,使其能够接受模板参数,从而变得更加通用。
示例:
auto add = [](auto a, auto b) {
return a + b;
};
std::cout << add(1, 2) << std::endl; // 输出 3
std::cout << add(1.5, 2.5) << std::endl; // 输出 4.0
背后的原理:
泛型lambda表达式使用 auto
关键字来表示任意类型的参数,编译器会根据传递的参数类型自动生成模板代码。这大大提高了lambda表达式的灵活性和通用性。
2. 二进制字面量(Binary Literals)
C++14引入了二进制字面量,使得编写和阅读二进制数据更加方便。
示例:
int binary_value = 0b1010; // 等价于十进制的10
std::cout << binary_value << std::endl; // 输出 10
背后的原理:
二进制字面量通过前缀 0b
或 0B
来表示,使得开发者可以更直观地定义和理解二进制数据,特别是在嵌入式系统和底层编程中非常有用。
3. 数字分隔符(Digit Separators)
C++14引入了单引号作为数字字面量的分隔符,使得长数字更易读。
示例:
int largeNumber = 1'000'000; // 等价于1000000
std::cout << largeNumber << std::endl; // 输出 1000000
背后的原理:
数字分隔符通过在数字字面量中插入单引号来分隔数字,这不会影响数字的值,但显著提高了长数字的可读性和维护性。
4. constexpr
的增强(Extended constexpr
)
C++14增强了 constexpr
的功能,使得 constexpr
函数可以包含更多的语句和逻辑。
示例:
constexpr int factorial(int n) {
if (n <= 1) return 1;
else return n * factorial(n - 1);
}
constexpr int result = factorial(5); // 在编译时计算结果
背后的原理:
C++14的 constexpr
使得函数可以包含循环、条件语句等复杂逻辑,从而编写更强大的编译时计算代码,提升了代码的性能和灵活性。
5. 返回类型推断(Return Type Deduction)
C++14引入了返回类型推断功能,使得编译器可以自动推断函数的返回类型。
示例:
auto add(int a, int b) {
return a + b;
}
std::cout << add(1, 2) << std::endl; // 输出 3
背后的原理:
返回类型推断通过 auto
关键字使得编译器根据函数体内容自动推断返回类型,从而简化了函数的定义,特别适用于返回类型复杂或依赖模板参数的情况。
6. decltype(auto)
C++14引入了 decltype(auto)
关键字,使得返回类型推断更加灵活和准确。
示例:
int x = 5;
decltype(auto) y = x; // y的类型为int
decltype(auto) z = (x); // z的类型为int&
std::cout << y << std::endl; // 输出 5
std::cout << z << std::endl; // 输出 5
背后的原理:
decltype(auto)
结合了 auto
和 decltype
的特性,使得编译器可以根据表达式的类型和值类别(左值/右值)来准确推断变量或返回值的类型。
7. 逐步初始化捕获(Generalized Lambda Capture)
C++14扩展了lambda表达式的捕获机制,使得可以直接捕获并初始化变量。
示例:
int x = 10;
auto lambda = [y = x + 5]() {
return y;
};
std::cout << lambda() << std::endl; // 输出 15
背后的原理:
逐步初始化捕获通过在捕获列表中使用 =
或 &
进行初始化,使得lambda表达式可以捕获并初始化变量,从而增强了lambda表达式的灵活性和可读性。
8. 标准库增强(Standard Library Enhancements)
C++14对标准库进行了一些增强和改进,增加了更多实用的功能和容器。
示例:
#include <shared_mutex>
#include <make_unique>
// std::shared_timed_mutex
std::shared_timed_mutex mutex;
std::shared_lock<std::shared_timed_mutex> lock(mutex);
// std::make_unique
auto ptr = std::make_unique<int>(10);
std::cout << *ptr << std::endl; // 输出 10
背后的原理:
C++14的标准库增强引入了更多线程管理和资源管理工具,如 std::shared_timed_mutex
和 std::shared_lock
,以及 std::make_unique
等方便的工厂函数,提升了代码的可维护性和性能。
9. std::integer_sequence
和 std::make_integer_sequence
C++14引入了 std::integer_sequence
和 std::make_integer_sequence
,用于生成整数序列,常用于模板元编程。
示例:
#include <utility>
#include <iostream>
template<typename T, T... Ints>
void print_sequence(std::integer_sequence<T, Ints...>) {
((std::cout << Ints << ' '), ...);
}
int main() {
print_sequence(std::make_integer_sequence<int, 5>{}); // 输出 0 1 2 3 4
return 0;
}
背后的原理:
std::integer_sequence
和 std::make_integer_sequence
提供了一个生成整数序列的工具,极大地方便了模板元编程,特别是在处理参数包和编译时计算时非常有用。
五、C++17 新特性详细总结
1. 结构化绑定声明(Structured Bindings)
C++17引入了结构化绑定声明,使得可以将结构或元组的元素绑定到独立的变量中。
示例:
#include <tuple>
#include <iostream>
std::tuple<int, double, std::string> getTuple() {
return std::make_tuple(42, 3.14, "hello");
}
int main() {
auto [i, d, s] = getTuple();
std::cout << i << ", " << d << ", " << s << std::endl; // 输出 42, 3.14, hello
return 0;
}
背后的原理:
结构化绑定通过在变量声明时使用 auto
关键字和方括号,将结构或元组的元素解包并绑定到独立的变量上,从而简化代码,增强可读性。
2. if
和 switch
初始化语句(If and Switch Initialization Statements)
C++17允许在 if
和 switch
语句中进行初始化,从而减少代码范围,提升可读性。
示例:
if (std::vector<int> vec = {1, 2, 3}; !vec.empty()) {
std::cout << "Vector is not empty" << std::endl;
}
switch (int n = 42; n) {
case 1:
std::cout << "One" << std::endl;
break;
case 42:
std::cout << "Forty-Two" << std::endl;
break;
default:
std::cout << "Other" << std::endl;
break;
}
背后的原理:
if
和 switch
初始化语句通过引入初始化部分,使得在判断条件语句前可以进行变量的声明和初始化,从而缩小变量的作用域,增强代码可维护性。
3. 内联变量(Inline Variables)
C++17引入了内联变量关键字 inline
,用来声明在多个翻译单元中共享的变量。
示例:
inline int global_counter = 0;
void increment() {
++global_counter;
}
背后的原理:
内联变量通过 inline
关键字使得变量在多个翻译单元中共享定义,避免链接错误,增强全局变量的可用性。
4. 折叠表达式(Fold Expressions)
C++17引入了折叠表达式,简化了对参数包的操作。
示例:
template<typename... Args>
auto sum(Args... args) {
return (args + ...); // 右折叠
}
int main() {
std::cout << sum(1, 2, 3, 4, 5) << std::endl; // 输出 15
return 0;
}
背后的原理:
折叠表达式通过 ...
语法简化了对可变参数模板包的操作,支持左折叠和右折叠两种形式,减少了代码的冗余和复杂性。
5. std::optional
C++17引入了 std::optional
,用于表示一个可能包含值或者为空的对象。
示例:
#include <optional>
#include <iostream>
std::optional<int> findValue(bool found) {
if (found) return 42;
else return std::nullopt;
}
int main() {
auto value = findValue(true);
if (value) {
std::cout << "Value: " << *value << std::endl; // 输出 42
} else {
std::cout << "Value not found" << std::endl;
}
return 0;
}
背后的原理:
std::optional
提供了一种安全的方式来处理可能为空的值,避免了空指针异常和非必要的默认值,提高了代码的安全性和可读性。
6. std::variant
C++17引入了 std::variant
,一种类型安全的联合体,可以存储多种类型中的一种。
示例:
#include <variant>
#include <iostream>
std::variant<int, double, std::string> getValue(bool condition) {
if (condition) return 42;
else return "hello";
}
int main() {
auto value = getValue(true);
if (std::holds_alternative<int>(value)) {
std::cout << "Integer: " << std::get<int>(value) << std::endl; // 输出 42
} else if (std::holds_alternative<std::string>(value)) {
std::cout << "String: " << std::get<std::string>(value) << std::endl;
}
return 0;
}
背后的原理:
std::variant
提供了一种类型安全的方式来处理多个可能的类型,避免了传统联合体的类型安全问题,增强了代码的健壮性和可维护性。
7. std::any
C++17引入了 std::any
,可以存储任意类型的对象。
示例:
#include <any>
#include <iostream>
int main() {
std::any value = 42;
if (value.type() == typeid(int)) {
std::cout << "Integer: " << std::any_cast<int>(value) << std::endl; // 输出 42
}
value = std::string("hello");
if (value.type() == typeid(std::string)) {
std::cout << "String: " << std::any_cast<std::string>(value) << std::endl; // 输出 hello
}
return 0;
}
背后的原理:
std::any
提供了一种灵活的方式来存储任意类型的对象,通过类型安全的访问机制(如 std::any_cast
),避免了类型错误,增强了代码的灵活性和安全性。
8. std::string_view
C++17引入了 std::string_view
,这是一个轻量级的不可变字符串视图,避免了不必要的字符串拷贝。
示例:
#include <string_view>
#include <iostream>
void printStringView(std::string_view str) {
std::cout << str << std::endl;
}
int main() {
std::string s = "hello world";
printStringView(s); // 输出 hello world
return 0;
}
背后的原理:
std::string_view
提供了一种高效的方式来操作字符串数据,避免了字符串拷贝的开销,适用于只读字符串操作,提升了性能和代码的可读性。
9. std::filesystem
C++17引入了标准文件系统库 std::filesystem
,提供了一组高效便捷的文件和目录操作API。
示例:
#include <filesystem>
#include <iostream>
int main() {
std::filesystem::path p = "/tmp";
if (std::filesystem::exists(p)) {
std::cout << "Directory exists" << std::endl;
}
for (const auto& entry : std::filesystem::directory_iterator(p)) {
std::cout << entry.path() << std::endl;
}
return 0;
}
背后的原理:
std::filesystem
提供了跨平台的文件和目录操作API,包括路径操作、文件状态查询、目录遍历等,大大简化了文件系统相关的编程任务。
10. 并行算法(Parallel Algorithms)
C++17引入了并行算法,通过扩展标准库算法,支持多线程并行执行。
示例:
#include <algorithm>
#include <vector>
#include <execution>
#include <iostream>
int main() {
std::vector<int> vec(1000, 1);
std::for_each(std::execution::par, vec.begin(), vec.end(), [](int& n) { n++; });
std::cout << "Sum: " << std::accumulate(vec.begin(), vec.end(), 0) << std::endl; // 输出 2000
return 0;
}
背后的原理:
并行算法通过支持并行执行策略(如 std::execution::par
和 std::execution::par_unseq
),利用多线程和硬件加速,显著提升了算法的执行效率和性能。
六、C++20 新特性详细总结
1. 概念(Concepts)
C++20引入了概念(Concepts),用于约束模板参数,提高模板代码的可读性和错误检查能力。
示例:
#include <concepts>
#include <iostream>
template<typename T>
concept Integral = std::is_integral_v<T>;
template<Integral T>
T add(T a, T b) {
return a + b;
}
int main() {
std::cout << add(1, 2) << std::endl; // 输出 3
// std::cout << add(1.0, 2.0) << std::endl; // 编译错误,因为double不是Integral
return 0;
}
背后的原理:
概念通过 concept
关键字定义,并结合模板参数,使得模板代码在编译时进行约束检查,避免不符合条件的类型传递,提升模板代码的安全性和可读性。
2. 三方比较运算符(<=>)(Three-way Comparison Operator)
C++20引入了三方比较运算符(<=>),用于简化和统一比较操作。
示例:
#include <iostream>
#include <compare>
struct Point {
int x, y;
auto operator<=>(const Point&) const = default;
};
int main() {
Point p1{1, 2};
Point p2{1, 3};
if (p1 < p2) {
std::cout << "p1 is less than p2" << std::endl;
} else if (p1 == p2) {
std::cout << "p1 is equal to p2" << std::endl;
} else {
std::cout << "p1 is greater than p2" << std::endl;
}
return 0;
}
背后的原理:
三方比较运算符(<=>)生成统一的比较操作符,使得对象的比较更加简洁和一致,支持 <
, <=
, ==
, !=
, >=
, >
这些操作符。
3. 范围库(Ranges)
C++20引入了范围库(Ranges),大大简化了对集合和序列的操作。
示例:
#include <ranges>
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
auto even_numbers = vec | std::ranges::views::filter([](int n) { return n % 2 == 0; });
for (int n : even_numbers) {
std::cout << n << std::endl; // 输出 2 4
}
return 0;
}
背后的原理:
范围库通过引入 views
和 actions
,使得对集合和序列的操作更加直观和简洁,增强了代码的可读性和灵活性。
4. 协程(Coroutines)
C++20引入了协程(Coroutines),用于简化异步编程和生成器的实现。
示例:
#include <coroutine> // 包含协程支持
#include <iostream> // 包含输入输出支持
#include <optional> // 包含 std::optional(在这段代码中未使用,但通常有用)
// 协程生成器类
struct Generator {
// 定义协程的 promise_type
struct promise_type {
int current_value; // 存储当前生成的值
// 协程挂起并返回值
std::suspend_always yield_value(int value) {
current_value = value;
return {};
}
// 协程初始挂起
std::suspend_always initial_suspend() { return {}; }
// 协程最终挂起
std::suspend_always final_suspend() noexcept { return {}; }
// 获取生成器对象
Generator get_return_object() { return Generator{this}; }
// 返回 void,因为生成器不返回值
void return_void() {}
// 处理未捕获的异常
void unhandled_exception() {}
};
// 迭代器用于遍历生成器生成的值
struct iterator {
std::coroutine_handle<promise_type> handle; // 协程句柄
bool done; // 标记协程是否完成
// 前置递增运算符用于推进协程
iterator& operator++() {
handle.resume(); // 恢复协程
done = handle.done(); // 更新完成状态
return *this;
}
// 解引用运算符返回当前值
int operator*() const { return handle.promise().current_value; }
// 比较运算符用于结束迭代
bool operator!=(const iterator&) const { return !done; }
};
// 返回生成器的开始迭代器
iterator begin() {
handle.resume(); // 恢复协程以获取第一个值
return iterator{handle, handle.done()};
}
// 返回生成器的结束迭代器
iterator end() { return iterator{handle, true}; }
// 协程句柄
std::coroutine_handle<promise_type> handle;
};
// 协程函数,生成从 1 到 max 的整数序列
Generator counter(int max) {
for (int i = 1; i <= max; ++i) {
co_yield i; // 生成值并挂起协程
}
}
// 主函数,测试生成器
int main() {
for (int value : counter(5)) {
std::cout << value << std::endl; // 输出 1 2 3 4 5
}
return 0;
}
代码解释:
包含头文件:
#include <coroutine> #include <iostream> #include <optional>
#include <coroutine>
:用于C++协程支持。#include <iostream>
:用于输入输出操作。#include <optional>
:虽然未在代码中使用,但通常有用。定义
Generator
类:
promise_type
结构体:
int current_value;
:存储当前生成的值。std::suspend_always yield_value(int value);
:协程挂起并返回值。std::suspend_always initial_suspend();
:协程初始挂起。std::suspend_always final_suspend() noexcept;
:协程最终挂起。Generator get_return_object();
:返回生成器对象。void return_void();
:生成器不返回值。void unhandled_exception();
:处理未捕获的异常。iterator
结构体:
std::coroutine_handle<promise_type> handle;
:协程句柄。bool done;
:标记协程是否完成。iterator& operator++();
:前置递增运算符,用于推进协程。int operator*() const;
:解引用运算符,返回当前值。bool operator!=(const iterator&) const;
:比较运算符,用于结束迭代。begin
和end
方法:
iterator begin();
:返回生成器的开始迭代器。iterator end();
:返回生成器的结束迭代器。定义协程函数
counter
:Generator counter(int max) { for (int i = 1; i <= max; ++i) { co_yield i; } }
- 生成从1到
max
的整数序列。主函数
main
:int main() { for (int value : counter(5)) { std::cout << value << std::endl; // 输出 1 2 3 4 5 } return 0; }
- 使用
for
循环遍历生成器生成的值,并输出它们。
背后的原理:
协程通过 co_await
, co_yield
和 co_return
关键字,使得函数可以在中间挂起和恢复执行,从而简化了异步操作和生成器的实现,提升了代码的可维护性和性能。
5. 模块(Modules)
C++20引入了模块(Modules),用于替代传统的头文件机制,提高编译速度和模块化程度。
示例:
// example.ixx
export module example;
export int add(int a, int b) {
return a + b;
}
// main.cpp
import example;
#include <iostream>
int main() {
std::cout << add(1, 2) << std::endl; // 输出 3
return 0;
}
背后的原理:
模块通过 export
和 import
关键字,引入模块化机制,解决了头文件的重复编译问题,提高了编译速度和代码的模块化程度。
6. 计时器库增强(Calendar and Time Zone Library)
C++20引入了增强的计时器库,支持日历和时区操作。
示例:
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
auto now = system_clock::now();
auto today = floor<days>(now);
std::cout << "Today: " << today.time_since_epoch().count() << std::endl; // 输出自纪元以来的天数
return 0;
}
背后的原理:
增强的计时器库通过引入 std::chrono
的新工具和类型,使得日历和时区操作更加直观和便捷,提升了代码的时间处理能力。
7. constinit
关键字
C++20引入了 constinit
关键字,用于确保变量在编译时初始化。
示例:
constinit int global_value = 42;
int main() {
std::cout << global_value << std::endl; // 输出 42
return 0;
}
背后的原理:
constinit
关键字确保变量在编译时完成初始化,避免未初始化变量的使用,增强了代码的安全性和可预测性。
8. [[likely]]
和 [[unlikely]]
属性
C++20引入了 [[likely]]
和 [[unlikely]]
属性,用于提示编译器哪条分支更可能被执行,从而优化分支预测。
示例:
#include <iostream>
int main() {
int n = 42;
if (n == 42) [[likely]] {
std::cout << "Likely branch" << std::endl;
} else [[unlikely]] {
std::cout << "Unlikely branch" << std::endl;
}
return 0;
}
背后的原理:
[[likely]]
和 [[unlikely]]
属性通过提示编译器进行分支预测优化,从而提升代码的运行效率,特别是在性能关键的代码路径中。
9. std::span
C++20引入了 std::span
,一个轻量级的视图,可以非拥有地访问连续的元素。
示例:
#include <span>
#include <iostream>
#include <vector>
void printSpan(std::span<int> sp) {
for (int n : sp) {
std::cout << n << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
printSpan(vec); // 输出 1 2 3 4 5
int arr[] = {6, 7, 8, 9, 10};
printSpan(arr); // 输出 6 7 8 9 10
return 0;
}
背后的原理:
std::span
提供了一种高效的方式来访问连续内存中的元素,避免了不必要的拷贝和所有权管理,提升了代码的性能和灵活性。
10. 错误传播(std::expected)
C++20引入了 std::expected
,用于表示可能成功或错误的结果,极大简化了错误处理。
示例:
#include <iostream>
#include <expected> // 包含<std::expected>头文件,需要C++23或支持该功能的编译器
// 定义一个函数 divide,用于进行两个整数的除法操作,并返回一个 std::expected 类型的结果
std::expected<int, std::string> divide(int a, int b) {
// 检查除数是否为零
if (b == 0)
// 如果除数为零,返回一个 std::unexpected 对象,包含错误信息 "Division by zero"
return std::unexpected("Division by zero");
// 如果除数不为零,返回商
return a / b;
}
int main() {
// 调用 divide 函数,进行 10 除以 2 的操作
auto result = divide(10, 2);
// 检查 result 是否包含一个有效值
if (result) {
// 如果 result 有效,解引用 result 并打印结果
std::cout << "Result: " << *result << std::endl; // 输出 5
} else {
// 如果 result 无效,打印错误信息
std::cout << "Error: " << result.error() << std::endl;
}
return 0;
}
示例解释:
包含头文件:
#include <iostream> #include <expected>
#include <iostream>
用于输入输出操作。#include <expected>
用于std::expected
。定义
divide
函数:std::expected<int, std::string> divide(int a, int b) { if (b == 0) return std::unexpected("Division by zero"); return a / b; }
- 接受两个整数
a
和b
。- 如果
b
为零,返回错误信息 "Division by zero"。- 否则,返回
a
除以b
的结果。主函数:
int main() { auto result = divide(10, 2); if (result) { std::cout << "Result: " << *result << std::endl; } else { std::cout << "Error: " << result.error() << std::endl; } return 0; }
- 调用
divide
函数,执行10
除以2
。- 检查
result
是否有效:
- 如果有效,打印结果。
- 如果无效,打印错误信息。
这段代码演示了如何使用
std::expected
来处理可能的错误情况,例如除以零的错误。
背后的原理:
std::expected
提供了一种类型安全的方式来处理可能成功或失败的操作,避免了异常的使用,提升了代码的健壮性和可读性。
这些是C++20中的主要新特性,它们在提升语言灵活性、简化代码和增强性能方面都有显著作用。C++20的引入标志着C++语言的又一次重要进化,进一步增强了其作为现代编程语言的地位。
七、总结
从C++11到C++20,每一次标准更新都带来了大量的新特性和改进,使得C++语言更加现代化和强大。这些新特性不仅提升了开发效率和代码质量,还极大地增强了语言的表达能力和灵活性。通过引入自动类型推导、Lambda表达式、智能指针、并行STL算法、协程、模块等功能,C++逐渐成为了更适合现代软件开发的语言。了解并掌握这些新特性,可以帮助开发者在实际项目中更好地应用C++,从而开发出更加高效和可靠的软件系统。未来的C++标准还将继续演进,我们期待C++23及以后的版本能够带来更加丰富和实用的功能,为开发者提供更好的编程体验。
希望本文能够帮助你全面了解C++各个版本的新特性,并在实际开发中充分利用这些功能。无论是初学者还是经验丰富的开发者,深入理解并应用这些新特性,都是提升编程技能和开发效率的关键。