C++11

  1. 自动类型推导

auto 需要注意的是:

  • 必须在定义时初始化 auto 类型的变量
  • auto 类型不能定义数组
  • 一旦初始化,就不可更改类型
auto x = 5; 
auto y = 3.14; 
auto a;  // 错误
auto array[10]; //错误
auto a = 10;
a = "ABC"; //错误

decltype 的类型推导并不是像 auto 一样从变量声明的初始化表达式获得变量类型,而是以一个普通表达式作为参数,并返回表达式类型,并不会对表达式求值。

int i = 4;
decltype(i) a; //推导a为int
  1. for range 循环

需要注意的是:

  • 这种循环方式不能使用索引
  • 基于 for range 循环的遍历是只读遍历,除非将变量类型声明为引用类型。
std::vector<int> v = {1, 2, 3, 4, 5};
for (auto& element : v) {
    std::cout << element << " ";
}
  1. Lambda 表达式

利用 Lambda 表达式,可以方便地定义和创建匿名函数。声明格式如下:

[capture list] (params list) mutable exception-> return type { function body }
  • [capture list]:捕获外部变量列表
  • params list:形参列表
  • mutable:说明是否可以修改捕获的变量
  • exception:异常设置
  • return type:返回值类型
  • {function body}:函数体

在下面示例中:

  • auto lambda 说明为 lambda 类型变量,lambda 不能手动指定类型
  • 为捕获外部变量列表
  • (int a, int b) 为形参列表,接受 2 个整形参数
  • mutable 关键字,说明可以修改捕获的变量
  • throw() 表示声明不抛出任何异常
  • -> int 说明返回值为一个整型值
  • { x += a + b; return x; } 函数体用于实现具体功能,也就是返回 x = x + (a + b)
auto lambda = [x] (int a, int b) mutable throw() -> int {
    x += a + b;
    return x;
};
std::cout << lambda(3, 4) << std::endl;

再写一个例子,是一个无捕获的 Lambda 表达式。

这个很多人一开始看不懂,sort() 函数的第 3 个可选参数是排序方法 comp,默认情况下是升序,当 a < b = true 时,sort() 认为 a 应该出现在 b 之前;当 a < b = false 时,sort() 认为 b 应该出现在 a 之前或两者相等。

std::vector<int> v = {3, 1, 4, 1, 5, 9};
std::sort(v.begin(), v.end(), [](int a, int b) {
    return a < b;
});
  1. 智能指针
std::unique_ptr<int> p1(new int(42)); // 独占所有权,保证同一时间内只有一个智能指针可以指向该对象。
std::shared_ptr<int> p2 = std::make_shared<int>(42); // 共享所有权,多个智能指针可以指向相同对象,该对象和其相关资源会在最后一个引用被销毁时(计数为 0)候释放
std::weak_ptr<int> p3 = p2   // weak_ptr 只是提供了对管理对象的一个访问手段,引用对象时引用计数不增加
std::weak_ptr<int> p3 = p1   // 错误,不能指向 unique_ptr
  1. 右值引用和移动语义
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1); // 移动语义,避免深拷贝

左值是一般指表达式结束后依然存在的持久化对象,右值指表达式结束时就不再存在的临时对象。区分左值和右值的便捷方法:能对表达式取地址、有名字的对象为左值。反之,不能取地址、匿名的对象为右值。

  1. 线程库
#include <thread>
void func() {
    std::cout << "Hello thread!" << std::endl;
}
std::thread t(func);
t.join();
  1. nullptr

表示空指针,以后不要用 NULL,因为 NULL 本质上是个 int 型的 0。

  1. constexpr 关键字

使指定的常量表达式在编译阶段计算出结果。可以修饰变量、指针和函数。

  • 变量:const 关键字表示只读,能够保证运行时不被更改,但初值不确定,不能在编译期决定;constexpr 在编译期就会算出来。
  • 指针:顶层 const 表示指针自身无法更改;底层 const 表示指针指向对象无法更改;constexpr 表示指针无法更改。
  • 函数:const 修饰的是函数的返回值,该函数不能修改对象内的任何成员,只能发生读操作,不能发生写操作。constexpr 无法修饰成员函数,只能作为函数返回值类型,表明该函数返回的是一个编译期可确定的常量

