C++11特性

C++11特性

Move semantics 移动语义

[!tip]
在 C++98 中,所有的对象拷贝都使用了 拷贝构造函数拷贝赋值运算符,这通常需要深拷贝资源,例如动态分配的内存或文件句柄。这会导致性能开销,尤其是对于临时对象而言(如函数返回值)。

为了解决这个问题,C++11 引入了移动语义,通过区分“拷贝”和“移动”两种语义,大幅提高性能

左值和右值

  • 左值(Lvalue): 指向内存中的某个具体位置,具有持久性
    • 示例:变量名 int a = 10; a 是左值
  • 右值(Rvalue): 临时值,通常是没有持久性的
    • 示例:字面值 10 或临时对象 a + b 的结果

C++11 中,右值被细化为 纯右值(prvalue)亡值(xvalue),其中亡值用于表示即将失去所有权的对象(如返回值)

移动构造函数和移动赋值运算符

  • 移动构造函数:允许从另一个对象中“窃取”资源,而不是复制资源。
  • 移动赋值运算符:将一个对象的资源转移给另一个对象,而不需要深拷贝。
class MyClass {
    int* data;

public:
    // 构造函数
    MyClass(int val) : data(new int(val)) {}

    // 移动构造函数
    MyClass(MyClass&& other) noexcept : data(other.data) {
        other.data = nullptr; // 释放所有权
    }

    // 移动赋值运算符
    MyClass& operator=(MyClass&& other) noexcept {
        if (this != &other) {
            delete data;       // 释放原有资源
            data = other.data; // 转移所有权
            other.data = nullptr;
        }
        return *this;
    }

    ~MyClass() { delete data; }
};

标准库函数

C++11

Rvalue references 右值引用

C++11 新增了一种类型引用:右值引用,使用 && 表示,右值引用只绑定到右值。

int x = 0; // `x` is an lvalue of type `int`
int& xl = x; // `xl` is an lvalue of type `int&`
int&& xr = x; // compiler error -- `x` is an lvalue
int&& xr2 = 0; // `xr2` is an lvalue of type `int&&` -- binds to the rvalue temporary, `0`

void f(int& x) {}
void f(int&& x) {}

f(x);  // calls f(int&)
f(xl); // calls f(int&)
f(3);  // calls f(int&&)
f(std::move(x)); // calls f(int&&)

f(xr2);           // calls f(int&)
f(std::move(xr2)); // calls f(int&& x)

右值引用的主要目的是:

  • 接收和操作右值(如临时对象)。
  • 实现 移动语义完美转发

Forwarding references 转发引用

也被称为Universal References(万能引用、通用引用),与std::forward一起使用实现完美转发(Perfect Forwarding。转发引用是通过语法 T&& 创建的,其中 T 是模板类型参数,或使用 auto&&
主要作用是:根据传入参数的值类别(左值或右值)保持其原始类别,从而正确地传递给另一个函数(例如,左值保持为左值,临时对象作为右值转发)

转发引用允许引用根据类型绑定到左值或右值。转发引用遵循引用折叠规则:

  • T& & 变成 T&
  • T& && 变成 T&
  • T&& & 变成 T&
  • T&& && 变成 T&&

auto 类型推导与左值和右值:

int x = 0; // `x` is an lvalue of type `int`
auto&& al = x; // `al` is an lvalue of type `int&` -- binds to the lvalue, `x`
auto&& ar = 0; // `ar` is an lvalue of type `int&&` -- binds to the rvalue temporary, `0`

带有左值和右值的模板类型参数推导:

// Since C++14 or later:
void f(auto&& t) {
  // ...
}

// Since C++11 or later:
template <typename T>
void f(T&& t) {
  // ...
}

int x = 0;
f(0); // T is int, deduces as f(int &&) => f(int&&)
f(x); // T is int&, deduces as f(int& &&) => f(int&)

int& y = x;
f(y); // T is int&, deduces as f(int& &&) => f(int&)

int&& z = 0; // NOTE: `z` is an lvalue with type `int&&`.
f(z); // T is int&, deduces as f(int& &&) => f(int&)
f(std::move(z)); // T is int, deduces as f(int &&) => f(int&&)

[!note]

int x = 10,为什么int&& y = x会出错,而auto&& y = x却没有问题?

  • int && y = x中,x是一个左值,而y是一个右值引用,右值引用只能绑定右值
  • auto&& y = x中,auto&&是一个万能引用,x是左值时,其会自动推导为int&x是右值时,其会自动推到为int&&,所以该式子会自动推导为int& y = x

标准库函数

C++11

用于移动语义的特殊成员函数

复制构造函数和复制赋值运算符在进行复制时被调用,而随着 C++11 引入移动语义,现在有了用于移动的移动构造函数和移动赋值运算符。目的是提高性能,特别是当操作涉及临时对象时

移动构造函数

移动构造函数的作用是“窃取”另一个对象的资源,而不是复制资源,从而避免了不必要的深拷贝操作。

特点:

  • 接受一个右值引用(T&&)作为参数。
  • 通常会转移资源的所有权。
  • 被移动的对象(源对象)会进入“有效但无用”的状态(如将指针置为 nullptr
#include <iostream>
#include <utility> // for std::move

class MyClass {
    int* data;

public:
    // 构造函数
    MyClass(int val) : data(new int(val)) {
        std::cout << "Constructed with value: " << val << std::endl;
    }

    // 移动构造函数
    MyClass(MyClass&& other) noexcept : data(other.data) {
        other.data = nullptr; // 释放源对象的所有权
        std::cout << "Moved!" << std::endl;
    }

    // 析构函数
    ~MyClass() {
        if (data) {
            std::cout << "Destroyed: " << *data << std::endl;
            delete data;
        } else {
            std::cout << "Destroyed empty object." << std::endl;
        }
    }
};

int main() {
    MyClass obj1(10);         // 正常构造
    MyClass obj2(std::move(obj1)); // 使用移动构造
    return 0;
}

Variadic templates  可变参数模板

... 语法创建一个参数包或展开一个参数包。它允许模板接受可变数量的参数。这是实现 泛型编程 和简化代码的重要特性

template<typename... Args>
void function(Args... args) {
    // ...
}
  • ...(省略号)
    • Args... 表示类型参数包,可以接收零个或多个类型。
    • args... 表示非类型参数包,可以接收零个或多个值。
  • 展开参数包
    • 参数包需要展开(使用 ...),否则无法直接操作其内容

示例:

#include <iostream>

template<typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << "
"; // 参数包展开
}

int main() {
    print(1, 2, 3, "Hello", 4.5); // 打印: 123Hello4.5
    return 0;
}

参数包展开方式

  1. 递归展开(C++11/C++14)
    #include <iostream>
    
    // 基础函数:零参数时的终止条件
    void print() {
        std::cout << "End of arguments.
    ";
    }
    
    // 可变参数模板
    template<typename T, typename... Args>
    void print(T first, Args... rest) {
        std::cout << first << " "; // 打印第一个参数
        print(rest...);            // 递归调用,展开剩余参数
    }
    
    int main() {
        print(1, "Hello", 3.14, "World");
        return 0;
    }
    
    
  2. 折叠表达式(C++17)
    #include <iostream>
    
    template<typename... Args>
    void print(Args... args) {
        ((std::cout << args << " "), ...) << "
    "; // 左折叠
    }
    
    int main() {
        print(1, "Hello", 3.14, "World");
        return 0;
    }
    

折叠表达式的形式:

  • 一元左折叠:(... op expor)
  • 一元右折叠:(expr op ...)
  • 二元左折叠:(init op ... op expr)
  • 二元右折叠:(expr op ... op init)

应用场景

  1. 实现通用打印函数

    #include <iostream>
    
    template<typename... Args>
    void log(const std::string& prefix, Args... args) {
      std::cout << prefix << ": ";
      (std::cout << ... << args) << "
    "; // 折叠表达式
    }
    
    int main() {
      log("Info", "Hello", ", ", "World", "!", 123);
      log("Error", "An error occurred: ", 404);
      return 0;
    }
    
  2. 转发参数

    在 C++11 中,可变参数模板与 std::forward 结合,可实现 完美转发

    #include <iostream>
    #include <utility>
    
    void display(int x) {
        std::cout << "Int: " << x << "
    ";
    }
    
    void display(const std::string& str) {
        std::cout << "String: " << str << "
    ";
    }
    
    template<typename... Args>
    void forwarder(Args&&... args) {
        (display(std::forward<Args>(args)), ...); // 完美转发
    }
    
    int main() {
        int x = 42;
        std::string str = "Hello";
    
        forwarder(x, str, 100, "World");
        return 0;
    }
    
    
  3. 构造函数的可变参数模板

    可变参数模板常用于实现容器类的构造函数,支持任意数量的初始化参数:

    #include <iostream>
    #include <vector>
    
    class MyContainer {
        std::vector<int> data;
    
    public:
        template<typename... Args>
        MyContainer(Args... args) : data{args...} {}
    
        void print() const {
            for (auto x : data) {
                std::cout << x << " ";
            }
            std::cout << "
    ";
        }
    };
    
    int main() {
        MyContainer c(1, 2, 3, 4, 5);
        c.print(); // 输出: 1 2 3 4 5
        return 0;
    }
    
    

初始化列表

通过花括号 {} 提供一组值,并且这些值可以自动转换为 std::initializer_list 对象
特性:

  1. 不可修改:
    • std::initializer_list 提供只读访问(begin()、end())。
    • 无法修改其元素。
  2. 轻量级:
    • 它是一个轻量级对象,通常由编译器管理其生命周期。
  3. 适配统一的初始化语法:
    • 支持统一的列表初始化(如 {})语法。

应用场景

  1. 构造函数的初始化

    #include <iostream>
    #include <initializer_list>
    
    class MyClass {
        std::vector<int> data;
    public:
        MyClass(std::initializer_list<int> list) {
            std::cout << "Initialized with: ";
            for (auto value : list) {
                std::cout << value << " ";
            }
            std::cout << std::endl;
        }
         // 传统构造函数
        MyClass(int n, int value) : data(n, value) {
            std::cout << "Initialized with size and value.
    ";
        }
    };
    
    int main() {
        MyClass obj1{1, 2, 3, 4}; // 使用初始化列表
        MyClass obj2{10, 20};     // 使用另一个初始化列表
        MyClass obj2(5, 10);          // 使用传统方法初始化
        return 0;
    }
    
    
  2. 函数参数的初始化

    #include <iostream>
    #include <initializer_list>
    
    void print(std::initializer_list<std::string> list) {
        for (const auto& item : list) {
            std::cout << item << " ";
        }
        std::cout << std::endl;
    }
    
    int main() {
        print({"Hello", "World", "!"}); // 使用初始化列表
        return 0;
    }
    
    
  3. 容器初始化

    #include <iostream>
    #include <vector>
    #include <set>
    
    int main() {
        std::vector<int> vec = {1, 2, 3, 4}; // 初始化 std::vector
        std::set<std::string> s = {"apple", "banana", "cherry"}; // 初始化 std::set
    
        for (auto v : vec) std::cout << v << " ";
        std::cout << std::endl;
    
        for (auto item : s) std::cout << item << " ";
        return 0;
    }
    
特性 std::initializer_list 普通数组 变长模板参数
语法 {} {} template<typename... Args>
是否支持动态大小 固定大小 固定大小 支持
元素类型 必须相同 必须相同 可不同
是否提供容器接口
常用场景 容器初始化、构造函数 数组初始化 函数调用

静态断言

static_assert 是 C++11 引入的一种编译期断言工具,用于在编译时检查某些条件是否满足。如果条件不满足,编译器会报错并停止编译。

static_assert(constexpr_condition, message);

constexpr_condition:

  • 一个编译期可计算的布尔表达式。
  • 必须是 true,否则编译器会报错。

message:

  • (可选)当断言失败时,编译器输出的错误消息。

应用场景

  1. 类型属性检查

    确保某些类型满足特定条件,例如大小、对齐方式、是否是整数类型等

    template<typename T>
    void checkType() {
        static_assert(std::is_integral<T>::value, "T must be an integral type");
    }
    
    int main() {
        checkType<int>();    // OK
        checkType<double>(); // 编译失败:T must be an integral type
        return 0;
    }
    
    
  2. 构造编译期约束

    在编译期对模板参数进行检查,防止传入不符合条件的类型或值。

    template<typename T>
    struct MyStruct {
        static_assert(sizeof(T) <= 4, "T must be at most 4 bytes");
    };
    
    MyStruct<int> obj;    // OK
    MyStruct<double> obj; // 编译失败:T must be at most 4 bytes
    
  3. 平台相关检查

    static_assert(sizeof(void*) == 8, "This code requires a 64-bit environment");
    
  4. 枚举值的合法性

    enum class Color { Red, Green, Blue, Max };
    static_assert(static_cast<int>(Color::Max) <= 3, "Color enum out of range");
    

动态断言 (assert) vs 静态断言 (static_assert)

特性 assert static_assert
检查时间 运行时 编译时
适用场景 动态条件检查 编译期条件检查
性能开销 存在运行时开销 无运行时开销
表达式类型 任意布尔表达式 constexpr(编译期可计算)
使用时机 需要根据运行时数据进行验证 条件可以在编译期验证
示例 assert(a > b); static_assert(sizeof(T) <= 4);

带默认消息的简洁用法

如果不提供第二个参数,编译器会自动生成一条默认的错误消息:

static_assert(sizeof(int) == 4); // 默认错误消息

示例

  1. 检查模板实例的正确性

    确保模板实例化时的类型或值满足特定约束。

    template<int N>
    struct Factorial {
        static_assert(N >= 0, "Factorial is undefined for negative numbers");
        static constexpr int value = N * Factorial<N - 1>::value;
    };
    
    // 特化终止递归
    template<>
    struct Factorial<0> {
        static constexpr int value = 1;
    };
    
    int main() {
        constexpr int result = Factorial<5>::value; // OK
        constexpr int invalid = Factorial< -1>::value; // 编译失败
        return 0;
    }
    
  2. 条件性静态断言

    结合 if constexpr,在特定情况下启用断言

    template<typename T>
    void process() {
        if constexpr (std::is_integral<T>::value) {
            static_assert(sizeof(T) <= 4, "Integral type must be at most 4 bytes");
        } else {
            static_assert(sizeof(T) > 4, "Non-integral type must be larger than 4 bytes");
        }
    }
    
    int main() {
        process<int>();    // OK
        process<float>();  // OK
        return 0;
    }
    
    

auto

用于让编译器根据表达式的上下文自动推导变量的类型,从而简化代码,提升代码的可读性和维护性

int x = 10;
auto y = x; // y 的类型自动推导为 int

应用场景

  1. 简化变量声明

    std::vector<int> vec = {1, 2, 3, 4};
    auto it = vec.begin(); // 自动推导迭代器类型
    
  2. 与范围 for 循环结合

    std::vector<int> vec = {1, 2, 3, 4};
    for (auto value : vec) {
        std::cout << value << " ";
    }
    

    如果需要修改值,可以使用 auto&

    for (auto& value : vec) {
        value *= 2; // 修改 vec 中的值
    }
    
  3. 结合 Lambda 表达式
    auto 可以用于推导 Lambda 表达式返回的类型。

    auto lambda = [](int a, int b) { return a + b; };
    std::cout << lambda(2, 3); // 输出:5
    
  4. 函数返回类型的自动推导
    auto 可以用于函数返回值的类型推导,结合 decltype 或直接返回值推导。
    (C++11) 使用 decltype

    template<typename T1, typename T2>
    auto add(T1 a, T2 b) -> decltype(a + b) {
        return a + b;
    }
    

    (C++14 起) 自动推导返回类型:

    template<typename T1, typename T2>
    auto add(T1 a, T2 b) {
        return a + b;
    }
    
  5. 协助模板类型推导

    template<typename Container>
    void printContainer(const Container& c) {
        for (auto it = c.begin(); it != c.end(); ++it) {
            std::cout << *it << " ";
        }
    }
    

注意事项

  1. 指针和引用类型
    auto 会去掉顶层的指针或引用属性,但可以显式添加。

    • 普通类型

      int x = 10;
      auto y = x; // y 的类型为 int
      
    • 引用类型

      int x = 10;
      auto& y = x; // y 是 int&,绑定到 x
      
    • 指针类型:

      int x = 10;
      int* p = &x;
      auto ptr = p; // ptr 的类型为 int*
      
  2. 常量和顶层 const

    auto 会去掉顶层 const,但保留底层 const

    • 顶层 const

      const int x = 10;
      auto y = x; // y 的类型为 int(顶层 const 被去掉)
      
    • 底层 const

      const int x = 10;
      const int* p = &x;
      auto ptr = p; // ptr 的类型为 const int*(底层 const 被保留)
      
  3. 带初始化列表时的行为

    autostd::initializer_list 有特殊关系。

    auto x = {1, 2, 3}; // x 的类型是 std::initializer_list<int>
    

    如果需要 x 是数组或容器类型,可以显式指定类型

  4. 函数声明中的 auto

    • Lambda 表达式的返回类型

      auto lambda = [](int a, int b) { return a + b; };
      
    • 普通函数返回类型

      auto add(int a, int b) {
          return a + b; // 返回类型由编译器推导
      }
      
  5. 类型推导可能不符合预期

    必须清楚 auto 推导的规则,避免误解。

    const int x = 42;
    auto y = x;  // y 的类型是 int,不是 const int
    
  6. 可能影响代码的可维护性

    当代码需要明确的类型信息时,滥用 auto 会降低可读性

auto可以避免代码冗余

当类型复杂时,auto可以避免重复声明类型

std::map<int, std::vector<int>> myMap;
auto it = myMap.begin(); // 避免写 std::map<int, std::vector<int>>::iterator

在模板和泛型编程中,auto 能自动适应变化的类型,提高代码的通用性。

Lambda表达式

lambda 是一个未命名的函数对象,能够捕获作用域中的变量,使用lambda可以简洁定义内联函数。

Lambda表达式的语法结构:

[捕获列表](参数列表) -> 返回类型 {
    函数体
};
  1. 捕获列表 [ ]

    • 用于指定 lambda 捕获外部作用域的变量。

    • 支持的捕获方式:

      • []:不捕获任何变量。
        • [=]:以值捕获所有外部作用域的变量。
        • [&]:以引用捕获所有外部作用域的变量。
        • [x]:以值捕获变量 x
        • [&x]:以引用捕获变量 x
        • [=, &x]:以值捕获其他变量,以引用捕获 x
        • [&, x]:以引用捕获其他变量,以值捕获 x
        • [this]:通过引用捕获this
  2. 参数列表 ( )

    • 用于定义传递给 lambda 表达式的参数,与普通函数的参数列表类似
  3. 返回类型 -> 返回类型(可选):

    • 明确指定返回类型。如果能推断返回类型,则可以省略。
  4. 函数体 { ... }

    • Lambda 表达式的实际逻辑。

示例

  1. 基本用法

    #include <iostream>
    using namespace std;
    
    int main() {
        auto add = [](int a, int b) -> int {
            return a + b;
        };
        cout << "3 + 4 = " << add(3, 4) << endl; // 输出 3 + 4 = 7
        return 0;
    }
    
  2. 捕获变量

    #include <iostream>
    using namespace std;
    
    int main() {
        int x = 10, y = 20;
        auto printSum = [x, y]() {
            cout << "Sum: " << x + y << endl;
        };
        printSum(); // 输出 Sum: 30
    
        auto modifyY = [&y]() {
            y += 10;
            cout << "Modified y: " << y << endl;
        };
        modifyY(); // 输出 Modified y: 30
        return 0;
    }
    
  3. 在 STL 算法中的使用

    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    
    int main() {
        vector<int> numbers = {1, 2, 3, 4, 5};
    
        // 使用 lambda 表达式来定义筛选条件
        auto evenNumbers = count_if(numbers.begin(), numbers.end(), [](int n) {
            return n % 2 == 0;
        });
    
        cout << "Even numbers count: " << evenNumbers << endl; // 输出 2
        return 0;
    }
    
  4. 可变捕获(mutable

    [!tip]

    默认情况下,值捕获不能在 lambda 内部修改,因为编译器生成的方法被标记为 constmutable 关键字允许修改捕获的变量。该关键字放置在参数列表之后(即使为空也必须存在)

    #include <iostream>
    using namespace std;
    
    int main() {
        int value = 42;
        auto captureByValue = [value]() mutable {
            value += 10;
            cout << "Inside lambda: " << value << endl; // 输出 52
        };
    
        captureByValue();
        cout << "Outside lambda: " << value << endl; // 输出 42
        return 0;
    }
    
  5. 泛型 Lambda(C++14 引入)

    #include <iostream>
    using namespace std;
    
    int main() {
        auto multiply = [](auto a, auto b) {
            return a * b;
        };
    
        cout << "3 * 4 = " << multiply(3, 4) << endl;     // 输出 12
        cout << "2.5 * 4.2 = " << multiply(2.5, 4.2) << endl; // 输出 10.5
        return 0;
    }
    

decltype

decltype 是一个运算符,主要用于推断表达式的类型,可以在编译时确定一个变量或表达式的类型。如果 cv 限定符(const,volatile)和引用是表达式的一部分,则会保留它们。

特性和用法

  1. 推断表达式的类型
    • decltype 直接返回表达式的类型,而不会对表达式进行计算。
  2. 区分变量和变量的引用
    • 如果表达式是一个变量,decltype 返回的是该变量的实际类型(可能包含引用)。
  3. 结合 auto 使用
    • auto 不同,decltype 用于类型推导,但不进行类型修改。auto 通常用于变量初始化,而 decltype 更适合于定义与已有变量或表达式相同类型的新变量。
    • decltype(auto), c++14特性
  4. 模板元编程中的应用
    • decltype 是模板元编程的重要工具,可以精确地推断函数返回值的类型。
decltype(expression) variable_name;
  • expression 是需要推断类型的表达式。
  • variable_name 是声明的新变量。

示例

  1. 基本用法

    #include <iostream>
    using namespace std;
    
    int main() {
        int a = 10;
        decltype(a) b = 20; // b 的类型与 a 一致,都是 int
    
        cout << "a: " << a << ", b: " << b << endl;
        return 0;
    }
    
    
  2. 推断表达式的类型

    #include <iostream>
    using namespace std;
    
    int main() {
        int x = 5;
        decltype(x + 1.0) y = 3.14; // x + 1.0 的类型是 double,所以 y 的类型也是 double
    
        cout << "y: " << y << endl;
        return 0;
    }
    
    
  3. 推断引用类型

    #include <iostream>
    using namespace std;
    
    int main() {
        int x = 10;
        int& ref = x;
    
        decltype(ref) y = x; // y 的类型是 int&,所以 y 是 x 的引用
        y = 20; // 改变 y 的值也会改变 x 的值
    
        cout << "x: " << x << ", y: " << y << endl;
        return 0;
    }
    
    
  4. auto 的对比

    #include <iostream>
    using namespace std;
    
    int main() {
        const int x = 10;
        
        auto a = x;        // auto 会去掉 const,a 是 int
        decltype(x) b = x; // decltype 保持 const,b 是 const int
    
        a = 20; // 合法
        // b = 30; // 非法,因为 b 是 const
    
        cout << "a: " << a << ", b: " << b << endl;
        return 0;
    }
    
    
  5. 用于函数返回类型推导

    #include <iostream>
    using namespace std;
    
    int add(int a, int b) {
        return a + b;
    }
    
    int main() {
        decltype(add(1, 2)) result = 5; // 推断 add 返回值的类型
        cout << "result: " << result << endl;
        return 0;
    }
    
    
int a = 1; // `a` is declared as type `int`
decltype(a) b = a; // `decltype(a)` is `int`
const int& c = a; // `c` is declared as type `const int&`
decltype(c) d = a; // `decltype(c)` is `const int&`
decltype(123) e = 123; // `decltype(123)` is `int`
int&& f = 1; // `f` is declared as type `int&&`
decltype(f) g = 1; // `decltype(f) is `int&&`
decltype((a)) h = g; // `decltype((a))` is int&

类型别名

在C++中,有两种方式定义类型别名:

  1. 使用 typedef
  2. 使用 using(C++11 引入,推荐方式)

typedef 方式

typedef 是一种传统方式,语法如下:

typedef existing_type new_type_name;

示例

include <iostream>
#include <vector>

typedef unsigned int uint;
typedef std::vector<int> IntVector;

int main() {
    uint a = 42;              // uint 作为 unsigned int 的别名
    IntVector vec = {1, 2, 3}; // IntVector 是 std::vector<int> 的别名

    std::cout << "a: " << a << std::endl;
    for (int val : vec) {
        std::cout << val << " ";
    }
    return 0;
}

using 方式

using 是 C++11 引入的语法,更加简洁且功能更强。语法如下:

using new_type_name = existing_type;

示例

#include <iostream>
#include <map>
#include <string>

using StringIntMap = std::map<std::string, int>;

int main() {
    StringIntMap myMap; // StringIntMap 是 std::map<std::string, int> 的别名
    myMap["apple"] = 3;
    myMap["banana"] = 5;

    for (const auto &pair : myMap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }
    return 0;
}

using 与模板兼容

#include <iostream>
#include <vector>

// 定义模板别名
template <typename T>
using Vec = std::vector<T>;

int main() {
    Vec<int> numbers = {1, 2, 3}; // Vec<int> 等价于 std::vector<int>
    for (int n : numbers) {
        std::cout << n << " ";
    }
    return 0;
}

强类型枚举

强类型枚举(Strongly-typed enums),也称为scoped enums

类型安全的枚举解决了 C 风格枚举的各种问题,包括:隐式转换、无法指定底层类型、作用域污染

强类型枚举的定义方式是使用关键字 enum classenum struct,两者功能相同,语法为:

enum class EnumName : underlying_type { ... };

传统枚举 (Unscoped Enum) 的问题

  • 全局作用域污染:传统枚举的枚举成员直接暴露在外部作用域,容易和其他标识符发生冲突。

  • 类型安全性差:传统枚举隐式转换为整数类型,可能导致意外的比较或运算。

  • 未指定底层类型:默认底层类型是 int,但无法直接控制。

#include <iostream>

enum Color { Red, Green, Blue }; // 枚举成员暴露在全局作用域

int main() {
    Color color = Red;  // 直接使用成员
    int number = color; // 隐式转换为 int,类型不安全

    std::cout << "Color as int: " << number << std::endl;
    return 0;
}

强类型枚举的特性

  • 作用域限制:枚举成员属于枚举类型的命名空间,不会污染全局作用域。
  • 类型安全:强类型枚举不会隐式转换为整数类型。
  • 自定义底层类型:可以显式指定底层类型(如 uint8_tint32_t 等),有助于节省内存或与特定数据结构兼容。
#include <iostream>
#include <cstdint> // for fixed-width integer types

enum class Color : uint8_t { Red, Green, Blue }; // 定义强类型枚举

int main() {
    Color color = Color::Red; // 使用限定作用域的成员访问方式
    // int number = color;    // 错误:强类型枚举不能隐式转换为整数

    if (color == Color::Red) {
        std::cout << "The color is Red!" << std::endl;
    }

    return 0;
}

强类型枚举 vs. 传统枚举

特性 传统枚举 (Unscoped Enum) 强类型枚举 (Strongly-typed Enum)
作用域 全局作用域污染 成员属于枚举类型作用域
类型安全性 允许隐式转换为整数类型 不允许隐式转换
底层类型控制 默认 int,不可修改 可显式指定底层类型
适用性 适用于简单场景 适用于需要类型安全和作用域隔离的场景

[!IMPORTANT]

由于强类型枚举不能隐式转换为整数类型,以下操作需要显式转换:

  • 将枚举值转换为整数:static_cast<int>(EnumValue)
  • 将整数转换为枚举值:static_cast<EnumType>(intValue)
 #include <iostream>
 
 enum class Days { Monday, Tuesday, Wednesday };
 
 int main() {
     Days today = Days::Tuesday;
 
     // 显式转换为整数
     int dayNumber = static_cast<int>(today);
     std::cout << "Day number: " << dayNumber << std::endl;
 
     return 0;
 }
 

属性 Attributes

Attributes 是 C++11 引入的一种语言特性,用于向编译器提供额外的元信息,以帮助优化代码、检查错误或修改编译行为。它们不会直接影响程序的逻辑,但可以增强代码的可读性、可维护性,并减少潜在的错误。

语法:

[[attribute_name]]
  • 属性放置在代码的特定位置,通常是函数、变量、类型或语句。
  • 可以组合多个属性,用逗号分隔,例如:[[attr1, attr2]]

内置属性

  1. [[nodiscard]]

    指示调用者必须使用函数的返回值,避免因忽略返回值而导致潜在问题。

    [[nodiscard]] int calculateSum(int a, int b) {
        return a + b;
    }
    
    int main() {
        calculateSum(2, 3); // 警告:返回值被忽略
        int result = calculateSum(2, 3); // 正确
        return 0;
    }
    
    
  2. [[maybe_unused]]

    用于抑制未使用变量或参数的警告。

    void func([[maybe_unused]] int unusedParam) {
        // unusedParam 没有被使用
    }
    
    
  3. [[deprecated]]

    标记为不建议使用的函数、类或变量,编译时会发出警告。

    [[deprecated("Use newFunction() instead")]]
    void oldFunction() {
        // ...
    }
    
    void newFunction() {
        // ...
    }
    
    int main() {
        oldFunction(); // 警告:该函数已弃用
        newFunction();
        return 0;
    }
    
    
  4. [[fallthrough]]

    switch 语句中明确表示允许无意的穿透(fall-through)。

    void check(int value) {
        switch (value) {
            case 1:
                // Intentional fall-through
                [[fallthrough]];
            case 2:
                std::cout << "Value is 1 or 2
    ";
                break;
            default:
                std::cout << "Other value
    ";
                break;
        }
    }
    
    
  5. [[likely]][[unlikely]]

提示编译器某个分支更可能或不太可能被执行(C++20 引入)

void process(int value) {
    if ([[likely]] value > 0) {
        std::cout << "Positive value
";
    } else {
        std::cout << "Non-positive value
";
    }
}

自定义属性命名空间

为了避免冲突,C++允许开发者使用命名空间为自定义属性命名。

namespace MyAttributes {
    [[nodiscard]] struct my_attr {};
}

[[MyAttributes::my_attr]] void myFunction() {
    // Function with custom attribute
}

使用场景

  1. 静态分析和错误检测:如 [[nodiscard]][[deprecated]] 提高了代码的健壮性。
  2. 代码优化:如 [[likely]][[unlikely]] 可帮助编译器生成更高效的分支预测代码。
  3. 清晰的语义表达:如 [[fallthrough]] 明确标注意图,避免误解。
  4. 压制警告:如 [[maybe_unused]] 在必要时避免警告。

常量表达式 constexpr

constexpr 是 C++11 引入的关键字,用于定义在编译时可以求值的表达式或函数。通过 constexpr,可以在编译时进行常量求值,优化性能并减少运行时计算开销。

constexpr 的主要用途

  1. 定义常量
    • 在编译时就能确定值的变量。
  2. 函数声明
    • 用于声明在编译时执行的函数。
  3. 支持编译时计算
    • 允许在编译时对复杂表达式进行求值。

constexpr 的规则

  1. constexpr 变量

    • 使用 constexpr 修饰的变量必须具有常量值。

    • 变量的初始化表达式必须在编译时可求值。

    constexpr int compileTimeValue = 42; // 编译时确定
    const int runTimeValue = 42;         // 运行时常量,但不一定在编译时求值
    
    constexpr int squared(int x) {
        return x * x;
    }
    
    int main() {
        constexpr int result = squared(4); // 编译时计算
        return result; // 编译器直接用 16 替代 result
    }
    
    
  2. constexpr 函数

    • 一个 constexpr 函数可以在编译时或运行时使用。
    • 函数体内必须只包含可以在编译时求值的操作(如常量运算或调用其他 constexpr 函数)。

    注意

    • 如果函数调用的参数是常量表达式,则在编译时求值;
    • 如果参数是运行时值,则在运行时执行。
    constexpr int add(int a, int b) {
        return a + b;
    }
    
    int main() {
        constexpr int result1 = add(2, 3); // 编译时计算
        int x = 5;
        int result2 = add(x, 3);          // 运行时计算,因为 x 是运行时变量
        return result1 + result2;
    }
    
    
  3. constexpr 和对象

    constexpr 也可以用于对象的构造函数,使得类对象在编译时创建。

    class Point {
    public:
        constexpr Point(int x, int y) : x_(x), y_(y) {}
        constexpr int getX() const { return x_; }
        constexpr int getY() const { return y_; }
    
    private:
        int x_, y_;
    };
    
    int main() {
        constexpr Point p(3, 4); // 编译时构造对象
        constexpr int x = p.getX(); // 编译时获取值
        return 0;
    }
    
    

constexprconst 的区别

特性 const constexpr
作用 定义常量 定义编译时常量或常量函数
编译时求值 不要求必须在编译时求值 要求初始化表达式在编译时求值
函数修饰 不适用于函数 适用于函数
类对象的支持 可定义 const 对象 可定义编译时类对象

C++14 中的增强

在 C++14 中,constexpr 进一步增强,支持更复杂的操作,包括:

  1. 函数体中允许使用 ifswitch 和循环。
  2. 变量可以在 constexpr 函数中声明为 mutable
constexpr int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

int main() {
    constexpr int fact5 = factorial(5); // 编译时计算 5!
    return fact5;
}

C++20 中的增强

C++20 进一步扩展了 constexpr 的能力:

  1. 允许在 constexpr 函数中使用动态内存分配(newdelete)。
  2. 允许在 constexpr 中使用标准库容器(如 std::vectorstd::string)。
#include <vector>

constexpr int sumElements(const std::vector<int>& vec) {
    int sum = 0;
    for (int val : vec) {
        sum += val;
    }
    return sum;
}

int main() {
    constexpr std::vector<int> numbers = {1, 2, 3, 4, 5};
    constexpr int total = sumElements(numbers); // 编译时计算
    return total;
}

委托构造函数 Delegating constructors

允许一个构造函数调用同一个类中的另一个构造函数

语法示例:

class ClassName {
public:
    ClassName(arguments) : ClassName(other_arguments) {
        // Additional initialization, if any
    }
};

示例:

#include <iostream>
#include <string>

class Person {
public:
    // 主构造函数
    Person(const std::string& name, int age) : name(name), age(age) {
        std::cout << "Primary constructor called
";
    }

    // 委托构造函数
    Person(const std::string& name) : Person(name, 0) {
        std::cout << "Delegating constructor called
";
    }

    void display() const {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }

private:
    std::string name;
    int age;
};

int main() {
    Person p1("Alice", 30); // 调用主构造函数
    p1.display();

    Person p2("Bob"); // 调用委托构造函数
    p2.display();

    return 0;
}

注意事项

  1. 初始化顺序
    • 被委托的构造函数会先执行。
    • 委托构造函数的其余部分在被委托构造函数执行完成后执行。
  2. 防止递归调用 如果构造函数相互委托,会导致无限递归,进而引发编译错误或运行时错误。
  3. 不能与继承的构造函数一起使用
    • 委托构造函数不能调用基类的构造函数。
    • 委托只发生在同一个类中。

多重委托构造

#include <iostream>

class Rectangle {
public:
    // 主构造函数
    Rectangle(int width, int height) : width(width), height(height) {
        std::cout << "Rectangle(int, int) called
";
    }

    // 默认构造函数,委托给 Rectangle(int, int)
    Rectangle() : Rectangle(0, 0) {
        std::cout << "Rectangle() called
";
    }

    // 构造正方形,委托给 Rectangle(int, int)
    Rectangle(int side) : Rectangle(side, side) {
        std::cout << "Rectangle(int) called
";
    }

    void display() const {
        std::cout << "Width: " << width << ", Height: " << height << std::endl;
    }

private:
    int width, height;
};

int main() {
    Rectangle r1;       // 默认构造函数
    Rectangle r2(5);    // 构造正方形
    Rectangle r3(4, 6); // 主构造函数

    r1.display();
    r2.display();
    r3.display();

    return 0;
}

用户定义字面量 User-defined literals

允许开发者为数值、字符串等字面量定义自己的后缀,从而创建更具可读性和语义化的代码

语法

用户自定义字面量以一个 _ 开头的后缀标识。可以为整数、浮点数、字符、字符串等字面量定义后缀。

Type operator "" _suffix(arguments);

suffix 是用户定义的后缀名称(必须以 _ 开头)。

参数类型由字面量的类型决定:

  • 整数字面量(e.g., 123):unsigned long long
  • 浮点数字面量(e.g., 1.23):long double
  • 字符字面量(e.g., "c"):char
  • 字符串字面量(e.g., "hello"):const char*std::string

示例

  1. 为长度单位定义字面量

    将数字字面量转换为单位化的类型:

    #include <iostream>
    
    constexpr long double operator "" _cm(long double value) {
        return value; // 以厘米为单位
    }
    
    constexpr long double operator "" _m(long double value) {
        return value * 100.0; // 转换为厘米
    }
    
    constexpr long double operator "" _km(long double value) {
        return value * 100000.0; // 转换为厘米
    }
    
    int main() {
        long double length = 1.0_km + 50.0_m + 25.0_cm;
        std::cout << "Length in cm: " << length << " cm" << std::endl;
        return 0;
    }
    
    
  2. 自定义字符串字面量

    将字符串字面量转换为 std::string

    #include <iostream>
    #include <string>
    
    std::string operator "" _s(const char* str, size_t) {
        return std::string(str);
    }
    
    int main() {
        std::string message = "Hello, world!"_s; // 使用自定义字符串字面量
        std::cout << message << std::endl;
        return 0;
    }
    
    
  3. 时间单位转换

    #include <iostream>
    #include <chrono>
    
    // 秒
    constexpr std::chrono::seconds operator "" _sec(unsigned long long s) {
        return std::chrono::seconds(s);
    }
    
    // 毫秒
    constexpr std::chrono::milliseconds operator "" _ms(unsigned long long ms) {
        return std::chrono::milliseconds(ms);
    }
    
    int main() {
        auto duration = 10_sec + 500_ms; // 10秒500毫秒
        std::cout << "Total milliseconds: " 
                  << std::chrono::duration_cast<std::chrono::milliseconds>(duration).count()
                  << " ms" << std::endl;
        return 0;
    }
    
    

参数类型说明

字面量类型 参数类型 示例
整数字面量 unsigned long long 123_ull
浮点数字面量 long double 3.14_ld
字符字面量 char "c"_ch
字符串字面量 const char*size_t "text"_str
原始字符串字面量 const char*size_t R"text("_rawstr

标准库中的用户自定义字面量

C++ 提供了一些内置的用户自定义字面量(C++14 开始),例如:

  • 时间单位std::chrono 提供的 _h_min_s 等字面量。
  • 字符串std::string_literals 提供的 _s
  • 复杂数std::complex_literals 提供的 _i
#include <iostream>
#include <chrono>
#include <string>
#include <complex>

using namespace std::chrono_literals; // 时间单位
using namespace std::string_literals; // 字符串
using namespace std::complex_literals; // 复数

int main() {
    auto time = 10s;               // 10 秒
    auto str = "Hello!"s;          // std::string
    auto complex = 3.0 + 4.0i;     // std::complex<double>

    std::cout << "Time: " << time.count() << " seconds" << std::endl;
    std::cout << "String: " << str << std::endl;
    std::cout << "Complex: " << complex << std::endl;

    return 0;
}

override

在派生类中显式地重写(override)基类的虚函数

#include <iostream>

class Base {
public:
    virtual void showMessage() const {
        std::cout << "Base message" << std::endl;
    }

    virtual ~Base() = default;
};

class Derived : public Base {
public:
    void showMessage() const override { // 使用 override 明确覆盖基类的虚函数
        std::cout << "Derived message" << std::endl;
    }
};

int main() {
    Base* obj = new Derived();
    obj->showMessage(); // 输出: Derived message
    delete obj;
    return 0;
}

final

指定虚函数不能在派生类中被重写,或者类不能被继承

struct A {
  virtual void foo();
};

struct B : A {
  virtual void foo() final;
};

struct C : B {
  virtual void foo(); // error -- declaration of "foo" overrides a "final" function
};
struct A final {};
struct B : A {}; // error -- base "A" is marked "final"

default

default 函数是用于显式声明类的默认行为的一种特性,通常与构造函数、析构函数和赋值运算符相关,使用 default 关键字可以让开发者控制哪些函数需要编译器自动生成的默认实现

默认函数:如果不显式定义,C++ 编译器会为类自动生成以下默认函数:

  • 默认构造函数(如果类没有用户定义构造函数)。
  • 默认析构函数。
  • 复制构造函数。
  • 移动构造函数(C++11 起)。
  • 复制赋值运算符。
  • 移动赋值运算符(C++11 起)。

使用 = default 的目的

  • 明确表明某个函数是默认生成的。
  • 在类的接口中显式地声明默认行为,增加可读性。
  • 强制生成某些情况下编译器不会自动生成的函数(例如某些模板类)
#include <iostream>

class Example {
public:
    Example() = default; // 显式声明默认构造函数
    ~Example() = default; // 显式声明默认析构函数

    Example(const Example&) = default; // 默认复制构造函数
    Example& operator=(const Example&) = default; // 默认复制赋值运算符

    Example(Example&&) = default; // 默认移动构造函数
    Example& operator=(Example&&) = default; // 默认移动赋值运算符
};

int main() {
    Example e1; // 默认构造函数
    Example e2 = e1; // 复制构造函数
    Example e3 = std::move(e1); // 移动构造函数
    e3 = e2; // 复制赋值运算符
    e3 = std::move(e2); // 移动赋值运算符

    return 0;
}

delete

通过关键字 = delete 明确声明的函数,表示这些函数被删除,禁止调用。被标记为 = delete 的函数会让编译器在编译阶段检查并报告错误,从而防止意外使用

#include <iostream>

class NonCopyable {
public:
    NonCopyable() = default;  // 默认构造函数
    ~NonCopyable() = default; // 默认析构函数

    NonCopyable(const NonCopyable&) = delete;            // 禁用复制构造函数
    NonCopyable& operator=(const NonCopyable&) = delete; // 禁用复制赋值运算符
};

int main() {
    NonCopyable obj1;
    // NonCopyable obj2 = obj1; // 错误:复制构造函数被删除
    // obj1 = obj2;             // 错误:复制赋值运算符被删除
    return 0;
}

基于范围的for循环

用于遍历容器(如数组、向量等)或范围

语法

for (declaration : range) {
    // 循环体
}
  • declaration:循环变量,表示容器中每个元素的类型。

  • range:要遍历的范围,可以是标准容器(如 std::vector)、数组、初始化列表或任何实现了 begin()end() 的范围。

示例:

#include <iostream>
#include <vector>

int main() {
    int arr[] = {1, 2, 3, 4, 5};

    for (int num : arr) { // 遍历数组,值传递,会发生拷贝
        std::cout << num << " ";
    }
    // 输出: 1 2 3 4 5
    
    std::vector<std::string> fruits = {"apple", "banana", "cherry"};
    for (const auto& fruit : fruits) { // 使用引用避免拷贝
        std::cout << fruit << " ";
    }
    // 输出: apple banana cherry
    return 0;
}

转换构造函数

转换构造函数(Converting constructors)是一种特殊的构造函数,可以通过单个参数的隐式或显式转换,将其他类型的值转换为类类型的对象的构造函数。c++03中要求转换构造函数只有单个参数,或其它参数具有默认值,c++11中转换构造函数则可以拥有多个参数,可以使用列表初始化{}调用拥有多个参数的转换构造函数

示例:

#include <iostream>
using namespace std;
class A{
public:
    A(int){cout << "A(int)" << endl;}
    A(const char* s, int){cout << "A(const char* s, int)" << endl;}
    A(int x, int y){cout << "A(int x, int y)" << endl;}
};

int main(){
    A a1 = 1; //A(int)
    A a2(2); //A(int)
    A a3{4,5}; //A(int x, int y)
    A a4 = {"ciallo",5}; //A(const char* s, int)
    A a5 = (A)1; //A(int)
    A a6(1,2); //A(int x, int y)
}

如果构造函数接受std::initializer_list, 则会调用它

struct A {
  A(int) {}
  A(int, int) {}
  A(int, int, int) {}
  A(std::initializer_list<int>) {}
};

A a {0, 0}; // calls A::A(std::initializer_list<int>)
A b(0, 0); // calls A::A(int, int)
A c = {0, 0}; // calls A::A(std::initializer_list<int>)
A d {0, 0, 0}; // calls A::A(std::initializer_list<int>)

如果对构造函数使用了explicit修饰符,则禁止对构造函数进行隐式转换

#include <iostream>
using namespace std;

class A{
public:
    explicit A(int){cout << "A(int)" << endl;}
};

int main(){
    A a1 = 1; //编译错误
}

显式转换函数

显式转换函数是通过在类成员函数前添加 explicit 关键字定义的特殊类型转换函数。这些函数只会在显式调用时生效,而不会在隐式上下文中自动调用。

class ClassName {
public:
    explicit operator TargetType() const {
        // Conversion logic here
    }
};

示例

#include <iostream>
#include <string>

class Temperature {
private:
    double celsius; // Temperature in Celsius
public:
    explicit Temperature(double c) : celsius(c) {}

    // Explicit conversion to double
    explicit operator double() const {
        return celsius;
    }

    // Explicit conversion to string
    explicit operator std::string() const {
        return std::to_string(celsius) + "°C";
    }

    // A method to print temperature
    void print() const {
        std::cout << "Temperature: " << celsius << "°C
";
    }
};

int main() {
    Temperature temp(36.6);

    // Error: implicit conversion not allowed due to explicit keyword
    // double value = temp;

    // Explicit conversion to double
    double value = static_cast<double>(temp);
    std::cout << "Temperature in double: " << value << "
";

    // Explicit conversion to string
    std::string strValue = static_cast<std::string>(temp);
    std::cout << "Temperature in string: " << strValue << "
";

    return 0;
}

struct A {
  operator bool() const { return true; }
};

struct B {
  explicit operator bool() const { return true; }
};

A a;
if (a); // OK calls A::operator bool()
bool ba = a; // OK copy-initialization selects A::operator bool()

B b;
if (b); // OK calls B::operator bool()
bool bb = b; // error copy-initialization does not consider B::operator bool()
bool bbb = static_cast<bool> b; //OK calls B::operator bool()

内联命名空间

内联命名空间用于库的版本控制和名称管理。一个 inline 命名空间中的所有成员可以被视为声明在外围命名空间中

namespace Program {
  namespace Version1 {
    int getVersion() { return 1; }
    bool isFirstVersion() { return true; }
  }
  inline namespace Version2 {
    int getVersion() { return 2; }
  }
}

int version {Program::getVersion()};              // Uses getVersion() from Version2
int oldVersion {Program::Version1::getVersion()}; // Uses getVersion() from Version1
bool firstVersion {Program::isFirstVersion()};    // Does not compile when Version2 is added

非静态数据成员初始化器

Non-static data member initializers

允许在声明非静态数据成员的地方对其进行初始化,这可能会简化默认初始化的构造函数

//  C++11之前的默认初始化
class Human {
    Human() : age{0} {}
  private:
    unsigned age;
};
// C++11的默认初始化
class Human {
  private:
    unsigned age {0};//括号初始化
    int x = 10; //直接初始化
};

[!NOTE]

不能用于动态内存分配,但可以用来初始化指针为 nullptr

引用限定成员函数

引用限定成员函数(Ref-qualified member functions)允许为类的成员函数指定调用的对象类型约束,即只能通过特定类型的对象(左值、右值)调用。这是通过在函数签名后使用 &&& 限定符来实现的

class ClassName {
public:
    void func() &;   // 只能通过左值调用
    void func() &&;  // 只能通过右值调用
};
  • & 限定符:表示该成员函数只能通过左值对象调用。
  • && 限定符:表示该成员函数只能通过右值对象调用。
#include <utility>
struct Bar {
  // ...
};

struct Foo {
  Bar& getBar() & { return bar; }
  const Bar& getBar() const& { return bar; }
  Bar&& getBar() && { return std::move(bar); }
  const Bar&& getBar() const&& { return std::move(bar); }
private:
  Bar bar;
};

int main(){
    Foo foo{};
    Bar bar = foo.getBar(); // calls `Bar& getBar() &`

    const Foo foo2{};
    Bar bar2 = foo2.getBar(); // calls `Bar& Foo::getBar() const&`

    Foo{}.getBar(); // calls `Bar&& Foo::getBar() &&`
    std::move(foo).getBar(); // calls `Bar&& Foo::getBar() &&`
    std::move(foo2).getBar(); // calls `const Bar&& Foo::getBar() const&`
}

尾随返回类型

允许在函数定义中将返回类型写在参数列表之后,而不是函数名之前。这种语法主要用于以下场景:

  1. 复杂返回类型的清晰表达
  2. 需要依赖函数参数的类型来决定返回类型时
auto function_name(parameters) -> return_type;

示例

#include <iostream>
#include <type_traits>
#include <vector>

// 简单返回类型
auto add(int a, int b) -> int {
    return a + b;
}

// 返回类型依赖模板参数
template <typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
    return a * b;
}

// 返回复杂类型
auto get_vector() -> std::vector<int> {
    return {1, 2, 3};
}

int main() {
    std::cout << add(3, 4) << "
";            // 输出: 7
    std::cout << multiply(3, 4.5) << "
";    // 输出: 13.5
    auto vec = get_vector();
    for (auto x : vec) {
        std::cout << x << " ";                // 输出: 1 2 3
    }
    std::cout << "
";
    return 0;
}

Noexcept 描述符

noexcept 指定符指定一个函数是否可能抛出异常。它是 throw() 的改进版本。

void func1() noexcept;        // does not throw
void func2() noexcept(true);  // does not throw
void func3() throw();         // does not throw

void func4() noexcept(false); // may throw

[!CAUTION]

声明为noexcept的函数中调用可能抛出异常的代码是危险的

void unsafe() noexcept {
    throw std::runtime_error("Error!"); // 导致std::terminate被调用
}

noexcept操作符

C++提供了noexcept操作符,用于检查一个表达式是否为noexcept

#include <iostream>

void safeFunction() noexcept {}

void unsafeFunction() {
    throw std::runtime_error("Unsafe function");
}

int main() {
    std::cout << std::boolalpha;
    std::cout << "safeFunction is noexcept: " << noexcept(safeFunction()) << "
";
    std::cout << "unsafeFunction is noexcept: " << noexcept(unsafeFunction()) << "
";
    return 0;
}

原始字符串字面值

可以以原始形式输入转义字符

// msg1 and msg2 are equivalent.
const char* msg1 = "
Hello,
	world!
";
const char* msg2 = R"(
Hello,
	world!
)";