const 关键字写的比较粗略,更多内容可见:C语言 const 修饰函数参数 - C语言零基础入门教程 - 猿说编程 - 博客园

  1. explicit

修饰构造函数,表示只能显式构造,不可以被隐式转换。

  1. final & override

这两个关键字用于修饰类,final 表示该类不能进一步派生;override 表示该函数重写了基类的函数。

  1. 完美转发

通常与模板和右值一起使用,能实现泛型编程中的参数传递。通过 std::forward 方式实现,会保持参数原本的类型。

  1. 别名

可以提高代码可读性,简化一些很复杂的类型书写。

typedef int myInt;  // 将 int 定义为 myInt 类型的别名
typedef std::vector<int> IntVector;  // 将 std::vector<int> 定义为 IntVector 类型的别名

using myInt = int;  // 将 int 定义为 myInt 类型的别名
using IntVector = std::vector<int>;  // 将 std::vector<int> 定义为 IntVector 类型的别名

C++14

  1. 泛型 Lambda 表达式

C++14 的泛型 lambda 可以被看做 C++11 的(单态)lambda 的升级版,可以使用 auto 关键字来创建一个可以接受任何类型参数的 lambda 表达式。

要注意的是:

  • 泛型 lambda 可能接受隐式类型转换
  • 表达式中的操作不支持所有可能的类型时,类型推导错误进而导致编译错误。
auto add = [](int a, int b) { return a + b; };     // C++11
auto add = [](auto a, auto b) { return a + b; };   // C++14
  1. 返回类型推导
auto func() {
    return 42; // 返回类型自动推导为 int
}
  1. 变量模板

变量模板不是变量,只有实例化的变量模板,编译器才会生成实际的变量。变量模板实例化后简单的说就是一个全局变量,所以也不用担心生存期的问题。

  • 模板函数,生成类型无关的函数
template<typename T>
     T max(T a, T b) { return a > b ? a : b; }
  • 模板类,生成类型无关的类
struct Math {
         static const double pi; // 声明
     };
     const double Math::pi = 3.14159;
  • 模板变量
template<typename T>
constexpr T pi = T(3.141592653589793238462643383279);

double d = pi<double>;
float f = pi<float>;

是为了代码简洁性,也支持编译器计算。

  1. 二进制字面量

0b 或者 0B 开头。

int a = 0b1010; // 二进制字面量 -> 十进制的 10
int b = 0b0001'0011'1010; // 带有分隔符的二进制字面量,防止看花眼
  1. std::make_unique

std::make_unique 比使用 new 更安全,因为其可以防止内存泄漏,也可以避免裸指针。

std::unique_ptr<MyClass> ptr1(new MyClass());
std::unique_ptr<MyClass> ptr2=std::make_unique<MyClass>();

C++17

  1. 结构化绑定

可以把 std::pairstd::tuple 以及其他具有公开非静态类型的元素绑定到一组变量上。变量类型是自动推导出来的,不会出现类型不匹配的问题。

std::pair<int, std::string> p = {42, "hello"};
// 使用tie方法获取p中的元素
int x;
std::string y;
std::tie(x,y) = p;
// 结构化绑定方法获取p中的元素
auto [value, text] = p;

还可以用于遍历 map 以及获取有多个返回值函数的返回值:

std::map<int, std::string> m = {{1, "one"}, {2, "two"}};
for (const auto& [key, value] : m) {
    std::cout << key << ": " << value << std::endl;
}

auto getPerson() {
    return std::make_tuple("Alice", 30);
}
auto [name, age] = getPerson();
  1. constexpr if

普通的 if-else 是在执行期进行条件判断与选择,这意味着在泛型编程中无法使用。

constexpr if 支持在编译期执行,可以将之应用于泛型编程中的条件判断。

template<typename T>
auto get_value(T t) {
    if constexpr (std::is_integral_v<T>) {
        return t * 2;
    } else {
        return t;
    }
}
  1. 折叠表达式