标准库特性

std::move

std::move 是 C++ 标准库中的一个函数模板,定义在头文件 <utility> 中。它的作用是将一个对象显式地 转换为右值引用,以触发对象的移动语义(而不是拷贝语义)
std::move 的定义:

template <typename T>
typename remove_reference<T>::type&& move(T&& arg) {
  return static_cast<typename remove_reference<T>::type&&>(arg);
}

应用场景:

  • 避免深拷贝,触发移动语义
    当需要将对象的资源转移,而不需要深拷贝时使用 std::move
  • 临时对象的高效传递
    std::move 可以使容器在插入/移动对象时避免多余的拷贝操作。
  • 强制将变量转换为右值
    即使是一个左值变量(有名字的变量),使用 std::move 可以将其转为右值,允许资源转移

示例:

  • 基本移动语义
#include <cstring>
#include <iostream>
#include <utility> // for std::move
#include <string>

class MyString {
    char* data;
    size_t length;

public:
    // 构造函数
    MyString(const char* str) : length(strlen(str)), data(new char[length + 1]) {
        std::strcpy(data, str);
        std::cout << "Constructed: " << data << std::endl;
    }

    // 移动构造函数
    MyString(MyString&& other) noexcept : data(other.data), length(other.length) {
        other.data = nullptr;
        other.length = 0;
        std::cout << "Moved!" << std::endl;
    }

    // 析构函数
    ~MyString() {
        if (data) {
            std::cout << "Destroyed: " << data << std::endl;
            delete[] data;
        }
    }

    void print() const {
        if (data) std::cout << data << std::endl;
        else std::cout << "Empty!" << std::endl;
    }
};

int main() {
    MyString s1("Hello, World!"); // 正常构造
    MyString s2(std::move(s1));  // 使用 std::move 触发移动构造
    s1.print();                  // s1 已被移动,输出 "Empty!"
    s2.print();                  // s2 持有原来的资源,输出 "Hello, World!"
    return 0;
}

  • 容器中的优化
#include <iostream>
#include <vector>
#include <string>

int main() {
    std::vector<std::string> vec;

    std::string str = "Hello, World!";
    std::cout << "Pushing back copy:" << std::endl;
    vec.push_back(str); // 使用拷贝构造,str 内容未转移

    std::cout << "Pushing back move:" << std::endl;
    vec.push_back(std::move(str)); // 使用移动构造,str 内容被转移

    std::cout << "Vector contents: " << vec[0] << ", " << vec[1] << std::endl;
    std::cout << "Original string: " << str << std::endl; // str 可能为空或未定义状态

    return 0;
}

  • 强制转换为右值引用
#include <iostream>
#include <utility>