折叠表达式以更简单的语法形式进行形参包展开。

( 形参包 运算符 ... )              // 一元右折叠
( ... 运算符 形参包 )              // 一元左折叠
( 形参包 运算符 ... 运算符 初值 )   // 二元右折叠
( 初值 运算符 ... 运算符 形参包 )   // 二元左折叠

能看得出来,左/右折叠取决于 ... 相对于形参包的位置。

  1. std::optional

std::optional 表示一个可能有值的对象,没有值时就返回 std::nullopt

std::optional<int> find_value() {
    if (condition) {
        return 42;                // 有值,返回 42
    } else {
        return std::nullopt;      // 返回 nullpot
    }
}
  1. 文件系统库

可以更方便的进行包括但不限于路径操作,文件和目录的创建/删除/查询,获取文件大小和系统空间信息,目录遍历等操作。

#include <filesystem>

C++20

  1. Concept

本质上是一个模板类型的 bool 变量,用于在编译器约束模板类型。当模板参数不满足约束时,编译器能给出直观的错误提示。能够替代 C++11 的 enable_ifstatic_assert 等烧脑的约束方式。

// enable_if约束模板参数
template <typename T, 
          typename = std::enable_if_t<std::is_integral_v<T>>>  // 约束:必须是整数类型
T enable_if_add(T a, T b) {
    return a + b;
}

// concept约束模板参数
template <typename T>
concept Integral = std::is_integral_v<T>;  // 定义整数类型概念

template <Integral T>  // 使用concept约束
T concept_add(T a, T b) {
    return a + b;
}

// concept的简写形式
Integral auto concept_add_shorthand(Integral auto a, Integral auto b) {
    return a + b;
}
  1. Coroutine

协程。和 Subroutine 有区别,我们写的函数方法都是 Subroutine;而 Coroutine 可以与其他 Coroutine 和 Subroutine 协作,能够进行挂起(Suspend)和恢复(Resume)操作,但是不能是 main 函数,可以暂停协程,完成其他任务后再恢复到暂停的位置继续执行。协程让我们使用同步方式写异步代码

  • co_await:暂停协程,等待某个操作完成
  • co_yield:暂停协程并返回一个值
  • co_return:结束协程并返回结果
  1. Ranges

Ranges 提供了一种更高层次的抽象,允许你以声明式的方式描述数据处理操作,而无需手动编写循环。主要包含三个部分:

  • views:本身不会拥有数据,而是对底层数据进行操作。
  • actions:执行最终操作,比如将 Ranges 中的元素复制。
  • algorithms:对标准算法库的扩展。
  1. Modules

主要是为了解决由于多次包含头文件时导致的编译时间变长,头文件间的依赖关系导致的依赖管理复杂,以及命名空间污染等问题。

模块声明以 module 关键字开始,后接模块名,模块名唯一。

// 模块接口 module.cpp
module test_module;
export class MyClass { // export关键字声明公开接口
public:
    void foo();
};
// 模块实现 module_impl.cpp
module test_module;
void MyClass::foo() {
    // 实现
}
// main.cpp
import my_module;
int main() {
    MyClass obj;
    obj.foo();
    return 0;
}
  1. 三路比较运算符 <=>

参考文章

[1] C++11新特性——auto的使用方法

[2] C++ 11 Lambda表达式 - 滴水瓦 - 博客园

[3] C++ sort排序函数用法-CSDN博客

[4] C++干货系列——右值引用与移动语义 - 知乎

[5] 【C++】右值引用、移动语义、完美转发

[6] 变量模板 | 现代 C++ 模板教程

[7] 深入探索C++智能指针:std::unique_ptr、std::make_unique、std::make_shared与new的对比 - 知乎

[8] C++17尝鲜:结构化绑定声明

[9] C++17 之 "constexpr if"_c++17 constexpr if 表达式-CSDN博客

[10] 折叠表达式 | 现代 C++ 模板教程

[11] C++17 新特性之 std::optional(上) - 知乎

[12] 如何编写 C++ 20 协程(Coroutines) - 知乎