void printInt(int& n) {
    std::cout << "Lvalue: " << n << std::endl;
}

void printInt(int&& n) {
    std::cout << "Rvalue: " << n << std::endl;
}

int main() {
    int x = 10;

    printInt(x);               // 调用左值版本
    printInt(std::move(x));    // 调用右值版本
    printInt(20);              // 调用右值版本

    return 0;
}

  • std::unique_str的使用
#include <iostream>
#include <memory> // for std::unique_ptr and std::move
#include <vector>

void processUniquePtr(std::unique_ptr<int> p) {
    std::cout << "Processing value: " << *p << std::endl;
}

int main() {
    // 创建一个 std::unique_ptr,管理动态分配的 int
    std::unique_ptr<int> ptr1 = std::make_unique<int>(42);
    std::cout << "ptr1 owns: " << *ptr1 << std::endl;


    // 将所有权转移到 ptr2
    std::unique_ptr<int> ptr2 = std::move(ptr1);

    // ptr1 现在为空,ptr2 拥有资源
    if (!ptr1) {
        std::cout << "ptr1 is now null." << std::endl;
    }

    std::cout << "ptr2 owns: " << *ptr2 << std::endl;

    //传递 std::unique_ptr 到函数
    std::unique_ptr<int> ptr = std::make_unique<int>(100);
    // 使用 std::move 将所有权转移到函数参数
    processUniquePtr(std::move(ptr));

    if (!ptr) {
        std::cout << "ptr is now null after move." << std::endl;
    }

    //将 std::unique_ptr 放入容器
    std::vector<std::unique_ptr<int>> vec;

    // 创建两个 unique_ptr
    std::unique_ptr<int> ptr3 = std::make_unique<int>(10);
    std::unique_ptr<int> ptr4 = std::make_unique<int>(20);

    // 使用 std::move 将所有权转移到 vector 中
    vec.push_back(std::move(ptr3));
    vec.push_back(std::move(ptr4));

     // ptr1 和 ptr2 现在为空
    if (!ptr3 && !ptr4) {
        std::cout << "ptr3 and ptr4 are now null." << std::endl;
    }

    // 访问 vector 中的元素
    for (const auto& ptr : vec) {
        std::cout << "Vector element: " << *ptr << std::endl;
    }

    return 0;
}


std::forward

主要作用是:根据传入参数的值类别(左值或右值)保持其原始类别,从而正确地传递给另一个函数(例如,左值保持为左值,临时对象作为右值转发)

Forwarding references一起使用,实现完美转发

定义:

template <typename T>
T&& forward(typename remove_reference<T>::type& arg) {
  return static_cast<T&&>(arg);
}

T&& 的返回类型:

  • 如果 T 是左值引用,则返回左值引用。
  • 如果 T 是右值,则返回右值引用。

示例:

#include <iostream>
struct A {
  A() = default;
  A(const A& o) { std::cout << "copied" << std::endl; }
  A(A&& o) { std::cout << "moved" << std::endl; }
};
template <typename T>
A wrapper(T&& arg) {
  return A{std::forward<T>(arg)};
}
int main(){

    wrapper(A{}); // moved
    A a;
    wrapper(a); // copied
    wrapper(std::move(a)); // moved
}
  • 如果传递左值,T&& 会解析为 T& &(根据引用折叠规则,最终变为 T&)。
  • 如果传递右值,T&& 会解析为 T&&

[!note]
当需要将参数 arg 转发给另一个函数时,直接使用 arg 可能导致值类别被错误地修改。例如,右值可能被误认为左值传递,导致调用拷贝构造函数而非移动构造函数
若使用return A{arg},即使arg作为万能引用正确传递了引用类型,右值引用仍然会被作为左值使用,将会调用拷贝构造函数,输出三次copied

注意事项

  • std::forward 只能用于函数模板中的转发操作,因为它依赖于模板参数 T 的推导
  • 如果在不需要完美转发的地方使用 std::forward 可能导致意外行为
  • 不要在普通函数中使用 std::forward,普通函数的参数值类别是确定的,没有必要通过 std::forward 转发

std::forwardstd::move 的区别

特性 std::forward std::move
用途 保留参数的值类别,用于完美转发。 将左值显式地转换为右值引用。
典型场景 用于模板参数和万能引用。 用于移动语义和资源转移。
是否保留值类别 是,参数的值类别由模板参数决定。 否,强制转换为右值引用。
返回类型 与模板参数 T 的值类别一致。 始终返回右值引用。

std::thread

std::thread 库提供了一种标准方式来控制线程,例如生成和终止线程

void foo(bool clause) { /* do something... */ }

std::vector<std::thread> threadsVector;
threadsVector.emplace_back([]() {
  // Lambda function that will be invoked
});
threadsVector.emplace_back(foo, true);  // thread will run foo(true)
for (auto& thread : threadsVector) {
  thread.join(); // Wait for threads to finish
}

std::to_string

将数值参数转换为 std::string

std::to_string(1.2); // == "1.2"
std::to_string(123); // == "123"

类型特征

类型特征(Type traits)用于在编译时检查、修改或操作类型的属性和特性。

特点

  1. 编译时决策
    • Type Traits 提供的功能完全在编译时完成,无运行时开销。
  2. 判断类型特性
    • 检查一个类型是否满足某些条件(如是否为整数类型、是否为指针类型)。
  3. 修改类型
    • 对类型进行某些操作,比如移除指针、移除引用、添加常量修饰符等。
  4. 辅助 SFINAE
    • std::enable_if 和其他模板机制结合,构建更强大的模板代码。

常用 Type Traits

Type Traits 通常定义在 <type_traits> 头文件中。以下是一些常用的 Type Traits。

判断类型特性

Type Trait 功能描述
std::is_integral<T> 判断类型 T 是否为整型
std::is_floating_point<T> 判断类型 T 是否为浮点型
std::is_pointer<T> 判断类型 T 是否为指针类型
std::is_reference<T> 判断类型 T 是否为引用类型
std::is_const<T> 判断类型 T 是否为常量类型
std::is_same<T, U> 判断类型 TU 是否相同
std::is_array<T> 判断类型 T 是否为数组类型
std::is_class<T> 判断类型 T 是否为类类型
std::is_function<T> 判断类型 T 是否为函数类型
#include <iostream>
#include <type_traits>

template <typename T>
void checkTypeTraits() {
    if (std::is_integral<T>::value) {
        std::cout << "T is an integral type.
";
    }
    if (std::is_pointer<T>::value) {
        std::cout << "T is a pointer type.
";
    }
    if (std::is_const<T>::value) {
        std::cout << "T is a const type.
";
    }
}

int main() {
    checkTypeTraits<int>();        // 输出: T is an integral type.
    checkTypeTraits<const int>(); // 输出: T is an integral type. T is a const type.
    checkTypeTraits<int*>();      // 输出: T is a pointer type.
    return 0;
}

修改类型

Type Trait 功能描述
std::remove_const<T> 移除类型 Tconst 修饰符
std::remove_pointer<T> 移除类型 T 的指针修饰符
std::remove_reference<T> 移除类型 T 的引用修饰符
std::add_const<T> 为类型 T 添加 const 修饰符
std::add_pointer<T> 为类型 T 添加指针修饰符
std::add_reference<T> 为类型 T 添加引用修饰符
std::decay<T> 移除数组和函数类型中的修饰符
#include <iostream>
#include <type_traits>

template <typename T>
void modifyTypeTraits() {
    using NonConstType = typename std::remove_const<T>::type;
    using PointerType = typename std::add_pointer<T>::type;
    using ReferenceType = typename std::add_lvalue_reference<T>::type;

    std::cout << "Original type: " << typeid(T).name() << "
";
    std::cout << "Non-const type: " << typeid(NonConstType).name() << "
";
    std::cout << "Pointer type: " << typeid(PointerType).name() << "
";
    std::cout << "Reference type: " << typeid(ReferenceType).name() << "
";
}

int main() {
    modifyTypeTraits<const int>(); // 演示类型修改
    return 0;
}

辅助模板编程

Type Trait 功能描述
std::conditional<Cond, T1, T2> 如果 Condtrue,则类型为 T1,否则为 T2
std::enable_if<Cond, T> 如果 Condtrue,启用模板
std::is_base_of<Base, Derived> 判断 Base 是否为 Derived 的基类
std::integral_constant<T, v> 编译时保存常量值

与 SFINAE 结合

#include <iostream>
#include <type_traits>

template <typename T>
typename std::enable_if<std::is_integral<T>::value>::type
print(T value) {
    std::cout << "Integral value: " << value << "
";
}

template <typename T>
typename std::enable_if<std::is_floating_point<T>::value>::type
print(T value) {
    std::cout << "Floating-point value: " << value << "
";
}

int main() {
    print(42);         // 输出: Integral value: 42
    print(3.14);       // 输出: Floating-point value: 3.14
    // print("Hello"); // 编译错误,不符合任何模板
    return 0;
}

智能指针

封装了原始指针的类,提供自动化的内存管理功能,避免了手动管理内存可能引发的内存泄漏、悬垂指针等问题。智能指针通过RAII(资源获取即初始化)模式,确保在超出作用域时,资源会被安全释放

C++标准库提供了三种主要的智能指针,定义在头文件 <memory>

std::unique_ptr

  • 独占所有权的智能指针,一个对象只能被一个unique_ptr管理。

  • 适合表示独占资源,不能复制

  • 轻量级,适合高性能场景

  • 通过std::move可以将所有权转移

  • 自动释放资源,无需手动调用delete

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource acquired
"; }
    ~Resource() { std::cout << "Resource released
"; }
};

int main() {
    std::unique_ptr<Resource> ptr1 = std::make_unique<Resource>();
    // std::unique_ptr<Resource> ptr2 = ptr1; // 编译错误,不能复制
    std::unique_ptr<Resource> ptr2 = std::move(ptr1); // 转移所有权
    if (!ptr1) {
        std::cout << "ptr1 is now empty
";
    }
    //另一种初始化方式
    std::unique_ptr<Resource> p1 { new Resource{} };
    std::unique_ptr<Resource> p2 {std::move(p1)};
    
    return 0;
}

std::shared_ptr

  • 共享所有权的智能指针,多个shared_ptr可以管理同一个对象,使用引用计数来管理资源生命周期。

  • 使用引用计数,只有当引用计数为零时才会释放资源

  • 适合表示共享资源。

  • 支持赋值和拷贝

  • 使用std::make_shared创建,避免多次分配内存

  • 自动释放资源

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource acquired
"; }
    ~Resource() { std::cout << "Resource released
"; }
};

int main() {
    std::shared_ptr<Resource> ptr1 = std::make_shared<Resource>();
    std::shared_ptr<Resource> ptr2 = ptr1; // 引用计数增加
    std::cout << "Reference count: " << ptr1.use_count() << "
";
    ptr1.reset(); // 减少一个引用
    std::cout << "Reference count after reset: " << ptr2.use_count() << "
";
    return 0; // ptr2超出作用域,资源自动释放
}

std::weak_ptr

  • 辅助shared_ptr使用的弱引用,不影响共享资源的生命周期。

  • 不增加引用计数,只是对shared_ptr的弱引用

  • 解决了shared_ptr的循环引用问题

  • 需要通过lock获取一个有效的shared_ptr

  • 可以判断指向的资源是否还存在

#include <iostream>
#include <memory>

class Resource {
public:
    Resource() { std::cout << "Resource acquired
"; }
    ~Resource() { std::cout << "Resource released
"; }
};

int main() {
    std::shared_ptr<Resource> shared = std::make_shared<Resource>();
    std::weak_ptr<Resource> weak = shared; // 创建弱引用

    if (auto locked = weak.lock()) {
        std::cout << "Resource is still alive
";
    }

    shared.reset(); // 释放资源

    if (weak.expired()) {
        std::cout << "Resource has been released
";
    }
    return 0;
}

注意事项

  1. 避免循环引用
    • std::shared_ptr之间的循环引用会导致资源无法释放。
    • 使用std::weak_ptr打破循环引用。
  2. 避免不必要的性能开销
    • 如果资源是独占的,优先使用std::unique_ptr
    • std::shared_ptr的引用计数管理有一定的性能开销。
  3. 不要混用原始指针和智能指针
    • 避免用智能指针管理已经由原始指针管理的资源,或者反过来。

std::chrono

chrono 库包含一组处理持续时间、时钟和时间点的实用函数和类型。

std::chrono::time_point<std::chrono::steady_clock> start, end;
start = std::chrono::steady_clock::now();
// Some computations...
end = std::chrono::steady_clock::now();

std::chrono::duration<double> elapsed_seconds = end - start;
double t = elapsed_seconds.count(); // t number of seconds, represented as a `double`

元组

元组(Tuples) 是 C++ 标准库提供的一种容器,定义在 <tuple> 头文件中,用于存储一组异构类型的数据。元组可以看作是扩展版的 std::pair,支持多个元素,且每个元素的类型可以不同

主要特点

  1. 异构性
    • 元组可以存储多种不同类型的数据,例如整数、浮点数、字符串等。
  2. 固定大小
    • 元组的大小在编译时确定,不能动态更改。
  3. 类型安全
    • 通过类型索引(而不是名称)访问元组中的元素。
  4. 实用性
    • 常用于函数返回多个不同类型的值。

创建元组

#include <tuple>
#include <iostream>
#include <string>

int main() {
    // 创建元组
    std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");
    
    // 通过 std::make_tuple 创建
    auto anotherTuple = std::make_tuple(1, 2.718, "World");
    
    return 0;
}

访问元组元素

可以使用 std::get<index>(tuple) 按索引访问元素。

#include <tuple>
#include <iostream>

int main() {
    std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");

    // 按索引访问元素
    std::cout << "First element: " << std::get<0>(myTuple) << "
";
    std::cout << "Second element: " << std::get<1>(myTuple) << "
";
    std::cout << "Third element: " << std::get<2>(myTuple) << "
";

    return 0;
}

元组解构

使用 C++17 的结构化绑定直接解构元组

#include <tuple>
#include <iostream>

int main() {
    std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");

    // 解构元组
    auto [intValue, doubleValue, stringValue] = myTuple;

    std::cout << "Integer: " << intValue << "
";
    std::cout << "Double: " << doubleValue << "
";
    std::cout << "String: " << stringValue << "
";

    return 0;
}

修改元组元素

元组的元素可以通过 std::get<index> 修改

#include <tuple>
#include <iostream>

int main() {
    std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");

    // 修改元素
    std::get<0>(myTuple) = 100;
    std::get<2>(myTuple) = "World";

    std::cout << "Updated tuple: (" 
              << std::get<0>(myTuple) << ", "
              << std::get<1>(myTuple) << ", "
              << std::get<2>(myTuple) << ")
";

    return 0;
}

获取元组的大小

使用 std::tuple_size

#include <tuple>
#include <iostream>

int main() {
    std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");

    constexpr size_t tupleSize = std::tuple_size<decltype(myTuple)>::value;
    std::cout << "Size of tuple: " << tupleSize << "
";

    return 0;
}

拼接元组

使用 std::tuple_cat 将多个元组拼接

#include <tuple>
#include <iostream>

int main() {
    std::tuple<int, std::string> tuple1(42, "Hello");
    std::tuple<double, char> tuple2(3.14, "A");

    auto combinedTuple = std::tuple_cat(tuple1, tuple2);

    std::cout << "Combined tuple: (" 
              << std::get<0>(combinedTuple) << ", "
              << std::get<1>(combinedTuple) << ", "
              << std::get<2>(combinedTuple) << ", "
              << std::get<3>(combinedTuple) << ")
";

    return 0;
}

比较元组

元组支持比较操作符(==, !=, <, <=, >, >=

#include <tuple>
#include <iostream>

int main() {
    std::tuple<int, std::string> tuple1(42, "Hello");
    std::tuple<int, std::string> tuple2(42, "World");

    if (tuple1 < tuple2) {
        std::cout << "tuple1 is less than tuple2
";
    } else {
        std::cout << "tuple1 is not less than tuple2
";
    }

    return 0;
}

元组的典型用途

  1. 函数返回多个值

    #include <tuple>
    #include <iostream>
    
    std::tuple<int, double, std::string> getValues() {
        return std::make_tuple(42, 3.14, "Hello");
    }
    
    int main() {
        auto [intValue, doubleValue, stringValue] = getValues();
    
        std::cout << "Integer: " << intValue << "
    ";
        std::cout << "Double: " << doubleValue << "
    ";
        std::cout << "String: " << stringValue << "
    ";
    
        return 0;
    }
    

std::tie

std::tie 是 C++ 标准库中的一个实用工具,定义在头文件 <tuple> 中。它主要用于将元组的元素绑定到多个变量上,从而实现“解构”操作

主要功能

  1. 解构元组

    • 使用 std::tie 将元组中的元素绑定到多个变量。

      #include <iostream>
      #include <tuple>
      
      int main() {
          std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");
      
          int intValue;
          double doubleValue;
          std::string stringValue;
      
          // 使用 std::tie 解构元组
          std::tie(intValue, doubleValue, stringValue) = myTuple;
      
          std::cout << "Integer: " << intValue << "
      ";
          std::cout << "Double: " << doubleValue << "
      ";
          std::cout << "String: " << stringValue << "
      ";
      
          return 0;
      }
      
      
  2. 忽略部分元素

    • 使用 std::ignore 忽略不需要的元组元素。

      #include <iostream>
      #include <tuple>
      
      int main() {
          std::tuple<int, double, std::string> myTuple(42, 3.14, "Hello");
      
          int intValue;
          std::string stringValue;
      
          // 忽略中间的 double 元素
          std::tie(intValue, std::ignore, stringValue) = myTuple;
      
          std::cout << "Integer: " << intValue << "
      ";
          std::cout << "String: " << stringValue << "
      ";
      
          return 0;
      }
      
      
  3. 与函数返回值结合

    • 常用于从返回元组的函数中提取多个值,使用 std::tie 提取其中的值

      #include <iostream>
      #include <tuple>
      
      // 返回多个值的函数
      std::tuple<int, double, std::string> getValues() {
          return std::make_tuple(42, 3.14, "Hello");
      }
      
      int main() {
          int intValue;
          double doubleValue;
          std::string stringValue;
      
          // 使用 std::tie 解构函数返回的元组
          std::tie(intValue, doubleValue, stringValue) = getValues();
      
          std::cout << "Integer: " << intValue << "
      ";
          std::cout << "Double: " << doubleValue << "
      ";
          std::cout << "String: " << stringValue << "
      ";
      
          return 0;
      }
      

      注意事项

      1. 变量的引用关系

        std::tie 实际上绑定了变量的引用。如果对绑定的变量进行修改,会影响元组中的内容(前提是元组也是引用类型)

        #include <iostream>
        #include <tuple>
        
        int main() {
            int a = 42, b = 100;
            std::tuple<int&, int&> myTuple = std::tie(a, b);
        
            std::get<0>(myTuple) = 200; // 修改元组中的值
            std::cout << "a: " << a << "
        "; // 输出 200
        
            return 0;
        }
        
        
      2. 不能用于非可修改的类型

        如果需要解构const元组,std::tie无法使用,需要考虑使用结构化绑定(C++17)

      3. 推荐在需要引用的场景中使用

        std::tie 更适合那些希望通过引用访问元组元素的情况。如果不需要引用,直接使用结构化绑定更简洁

      与结构化绑定的对比

      结构化绑定是一种更现代、更直观的方式,用于解构元组,避免了对 std::tiestd::get 的依赖

      #include <iostream>
      #include <tuple>
      
      int main() {
          auto myTuple = std::make_tuple(42, 3.14, "Hello");
      
          // 使用结构化绑定(推荐)
          auto [intValue, doubleValue, stringValue] = myTuple;
      
          std::cout << "Integer: " << intValue << "
      ";
          std::cout << "Double: " << doubleValue << "
      ";
          std::cout << "String: " << stringValue << "
      ";
      
          return 0;
      }
      

std::array

std::array 是一个基于 C 风格数组构建的容器,用来表示一个固定大小的数组

std::array<Type, Size> name;
  • Type 是数组元素的类型。
  • Size 是数组的大小,必须是一个常量表达式。

成员函数

  1. at(index):安全地访问指定位置的元素,带越界检查。
  2. operator[]:访问元素,不做越界检查。
  3. front() / back():返回第一个或最后一个元素。
  4. size():返回数组的大小。
  5. fill(value):将数组中所有元素设置为 value
  6. data():返回底层数组的指针。

示例

#include <iostream>
#include <array>

int main() {
    // 定义一个大小为5的std::array
    std::array<int, 5> myArray = {1, 2, 3, 4, 5};

    // 使用[]访问元素
    std::cout << "使用[]访问元素:" << std::endl;
    for (size_t i = 0; i < myArray.size(); ++i) {
        std::cout << myArray[i] << " ";
    }
    std::cout << std::endl;

    // 使用at()访问元素
    std::cout << "使用at()访问元素:" << std::endl;
    for (size_t i = 0; i < myArray.size(); ++i) {
        std::cout << myArray.at(i) << " ";
    }
    std::cout << std::endl;

    // 修改元素
    myArray[0] = 10;
    myArray.at(1) = 20;
    std::cout << "修改后数组内容: ";
    for (const auto& value : myArray) {
        std::cout << value << " ";
    }
    std::cout << std::endl;

    // 使用front()和back()
    std::cout << "第一个元素: " << myArray.front() << std::endl;
    std::cout << "最后一个元素: " << myArray.back() << std::endl;

    // 使用fill()重置元素
    myArray.fill(42);
    std::cout << "使用fill()后的数组内容: ";
    for (const auto& value : myArray) {
        std::cout << value << " ";
    }
    std::cout << std::endl;

    // 获取底层指针
    int* ptr = myArray.data();
    std::cout << "底层数组地址: " << ptr << std::endl;

    return 0;
}

与C风格数组的对比

功能 C风格数组 std::array
类型安全性 较低 较高
越界检查 at()有越界检查
STL兼容性 需要手动适配 天生兼容
拷贝赋值操作 需手动实现 自动支持
是否动态分配内存

无序容器

无序容器(unordered containers),基于哈希表(hash table)实现,用于存储无序的键值对或集合。

std::unordered_map

  • 存储键值对(key-value)。
  • 键唯一,值可重复。
  • 类似于 std::map,但无序。
  • 用途:快速查找键对应的值。

std::unordered_multimap

  • 类似于 std::unordered_map
  • 允许键重复(同一键可以有多个值)。

std::unordered_set

  • 存储唯一的键,键不可重复。
  • 类似于 std::set,但无序。
  • 用途:快速查找某个值是否存在。

std::unordered_multiset

  • 类似于 std::unordered_set
  • 允许键重复。

特点

  • 底层实现:基于哈希表,平均时间复杂度为 O(1)。
  • 无序存储:元素的存储顺序不保证(内部依赖哈希值)。
  • 哈希函数:需要对键进行哈希运算,默认使用 std::hash
  • 桶分布(bucket)
    • 哈希表将元素分散到不同的桶中,每个桶存储多个元素。
    • 提供接口查看和调整桶的数量。

示例

std::unordered_map 示例

#include <iostream>
#include <unordered_map>
#include <string>

int main() {
    // 创建unordered_map
    std::unordered_map<std::string, int> umap;

    // 插入元素
    umap["Alice"] = 25;
    umap["Bob"] = 30;
    umap["Charlie"] = 35;

    // 遍历unordered_map
    for (const auto& pair : umap) {
        std::cout << pair.first << ": " << pair.second << std::endl;
    }

    // 查找元素
    auto it = umap.find("Alice");
    if (it != umap.end()) {
        std::cout << "Found Alice, age: " << it->second << std::endl;
    }

    return 0;
}

std::unordered_set 示例

#include <iostream>
#include <unordered_set>

int main() {
    // 创建unordered_set
    std::unordered_set<int> uset = {1, 2, 3, 4, 5};

    // 插入元素
    uset.insert(6);

    // 查找元素
    if (uset.find(3) != uset.end()) {
        std::cout << "Found 3 in the set." << std::endl;
    }

    // 遍历unordered_set
    for (int num : uset) {
        std::cout << num << " ";
    }
    std::cout << std::endl;

    return 0;
}

重要接口

公共成员函数

函数 作用
insert() 插入元素
erase() 删除元素
find() 查找元素,返回迭代器
clear() 清空容器
size() 返回容器中的元素个数
empty() 判断容器是否为空

哈希相关函数

函数 作用
bucket_count() 返回桶的数量
bucket_size(bucket) 返回指定桶中元素的数量
load_factor() 返回负载因子(size / bucket_count
rehash(n) 重置桶数量,至少为 n
reserve(n) 重新分配空间,确保能容纳至少 n 个元素

与有序容器的比较

特性 无序容器 (unordered_*) 有序容器 (map, set)
存储顺序 无序 按键值顺序
时间复杂度(查找) 平均 O(1) O(log N)
底层结构 哈希表 红黑树
支持排序操作 不支持 支持

std::make_shared

推荐使用 std::make_shared 来创建 std::shared_ptr 实例

std::ref

用于创建对对象的引用包装器(reference wrapper),提供一种安全的方式将引用存储在容器或函数对象中。它返回一个 std::reference_wrapper 类型的对象

std::ref 的主要功能是为引用类型创建一个可拷贝的包装器。因为引用本身不是对象,不能直接存储在 STL 容器中或传递给某些函数对象,而 std::reference_wrapper 提供了这种能力。

  • std::ref:用于包装左值引用。
  • std::cref:用于包装左值的 const 引用。

语法

#include <functional>

std::reference_wrapper<T> std::ref(T& t);
std::reference_wrapper<const T> std::cref(const T& t);
  • T& t 是需要包装的左值引用。

  • 返回值是一个 std::reference_wrapper<T> 对象。

常见用途

  1. 存储引用在 STL 容器中
    • 普通引用不能直接存储在容器中,而 std::ref 可以用来间接存储引用。
  2. 函数调用
    • 在函数调用中需要传递引用而不是拷贝时,可以使用 std::ref

示例

  1. 在容器中存储引用

    #include <iostream>
    #include <vector>
    #include <functional> // std::ref
    
    int main() {
        int a = 10, b = 20, c = 30;
    
        // 用std::ref将引用存入容器
        std::vector<std::reference_wrapper<int>> refs = {std::ref(a), std::ref(b), std::ref(c)};
    
        // 修改容器中的值会影响原变量
        for (auto& ref : refs) {
            ref.get() += 1;  // 使用.get()访问引用
        }
    
        std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;
    
        return 0;
    }
    
    
  2. 结合 std::bind 使用

    std::ref 可以用来绑定引用以避免拷贝

    #include <iostream>
    #include <functional> // std::bind, std::ref
    
    void print_and_increment(int& x) {
        std::cout << "x: " << x << std::endl;
        x++;
    }
    
    int main() {
        int value = 42;
    
        // 使用std::bind绑定引用
        auto bound_func = std::bind(print_and_increment, std::ref(value));
    
        bound_func(); // 调用时不会拷贝value
        std::cout << "Value after increment: " << value << std::endl;
    
        return 0;
    }
    
    
  3. 传递引用给线程

    在多线程编程中,std::ref 常用于向线程传递引用。

    #include <iostream>
    #include <thread>
    #include <functional>
    
    void increment(int& x) {
        x++;
    }
    
    int main() {
        int value = 0;
    
        // 使用std::ref传递引用给线程
        std::thread t(increment, std::ref(value));
        t.join();
    
        std::cout << "Value after thread increment: " << value << std::endl;
    
        return 0;
    }
    
    

std::ref 的返回值是 std::reference_wrapper 类型。std::reference_wrapper 提供了一些关键功能:

  1. get():访问包装的引用。
  2. 隐式转换为引用类型,方便使用。
  3. 支持拷贝、赋值操作。
#include <iostream>
#include <functional>

int main() {
    int x = 100;
    std::reference_wrapper<int> ref = std::ref(x);

    // 修改原变量
    ref.get() = 200;

    std::cout << "x: " << x << std::endl;  // 输出: x: 200

    return 0;
}

注意事项

  1. std::ref 仅支持左值引用

    如果传递的是临时对象(右值),编译会失败。

    int x = 10;
    std::ref(x); // 正确
    std::ref(10); // 错误,10 是右值
    
  2. 需要调用 get() 明确访问引用

    在某些场景下(如容器操作)需要使用 .get() 来显式访问包装的引用。

  3. 避免悬空引用

    如果引用的原始对象已经销毁,std::ref 将指向无效内存,导致未定义行为。

内存模型

内存模型(Memory Model) 定义了程序在多线程环境下如何共享和访问内存。它决定了不同线程间对变量的操作是否能够安全且一致地被观察到,并提供了相应的规则和工具来保证线程安全

内存中的共享状态

  • 共享状态指的是多个线程可以同时访问的内存(例如全局变量或堆上的动态分配内存)。
  • 在多线程环境中,多个线程可能同时读取或修改同一共享变量,导致竞争条件(Race Condition)。

数据竞争(Data Race)

  • 如果两个线程在没有同步机制的情况下同时访问同一共享变量,且至少有一个线程对该变量进行了写操作,则会发生数据竞争。
  • 数据竞争会导致未定义行为。

解决方案

  • 使用同步原语(例如 std::mutex)。
  • 使用 std::atomic 类型或合适的内存序(Memory Order)。

内存序(Memory Ordering)

C++ 内存模型定义了 内存序(Memory Order),它描述了线程之间对内存操作的顺序约束。主要包括:

内存序的分类

  1. std::memory_order_relaxed
    • 不保证操作的顺序,仅保证操作是原子的。
    • 用于性能要求极高但对操作顺序无严格需求的场景。
    • 不提供任何跨线程的同步
  2. memory_order_consume
    • 保证对当前操作依赖的数据的访问顺序
    • 主要用于读取依赖数据时,确保相关数据的正确性
    • 本线程中,所有后续的有关本原子类型的操作,必须在本条原子操作完成之后执行
  3. std::memory_order_acquire
    • 当前线程在执行该操作后,能保证所有之前的操作对当前线程可见。
    • 通常用于获取锁的场景。
    • 本线程中,所有后续的读操作必须在本条原子操作完成后执行
  4. std::memory_order_release
    • 当前线程在执行该操作前,能保证所有之前的操作对其他线程可见。
    • 通常用于释放锁的场景。
    • 本线程中,所有之前的写操作完成后才能执行本条原子操作
  5. std::memory_order_acq_rel
    • 同时具备 acquirerelease 的语义。
    • 用于读写操作。
  6. std::memory_order_seq_cst(默认)
    • 顺序一致性模型,保证全局的操作顺序性。
    • 简单且安全,但可能会有性能开销。

原子操作(Atomic Operation)

  • C++ 提供了 std::atomic 类型来确保对共享变量的操作是线程安全的。
  • 原子操作不会被打断或中断,确保操作的完整性。
#include <iostream>
#include <atomic>
#include <thread>

std::atomic<int> counter(0);

void increment() {
    for (int i = 0; i < 1000; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed);
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}

同步与屏障

C++ 提供了多种工具来实现线程同步:

  • std::mutex:用于保护临界区。

  • std::condition_variable:实现线程间的等待和通知。

  • 内存屏障(Memory Fence)

    使用 std::atomic_thread_fence 提供显式的内存屏障。

#include <atomic>
#include <iostream>
#include <thread>

std::atomic<bool> ready(false);
int data = 0;

void producer() {
    data = 42; // 写操作
    std::atomic_thread_fence(std::memory_order_release);
    ready.store(true, std::memory_order_relaxed);
}

void consumer() {
    while (!ready.load(std::memory_order_relaxed)); // 自旋等待
    std::atomic_thread_fence(std::memory_order_acquire);
    std::cout << "Data: " << data << std::endl; // 读取操作
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);

    t1.join();
    t2.join();
    return 0;
}

指针与内存模型

在多线程环境中,直接使用裸指针进行共享数据访问可能导致数据竞争。可以通过以下方法改善:

  • 使用智能指针(如 std::shared_ptr)。
  • 对指针的访问和修改采用原子操作(std::atomic<T*>)。

std::async

用于启动异步任务并返回一个 std::future 对象,这个对象可以用于获取任务的结果。std::async 使得任务能够在后台并发执行,而主线程或其他线程可以继续执行其他工作

示例

#include <iostream>
#include <future>
#include <thread>

int find_square(int x) {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
    return x * x;
}

int main() {
    // 启动异步任务,计算 10 的平方
    std::future<int> result = std::async(std::launch::async, find_square, 10);

    // 可以在这里做其他工作
    std::cout << "Doing other work while waiting for the result...
";

    // 获取异步任务的结果
    std::cout << "The square of 10 is: " << result.get() << std::endl;

    return 0;
}

延迟执行

#include <iostream>
#include <future>
#include <thread>

int find_square(int x) {
    std::this_thread::sleep_for(std::chrono::seconds(2)); // 模拟耗时操作
    return x * x;
}

int main() {
    // 启动延迟执行的任务
    std::future<int> result = std::async(std::launch::deferred, find_square, 10);

    // 可以在这里做其他工作
    std::cout << "Doing other work while waiting for the result...
";

    // 获取异步任务的结果,只有此时任务才会执行
    std::cout << "The square of 10 is: " << result.get() << std::endl;

    return 0;
}

  • 使用 std::launch::deferred 时,异步任务并不会立即启动,直到你调用 result.get() 时,任务才会在当前线程中执行
  • std::async 在这种模式下不会启动新的线程,而是将任务推迟到 get() 调用时执行

std::function

#include <functional>

std::function<返回类型(参数类型...)> func;
  1. 类型安全
    • std::function 封装了可调用对象,确保在调用时参数和返回值符合定义的签名。
  2. 支持任意可调用对象
    • 包括普通函数、Lambda 表达式、函数对象、成员函数指针等。
  3. 动态分配
    • std::function 内部使用了动态分配,可能引入额外的性能开销。
  4. 默认构造
    • 默认构造的 std::function 对象是空的,调用时会抛出 std::bad_function_call 异常。

示例

  1. 存储普通函数

    #include <iostream>
    #include <functional>
    
    void printMessage(const std::string& message) {
        std::cout << message << std::endl;
    }
    
    int main() {
        std::function<void(const std::string&)> func = printMessage;
        func("Hello, std::function!");  // 调用存储的普通函数
        return 0;
    }
    
  2. 存储Lambdai表达式

    #include <iostream>
    #include <functional>
    
    int main() {
        std::function<int(int, int)> add = [](int a, int b) {
            return a + b;
        };
    
        std::cout << "Sum: " << add(3, 5) << std::endl;  // 输出:Sum: 8
        return 0;
    }
    
  3. 存储函数对象

    #include <iostream>
    #include <functional>
    
    struct Multiplier {
        int factor;
        Multiplier(int f) : factor(f) {}
    
        int operator()(int value) const {
            return value * factor;
        }
    };
    
    int main() {
        std::function<int(int)> multiplyBy2 = Multiplier(2);
        std::cout << multiplyBy2(10) << std::endl;  // 输出:20
        return 0;
    }
    
  4. 存储成员函数

    #include <iostream>
    #include <functional>
    
    class MyClass {
    public:
        void printMessage(const std::string& msg) const {
            std::cout << "MyClass: " << msg << std::endl;
        }
    };
    
    int main() {
        MyClass obj;
        std::function<void(const MyClass&, const std::string&)> func = &MyClass::printMessage;
        func(obj, "Hello from member function!");
        return 0;
    }
    
  5. 动态切换可调用对象

    #include <iostream>
    #include <functional>
    
    void func1() {
        std::cout << "Function 1" << std::endl;
    }
    
    void func2() {
        std::cout << "Function 2" << std::endl;
    }
    
    int main() {
        std::function<void()> func;
    
        func = func1;
        func();  // 输出:Function 1
    
        func = func2;
        func();  // 输出:Function 2
    
        return 0;
    }
    

std::bindstd::placeholder

std::bind用于创建函数对象。它可以将一个函数(或可调用对象)的参数绑定为特定值或占位符,以生成一个新的可调用对象。

功能

  • 部分应用:固定函数的一部分参数,生成一个新的可调用对象。
  • 参数重排:改变参数的顺序。
  • 参数占位:用占位符指定调用时需要提供的参数。
#include <functional>
std::bind(Function, arg1, arg2, ..., argN)
  • Function:目标函数或可调用对象。

  • arg1, arg2, ..., argN:

    • 具体值:绑定为固定的参数值。
    • 占位符:通过std::placeholders::_1等形式表示,调用时动态传入。

占位符格式为:std::placeholders::_NN从1开始。

示例

  1. 绑定普通函数的参数

    #include <iostream>
    #include <functional> // std::bind
    using namespace std;
    
    void greet(const string& name, int age) {
        cout << "Hello, " << name << "! You are " << age << " years old." << endl;
    }
    
    int main() {
        // 绑定 name 参数为 "Alice"
        auto greetAlice = std::bind(greet, "Alice", std::placeholders::_1);
    
        // 调用时只需提供 age 参数
        greetAlice(25); // 输出:Hello, Alice! You are 25 years old.
    
        return 0;
    }
    
  2. 改变参数顺序

    #include <iostream>
    #include <functional>
    using namespace std;
    
    void subtract(int a, int b) {
        cout << "Result: " << a - b << endl;
    }
    
    int main() {
        // 交换参数的顺序
        auto reversedSubtract = std::bind(subtract, std::placeholders::_2, std::placeholders::_1);
    
        reversedSubtract(10, 20); // 输出:Result: 10
    
        return 0;
    }
    
  3. 绑定成员函数

    #include <iostream>
    #include <functional>
    using namespace std;
    
    class Calculator {
    public:
        void add(int a, int b) const {
            cout << "Sum: " << a + b << endl;
        }
    };
    
    int main() {
        Calculator calc;
    
        // 绑定成员函数,传入对象实例
        auto addFunc = std::bind(&Calculator::add, &calc, std::placeholders::_1, std::placeholders::_2);
    
        addFunc(10, 20); // 输出:Sum: 30
    
        return 0;
    }
    
  4. 结合std::bindstd::function

    #include <iostream>
    #include <functional>
    using namespace std;
    
    int multiply(int a, int b) {
        return a * b;
    }
    
    int main() {
        // 使用 std::bind
        auto doubleValue = std::bind(multiply, std::placeholders::_1, 2);
    
        // 存储到 std::function
        std::function<int(int)> func = doubleValue;
    
        cout << "5 * 2 = " << func(5) << endl; // 输出:5 * 2 = 10
    
        return 0;
    }
    
    

[!note]
Lambda可以替代std::bind

auto greetAlice = [](int age) { greet("Alice", age); };

原文地址:https://www.cnblogs.com/midraos/p/18832424