跳转至

现代C++(17/20/23)新特性

🎯 学习目标

完成本章学习后,你将能够: - 掌握C++17的核心语言特性和标准库扩展 - 深入理解C++20四大革命性特性:Concepts、Ranges、Coroutines、Modules - 了解C++23的最新实用特性 - 能够在实际工程中合理选择和使用现代C++特性 - 掌握面试中高频考察的现代C++知识点

现代C++17-20-23路线图

上图按版本阶段整理了现代 C++ 的学习路径,帮助你分层掌握特性演进。


一、C++17 核心特性

1.1 结构化绑定(Structured Bindings)📋

允许用一条声明语句同时绑定多个变量到结构体/tuple/数组的成员。

C++
#include <iostream>
#include <map>
#include <tuple>

// 旧写法
std::pair<int, std::string> get_info_old() {
    return {42, "hello"};
}

void old_style() {
    auto result = get_info_old();
    int id = result.first;
    std::string name = result.second;
}

// ✅ C++17 新写法
std::tuple<int, std::string, double> get_info() {
    return {42, "Alice", 3.14};
}

int main() {
    // 绑定tuple
    auto [id, name, score] = get_info();  // auto 自动类型推断 + 结构化绑定(C++17)
    std::cout << id << " " << name << " " << score << std::endl;

    // 绑定map遍历
    std::map<std::string, int> scores = {{"Alice", 95}, {"Bob", 87}};
    for (const auto& [key, value] : scores) {
        std::cout << key << ": " << value << std::endl;
    }

    // 绑定数组
    int arr[] = {1, 2, 3};
    auto [a, b, c] = arr;
    std::cout << a << " " << b << " " << c << std::endl;

    // 绑定结构体
    struct Point { double x, y, z; };
    Point p{1.0, 2.0, 3.0};
    auto [x, y, z] = p;

    return 0;
}

1.2 if/switch 初始化语句

C++
#include <iostream>
#include <map>
#include <mutex>

int main() {
    std::map<std::string, int> m = {{"key1", 100}};

    // 旧写法
    // auto it = m.find("key1");
    // if (it != m.end()) { ... }

    // ✅ C++17:if with initializer
    if (auto it = m.find("key1"); it != m.end()) {
        std::cout << "Found: " << it->second << std::endl;
        // it 的作用域仅在 if-else 块内
    } else {
        std::cout << "Not found" << std::endl;
    }
    // it 在这里不可见,避免命名空间污染

    // switch with initializer
    enum class Color { Red, Green, Blue };
    switch (Color c = Color::Green; c) {
        case Color::Red:   std::cout << "Red" << std::endl; break;
        case Color::Green: std::cout << "Green" << std::endl; break;
        case Color::Blue:  std::cout << "Blue" << std::endl; break;
    }

    return 0;
}

1.3 std::optional / std::variant / std::any 📋

C++
#include <iostream>
#include <optional>
#include <variant>
#include <any>
#include <string>
#include <vector>

// === std::optional:可能有值也可能没有 ===
// 旧写法:返回特殊值(-1)或指针(nullptr)表示"无值"
// ✅ C++17:用optional语义清晰地表达

std::optional<int> find_even(const std::vector<int>& v) {  // std::optional 可能有值也可能没有(C++17)
    for (int x : v) {  // 范围for循环,遍历容器元素
        if (x % 2 == 0) return x;  // 隐式构造optional
    }
    return std::nullopt;  // 表示"没有值"
}

// === std::variant:类型安全的联合体 ===
// 旧写法:union + tag,类型不安全
// ✅ C++17:variant编译期类型安全

using JsonValue = std::variant<int, double, std::string, bool>;  // std::variant 类型安全的联合体(C++17)

void print_json(const JsonValue& val) {
    // 使用std::visit + 重载的lambda
    std::visit([](const auto& v) {
        std::cout << v << std::endl;
    }, val);
}

int main() {
    // optional
    std::vector<int> v1 = {1, 3, 5};
    if (auto result = find_even(v1); result.has_value()) {
        std::cout << "Found even: " << *result << std::endl;
    } else {
        std::cout << "No even number" << std::endl;
    }

    // value_or: 提供默认值
    auto val = find_even(v1).value_or(-1);
    std::cout << "Result: " << val << std::endl;  // -1

    // variant
    JsonValue j1 = 42;
    JsonValue j2 = 3.14;
    JsonValue j3 = std::string("hello");

    print_json(j1);  // 42
    print_json(j2);  // 3.14
    print_json(j3);  // hello

    // 获取当前持有的类型索引
    std::cout << "j1 index: " << j1.index() << std::endl;  // 0 (int)

    // std::get_if 安全获取
    if (auto* p = std::get_if<int>(&j1)) {
        std::cout << "j1 is int: " << *p << std::endl;
    }

    // === std::any:可以持有任何类型 ===
    std::any a = 42;
    std::cout << std::any_cast<int>(a) << std::endl;  // 42

    a = std::string("hello");
    std::cout << std::any_cast<std::string>(a) << std::endl;  // hello

    // 类型检查
    std::cout << a.type().name() << std::endl;

    return 0;
}

📋 面试要点:optional vs 指针返回——optional值语义无堆分配,语义更清晰;variant vs union——variant类型安全,析构正确处理;variant vs any——variant编译期类型列表已知,any完全动态。

1.4 std::string_view 📋

C++
#include <iostream>
#include <string>
#include <string_view>

// 旧写法:接受const string&会触发临时对象构造
// void process_old(const std::string& s) { ... }
// process_old("hello");  // "hello" -> 构造临时std::string

// ✅ C++17:string_view是只读视图,零拷贝
void process(std::string_view sv) {
    std::cout << "Length: " << sv.length() << std::endl;
    std::cout << "Substr: " << sv.substr(0, 5) << std::endl;
}

int main() {
    std::string s = "Hello, World!";
    const char* cs = "Hello, C-string!";

    // 都不产生拷贝
    process(s);    // 从std::string
    process(cs);   // 从C字符串
    process("Hello, literal!");  // 从字符串字面量

    // ⚠️ 注意:string_view不拥有数据!
    // std::string_view dangerous() {
    //     std::string local = "oops";
    //     return local;  // ❌ 悬垂引用!
    // }

    return 0;
}

1.5 constexpr if —— 编译期分支 📋

C++
#include <iostream>
#include <type_traits>
#include <string>

// 旧写法:需要模板特化或SFINAE
// ✅ C++17:constexpr if 编译期选择分支

template <typename T>  // 函数模板,T为类型参数
std::string type_to_string(const T& value) {
    if constexpr (std::is_integral_v<T>) {  // constexpr if 编译期条件分支(C++17)
        return "Integer: " + std::to_string(value);
    } else if constexpr (std::is_floating_point_v<T>) {
        return "Float: " + std::to_string(value);
    } else if constexpr (std::is_same_v<T, std::string>) {
        return "String: " + value;
    } else {
        return "Unknown type";
    }
    // 不满足条件的分支不会被编译,所以不会报错
}

// 实用场景:遍历tuple
template <typename Tuple, std::size_t... Is>
void print_tuple_impl(const Tuple& t, std::index_sequence<Is...>) {
    ((std::cout << (Is == 0 ? "" : ", ") << std::get<Is>(t)), ...);
}

template <typename... Args>
void print_tuple(const std::tuple<Args...>& t) {
    std::cout << "(";
    print_tuple_impl(t, std::index_sequence_for<Args...>{});
    std::cout << ")" << std::endl;
}

int main() {
    std::cout << type_to_string(42) << std::endl;
    std::cout << type_to_string(3.14) << std::endl;
    std::cout << type_to_string(std::string("hello")) << std::endl;

    auto t = std::make_tuple(1, "hello", 3.14);
    print_tuple(t);  // (1, hello, 3.14)

    return 0;
}

1.6 折叠表达式(Fold Expressions)

C++
#include <iostream>
#include <string>

// 旧写法:递归模板展开
// template <typename T>
// T sum_old(T t) { return t; }
// template <typename T, typename... Args>
// T sum_old(T first, Args... rest) { return first + sum_old(rest...); }

// ✅ C++17:折叠表达式
template <typename... Args>
auto sum(Args... args) {
    return (args + ...);  // 右折叠: a1 + (a2 + (a3 + a4))
}

template <typename... Args>
auto sum_left(Args... args) {
    return (... + args);  // 左折叠: ((a1 + a2) + a3) + a4
}

// 打印所有参数
template <typename... Args>
void print_all(Args&&... args) {
    (std::cout << ... << args) << std::endl;
    // 等价于: ((std::cout << arg1) << arg2) << arg3
}

// 带分隔符打印
template <typename... Args>
void print_with_sep(Args&&... args) {
    ((std::cout << args << " "), ...) ;  // 逗号折叠: 展开为 (cout<<a1<<" "), (cout<<a2<<" "), ... 逗号运算符依次执行
    std::cout << std::endl;
}

// 复合条件检查
template <typename... Args>
bool all_positive(Args... args) {
    return (... && (args > 0));
}

int main() {
    std::cout << sum(1, 2, 3, 4, 5) << std::endl;  // 15
    print_all("Hello", " ", "World", "!");  // Hello World!
    print_with_sep(1, 2.5, "three", 4);    // 1 2.5 three 4

    std::cout << std::boolalpha;
    std::cout << all_positive(1, 2, 3) << std::endl;    // true
    std::cout << all_positive(1, -2, 3) << std::endl;   // false

    return 0;
}

1.7 std::filesystem

C++
#include <iostream>
#include <filesystem>

namespace fs = std::filesystem;

int main() {
    // 路径操作
    fs::path p = "/home/user/documents/report.txt";
    std::cout << "filename: " << p.filename() << std::endl;       // report.txt
    std::cout << "stem: " << p.stem() << std::endl;               // report
    std::cout << "extension: " << p.extension() << std::endl;     // .txt
    std::cout << "parent: " << p.parent_path() << std::endl;      // /home/user/documents

    // 遍历目录
    fs::path dir = fs::current_path();
    for (const auto& entry : fs::directory_iterator(dir)) {
        if (entry.is_regular_file()) {
            std::cout << entry.path().filename()
                      << " (" << entry.file_size() << " bytes)" << std::endl;
        }
    }

    // 递归遍历
    // for (const auto& entry : fs::recursive_directory_iterator(dir)) { ... }

    // 创建目录
    // fs::create_directories("a/b/c");

    // 复制文件
    // fs::copy("source.txt", "dest.txt", fs::copy_options::overwrite_existing);

    // 判断文件是否存在
    if (fs::exists("test.txt")) {
        std::cout << "test.txt exists" << std::endl;
    }

    return 0;
}

1.8 并行STL算法(std::execution)

C++
#include <iostream>
#include <vector>
#include <algorithm>
#include <numeric>
#include <execution>

int main() {
    std::vector<int> v(10000000);
    std::iota(v.begin(), v.end(), 0);

    // 顺序执行(传统)
    std::sort(std::execution::seq, v.begin(), v.end());

    // 并行执行
    std::sort(std::execution::par, v.begin(), v.end());

    // 并行 + 向量化
    std::sort(std::execution::par_unseq, v.begin(), v.end());

    // 并行reduce(对应accumulate)
    long long sum = std::reduce(std::execution::par,
                                 v.begin(), v.end(), 0LL);
    std::cout << "Sum: " << sum << std::endl;

    // 并行transform_reduce(map-reduce模式)
    long long sum_sq = std::transform_reduce(
        std::execution::par,
        v.begin(), v.end(),
        0LL,                          // 初始值
        std::plus<>(),                // reduce操作
        [](long long x) { return x * x; }  // transform操作
    );

    return 0;
}

二、C++20 革命性特性

2.1 概念(Concepts)📋

Concepts是C++20最重要的特性之一,提供了对模板参数的约束,让模板错误信息变得可读。

C++
#include <iostream>
#include <concepts>
#include <vector>
#include <string>
#include <type_traits>

// === 旧写法:SFINAE(复杂且错误信息糟糕)===
// template <typename T,
//           typename = std::enable_if_t<std::is_integral_v<T>>>
// T gcd_old(T a, T b) { ... }

// ✅ C++20:Concepts

// 方式1:使用标准库概念
template <std::integral T>  // Concepts约束模板参数(C++20)
T gcd(T a, T b) {
    while (b != 0) {
        T temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

// 方式2:自定义concept
template <typename T>
concept Printable = requires(T t) {
    { std::cout << t } -> std::same_as<std::ostream&>;
};

template <typename T>
concept Numeric = std::is_arithmetic_v<T>;

template <typename T>
concept Sortable = requires(T t) {
    { t.begin() } -> std::input_or_output_iterator;
    { t.end() } -> std::input_or_output_iterator;
    { t.size() } -> std::convertible_to<std::size_t>;
    // 嵌套requires: 外层requires是约束子句,内层requires(...)是表达式(测试花括号内表达式是否合法)
    requires requires(typename T::value_type a, typename T::value_type b) {
        { a < b } -> std::convertible_to<bool>;
    };
};

// 方式3:requires子句
template <typename T>
    requires Printable<T> && Numeric<T>
void print_number(T val) {
    std::cout << "Number: " << val << std::endl;
}

// 方式4:简写(auto + concept)
void print_anything(Printable auto const& val) {
    std::cout << val << std::endl;
}

// 复合concept
template <typename C>
concept Container = requires(C c) {
    typename C::value_type;
    typename C::iterator;
    { c.begin() } -> std::same_as<typename C::iterator>;
    { c.end() } -> std::same_as<typename C::iterator>;
    { c.size() } -> std::convertible_to<std::size_t>;
    { c.empty() } -> std::same_as<bool>;
};

template <Container C>
void print_container(const C& c) {
    for (const auto& elem : c) {
        std::cout << elem << " ";
    }
    std::cout << std::endl;
}

int main() {
    std::cout << gcd(12, 8) << std::endl;     // 4
    // gcd(3.14, 2.0);  // ❌ 编译错误:double不满足integral概念
    // 错误信息清晰:"constraints not satisfied"

    print_number(42);
    print_number(3.14);
    // print_number("hello");  // ❌ string不满足Numeric

    std::vector<int> v = {1, 2, 3};
    print_container(v);

    return 0;
}

📋 面试要点:Concepts vs SFINAE vs static_assert——Concepts在声明处就约束类型,编译错误信息更友好;SFINAE在重载决议阶段排除不匹配模板;static_assert在函数体内检查。C++20推荐Concepts。

2.2 范围(Ranges)📋

Ranges库提供了懒求值的管道式操作,极大提升代码可读性。

C++
#include <iostream>
#include <vector>
#include <ranges>
#include <algorithm>
#include <string>

int main() {
    std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // === 旧写法 ===
    // std::vector<int> evens;
    // std::copy_if(v.begin(), v.end(), std::back_inserter(evens),
    //              [](int x) { return x % 2 == 0; });
    // std::transform(evens.begin(), evens.end(), evens.begin(),
    //                [](int x) { return x * x; });

    // ✅ C++20 Ranges:管道操作符,懒求值
    auto result = v
        | std::views::filter([](int x) { return x % 2 == 0; })   // 过滤偶数
        | std::views::transform([](int x) { return x * x; });     // 平方

    for (int x : result) {
        std::cout << x << " ";  // 4 16 36 64 100
    }
    std::cout << std::endl;

    // views是懒求值的,不会产生临时容器!

    // 常用views
    // take: 取前n个
    for (int x : v | std::views::take(3)) {
        std::cout << x << " ";  // 1 2 3
    }
    std::cout << std::endl;

    // drop: 跳过前n个
    for (int x : v | std::views::drop(7)) {
        std::cout << x << " ";  // 8 9 10
    }
    std::cout << std::endl;

    // reverse
    for (int x : v | std::views::reverse | std::views::take(3)) {
        std::cout << x << " ";  // 10 9 8
    }
    std::cout << std::endl;

    // iota: 生成序列
    for (int x : std::views::iota(1, 6)) {
        std::cout << x << " ";  // 1 2 3 4 5
    }
    std::cout << std::endl;

    // 链式管道组合
    auto top3_even_squares = v
        | std::views::filter([](int x) { return x % 2 == 0; })
        | std::views::transform([](int x) { return x * x; })
        | std::views::take(3);

    for (int x : top3_even_squares) {
        std::cout << x << " ";  // 4 16 36
    }
    std::cout << std::endl;

    // ranges版本的算法(不需要显式传迭代器对)
    std::vector<int> v2 = {5, 3, 1, 4, 2};
    std::ranges::sort(v2);  // 直接传容器
    std::ranges::for_each(v2, [](int x) { std::cout << x << " "; });
    std::cout << std::endl;

    return 0;
}

2.3 协程(Coroutines)📋

协程是可以暂停和恢复执行的函数,C++20提供了底层协程机制(co_await, co_yield, co_return)。

C++
#include <iostream>
#include <coroutine>
#include <optional>

// === Generator协程:co_yield ===
template <typename T>
struct Generator {
    struct promise_type {
        T current_value;

        Generator get_return_object() {  // 协程创建时调用,返回协程对象给调用者
            return Generator{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        std::suspend_always initial_suspend() { return {}; }  // 协程开始时是否立即挂起(suspend_always=惰性启动)
        std::suspend_always final_suspend() noexcept { return {}; }  // 协程结束时是否挂起(必须noexcept)
        void unhandled_exception() { std::terminate(); }  // 协程内未捕获异常的处理

        std::suspend_always yield_value(T value) {  // co_yield时调用,保存产出值并挂起
            current_value = value;
            return {};
        }
        void return_void() {}  // co_return无返回值时调用
    };

    std::coroutine_handle<promise_type> handle;

    Generator(std::coroutine_handle<promise_type> h) : handle(h) {}
    ~Generator() { if (handle) handle.destroy(); }

    // 禁止拷贝
    Generator(const Generator&) = delete;
    Generator& operator=(const Generator&) = delete;
    Generator(Generator&& other) noexcept : handle(other.handle) { other.handle = nullptr; }

    bool next() {
        handle.resume();
        return !handle.done();
    }

    T value() const { return handle.promise().current_value; }
};

// 生成斐波那契数列的协程
Generator<long long> fibonacci(int n) {
    long long a = 0, b = 1;
    for (int i = 0; i < n; ++i) {
        co_yield a;  // co_yield 协程产出值并暂停执行(C++20)
        auto temp = a + b;
        a = b;
        b = temp;
    }
}

// 无限范围生成器
Generator<int> natural_numbers() {
    int i = 0;
    while (true) {
        co_yield i++;
    }
}

int main() {
    // 斐波那契数列
    auto fib = fibonacci(10);
    while (fib.next()) {
        std::cout << fib.value() << " ";
    }
    std::cout << std::endl;
    // 输出: 0 1 1 2 3 5 8 13 21 34

    // 取前5个自然数
    auto nums = natural_numbers();
    for (int i = 0; i < 5; ++i) {
        nums.next();
        std::cout << nums.value() << " ";
    }
    std::cout << std::endl;
    // 输出: 0 1 2 3 4

    return 0;
}

协程核心概念: - co_yield:暂停协程并产出一个值 - co_await:暂停协程等待异步操作完成 - co_return:结束协程并返回值 - promise_type:定义协程行为的关键类型 - coroutine_handle:控制协程的生命周期(resume/destroy)

2.4 模块(Modules)

C++
// === 旧写法:头文件 + 源文件 ===
// math_utils.h
// #pragma once
// int add(int a, int b);
//
// math_utils.cpp
// #include "math_utils.h"
// int add(int a, int b) { return a + b; }

// ✅ C++20 Modules
// --- math_utils.cppm (模块接口文件) ---
// export module math_utils;
//
// export int add(int a, int b) {
//     return a + b;
// }
//
// export namespace math {
//     double pi = 3.14159265358979;
//
//     double circle_area(double r) {
//         return pi * r * r;
//     }
// }

// --- main.cpp ---
// import math_utils;
// import <iostream>;
//
// int main() {
//     std::cout << add(3, 4) << std::endl;
//     std::cout << math::circle_area(5.0) << std::endl;
// }

模块优势: - 编译速度大幅提升:模块只需编译一次,不像头文件每次include都要重新解析 - 无宏泄漏:模块内部的宏不会影响外部 - 明确的导出控制:只有export的才对外可见

2.5 三路比较运算符 <=>(Spaceship Operator)📋

C++
#include <iostream>
#include <compare>
#include <string>

struct Point {
    int x, y;

    // 旧写法:需要实现6个比较运算符
    // bool operator<(const Point& o) const { ... }
    // bool operator>(const Point& o) const { ... }
    // bool operator<=(const Point& o) const { ... }
    // 等等...

    // ✅ C++20:一行搞定所有比较运算符
    auto operator<=>(const Point&) const = default;
    // 编译器自动生成 <, >, <=, >=, ==, !=
};

struct Version {
    int major, minor, patch;

    // 自定义比较逻辑
    std::strong_ordering operator<=>(const Version& other) const {
        if (auto cmp = major <=> other.major; cmp != 0) return cmp;
        if (auto cmp = minor <=> other.minor; cmp != 0) return cmp;
        return patch <=> other.patch;
    }
    bool operator==(const Version&) const = default;
};

int main() {
    Point p1{1, 2}, p2{1, 3};
    std::cout << std::boolalpha;
    std::cout << (p1 < p2) << std::endl;   // true
    std::cout << (p1 == p2) << std::endl;   // false

    Version v1{2, 1, 0}, v2{2, 0, 9};
    std::cout << (v1 > v2) << std::endl;  // true (2.1.0 > 2.0.9)

    // 三种比较类别:
    // strong_ordering:  完全有序(整数比较)
    // weak_ordering:    弱序(大小写不敏感字符串)
    // partial_ordering: 可能不可比较(浮点NaN)

    return 0;
}

2.6 std::format 格式化库

C++
#include <iostream>
#include <format>
#include <string>
#include <vector>

int main() {
    // 旧写法
    // printf("Name: %s, Age: %d, Score: %.2f\n", "Alice", 25, 95.5);
    // std::cout << "Name: " << name << ", Age: " << age << std::endl;

    // ✅ C++20:std::format(类似Python的f-string)
    std::string name = "Alice";
    int age = 25;
    double score = 95.5;

    std::string s = std::format("Name: {}, Age: {}, Score: {:.2f}", name, age, score);
    std::cout << s << std::endl;
    // "Name: Alice, Age: 25, Score: 95.50"

    // 位置参数
    std::cout << std::format("{1} is {0} years old", 25, "Bob") << std::endl;

    // 格式化选项
    std::cout << std::format("{:>10}", "right") << std::endl;   // 右对齐
    std::cout << std::format("{:<10}", "left") << std::endl;    // 左对齐
    std::cout << std::format("{:^10}", "center") << std::endl;  // 居中
    std::cout << std::format("{:*^10}", "hi") << std::endl;     // ****hi****

    // 数字格式化
    std::cout << std::format("{:#x}", 255) << std::endl;   // 0xff
    std::cout << std::format("{:#b}", 42) << std::endl;    // 0b101010
    std::cout << std::format("{:+d}", 42) << std::endl;    // +42

    return 0;
}

2.7 std::span —— 连续内存视图

C++
#include <iostream>
#include <span>
#include <vector>
#include <array>

// 统一接口:接受任何连续内存
void print_span(std::span<const int> data) {  // std::span 连续内存的轻量级视图(C++20)
    for (int x : data) {
        std::cout << x << " ";
    }
    std::cout << std::endl;
}

// 子视图操作
void process(std::span<int> data) {
    auto first3 = data.first(3);
    auto last2 = data.last(2);
    auto mid = data.subspan(1, 3);

    std::cout << "first 3: ";
    for (int x : first3) std::cout << x << " ";
    std::cout << std::endl;
}

int main() {
    // 可以接受不同类型的连续内存
    int arr[] = {1, 2, 3, 4, 5};
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::array<int, 5> stdarr = {1, 2, 3, 4, 5};

    print_span(arr);      // C数组
    print_span(vec);      // vector
    print_span(stdarr);   // std::array

    process(vec);

    return 0;
}

2.8 consteval 与 constinit

C++
#include <iostream>

// constexpr: 可以编译期也可以运行期求值
constexpr int square(int x) { return x * x; }

// consteval: 必须在编译期求值(立即函数)
consteval int cube(int x) { return x * x * x; }

// constinit: 保证编译期初始化(防止static初始化顺序问题)
constinit int global_val = square(10);  // OK,编译期初始化为100
// constinit int runtime_val = some_runtime_function();  // ❌ 编译错误

int main() {
    constexpr int a = square(5);  // 编译期
    int n = 5;
    int b = square(n);             // 运行期也OK

    constexpr int c = cube(3);     // 编译期,OK
    // int d = cube(n);            // ❌ 编译错误:consteval必须编译期

    std::cout << a << " " << b << " " << c << std::endl;  // 25 25 27

    return 0;
}

三、C++23 新特性

3.1 std::expected —— 零成本错误处理 📋

C++
#include <iostream>
#include <expected>
#include <string>
#include <charconv>

enum class ParseError {
    InvalidFormat,
    OutOfRange,
    Empty
};

// 旧写法:异常或错误码
// 异常有性能开销,错误码容易被忽略

// ✅ C++23:std::expected
std::expected<int, ParseError> parse_int(std::string_view sv) {
    if (sv.empty()) {
        return std::unexpected(ParseError::Empty);
    }

    int result = 0;
    auto [ptr, ec] = std::from_chars(sv.data(), sv.data() + sv.size(), result);

    if (ec == std::errc::invalid_argument) {
        return std::unexpected(ParseError::InvalidFormat);
    }
    if (ec == std::errc::result_out_of_range) {
        return std::unexpected(ParseError::OutOfRange);
    }

    return result;
}

int main() {
    auto r1 = parse_int("42");
    if (r1.has_value()) {
        std::cout << "Parsed: " << r1.value() << std::endl;  // 42
    }

    auto r2 = parse_int("abc");
    if (!r2) {
        std::cout << "Error code: " << static_cast<int>(r2.error()) << std::endl;
    }

    // value_or
    int val = parse_int("xyz").value_or(-1);
    std::cout << "value_or: " << val << std::endl;  // -1

    // monadic操作(链式调用)
    auto result = parse_int("42")
        .transform([](int v) { return v * 2; })     // 成功时变换
        .transform([](int v) { return v + 1; });     // 继续变换
    std::cout << "Chained: " << result.value() << std::endl;  // 85

    return 0;
}

📋 面试要点:expected vs optional vs exception——expected携带错误信息且零成本抽象;optional只表示有/无;异常有栈展开开销。Rust的Result与expected理念一致。

3.2 std::print / std::println

C++
#include <print>
#include <vector>
#include <string>

int main() {
    // 旧写法
    // std::cout << "Hello, " << name << "! You are " << age << " years old." << std::endl;
    // printf("Hello, %s! You are %d years old.\n", name.c_str(), age);

    // ✅ C++23:std::print / std::println
    std::string name = "Alice";
    int age = 25;

    std::println("Hello, {}! You are {} years old.", name, age);
    // 自动换行,无需endl

    // 格式化输出
    std::print("Pi = {:.4f}\n", 3.14159);  // 不自动换行
    std::println("{:>10}: {:05d}", "ID", 42);  // 右对齐 + 前导零

    return 0;
}

3.3 Deducing this —— 显式对象参数 📋

C++
#include <iostream>
#include <string>

struct Widget {
    std::string name = "default";

    // 旧写法:需要为const/非const/左值引用/右值引用写多个重载
    // std::string& get_name() & { return name; }
    // const std::string& get_name() const& { return name; }
    // std::string&& get_name() && { return std::move(name); }

    // ✅ C++23:deducing this,一个模板搞定所有
    template <typename Self>
    auto&& get_name(this Self&& self) {
        return std::forward<Self>(self).name;
    }

    // CRTP无需基类模板参数
    void print(this auto const& self) {
        std::cout << self.name << std::endl;
    }
};

int main() {
    Widget w;
    w.name = "hello";

    // 根据调用对象的值类别自动推导
    auto& ref = w.get_name();           // 左值引用
    const Widget cw;
    const auto& cref = cw.get_name();   // const左值引用
    auto moved = Widget{"temp"}.get_name();  // 右值引用 -> 移动

    std::cout << ref << std::endl;
    w.print();

    return 0;
}

3.4 多维下标运算符

C++
#include <iostream>
#include <vector>

template <typename T>
class Matrix {
    std::vector<T> data;
    size_t rows, cols;

public:
    Matrix(size_t r, size_t c) : data(r * c), rows(r), cols(c) {}

    // 旧写法:operator()或返回代理对象
    // T& operator()(size_t r, size_t c) { return data[r * cols + c]; }

    // ✅ C++23:多维下标运算符
    T& operator[](size_t r, size_t c) {
        return data[r * cols + c];
    }
    const T& operator[](size_t r, size_t c) const {
        return data[r * cols + c];
    }
};

int main() {
    Matrix<double> m(3, 4);
    m[1, 2] = 3.14;  // 直观的多维索引!
    std::cout << m[1, 2] << std::endl;

    return 0;
}

3.5 std::generator(C++23协程生成器)

C++
#include <iostream>
#include <generator>  // C++23
#include <ranges>

// C++23的标准generator,无需手写promise_type!
std::generator<int> fibonacci() {
    int a = 0, b = 1;
    while (true) {
        co_yield a;
        auto temp = a + b;
        a = b;
        b = temp;
    }
}

std::generator<int> range(int start, int end) {
    for (int i = start; i < end; ++i) {
        co_yield i;
    }
}

int main() {
    // 与ranges完美配合
    for (int x : fibonacci() | std::views::take(10)) {
        std::cout << x << " ";
    }
    std::cout << std::endl;
    // 0 1 1 2 3 5 8 13 21 34

    for (int x : range(1, 6)) {
        std::cout << x << " ";
    }
    std::cout << std::endl;
    // 1 2 3 4 5

    return 0;
}

3.6 std::flat_map / std::flat_set

C++
#include <iostream>
#include <flat_map>
#include <flat_set>
#include <string>

int main() {
    // flat_map: 底层用排序的vector代替红黑树
    // 优势:缓存友好、内存紧凑、遍历快
    // 劣势:插入/删除O(n)

    std::flat_map<std::string, int> fm;
    fm["Alice"] = 95;
    fm["Bob"] = 87;
    fm["Charlie"] = 92;

    // 适合"构建一次,查询多次"的场景
    for (const auto& [name, score] : fm) {
        std::cout << name << ": " << score << std::endl;
    }

    // flat_set: 同理,用排序vector代替红黑树
    std::flat_set<int> fs = {3, 1, 4, 1, 5, 9};
    for (int x : fs) {
        std::cout << x << " ";
    }
    std::cout << std::endl;

    return 0;
}

3.7 import std(标准库模块化)

C++
// C++23: 一行导入所有标准库
import std;

int main() {
    std::vector<int> v = {3, 1, 4, 1, 5};
    std::ranges::sort(v);
    std::println("Sorted: {}", v);
    return 0;
}

// 编译速度极大提升:标准库只需解析一次
// 相当于一次性替换了所有 #include 头文件

四、实际工程选型建议

4.1 已被业界广泛采用的特性

特性 采用程度 说明
结构化绑定(C++17) ⭐⭐⭐⭐⭐ 几乎所有新项目都在用
std::optional(C++17) ⭐⭐⭐⭐⭐ 替代返回指针/特殊值
std::string_view(C++17) ⭐⭐⭐⭐⭐ 零拷贝字符串传递
constexpr if(C++17) ⭐⭐⭐⭐ 模板库广泛使用
std::filesystem(C++17) ⭐⭐⭐⭐ 替代平台相关API
Concepts(C++20) ⭐⭐⭐⭐ 新库设计首选
Ranges(C++20) ⭐⭐⭐ 逐步推广中
std::format(C++20) ⭐⭐⭐ 替代printf/iostream
Coroutines(C++20) ⭐⭐⭐ 异步IO/生成器场景
Modules(C++20) ⭐⭐ 编译器支持尚未完善
std::expected(C++23) ⭐⭐ 逐步采用中
std::print(C++23) ⭐⭐ GCC14+, Clang17+支持

4.2 编译器支持状况(截至2025年底)

特性 GCC Clang MSVC
C++17全部 7+ 5+ 19.14+
Concepts 10+ 10+ 19.28+
Ranges 10+ 13+ 19.29+
Coroutines 10+ 8+ 19.28+
Modules 11+(部分) 15+(部分) 19.28+(部分)
std::format 13+ 14+ 19.29+
<=> 10+ 10+ 19.28+
std::expected 12+ 16+ 19.33+
std::print 14+ 17+ 19.37+
deducing this 14+ 18+ 19.36+
import std 15+(实验) 18+(实验) 19.35+(实验)

建议:新项目至少以C++17为基线,积极采用C++20特性(Concepts/Ranges/format),C++23特性视编译器支持情况逐步引入。


✏️ 练习

练习1:std::optional 实践

实现一个 safe_divide(double a, double b) -> std::optional<double>,除数为0时返回 std::nullopt。链式调用三次除法并处理错误。

练习2:Concepts 定义

定义一个 Hashable concept,要求类型T可以被 std::hash<T> 哈希。用此concept约束一个 hash_combine 函数模板。

练习3:Ranges 管道

给定 vector<string>,用Ranges管道找出长度大于3的字符串,转换为大写,取前5个。

练习4:Spaceship运算符

为一个 Student 结构体(姓名、年龄、GPA)实现 <=> 运算符,排序规则:GPA降序 > 年龄升序 > 姓名字典序。

练习5:std::variant 状态机

使用 std::variantstd::visit 实现一个简单的有限状态机(如交通信号灯:红->绿->黄->红)。


📋 面试要点

高频问题

  1. C++17 vs C++20 vs C++23的标志性特性各是什么? C++17: optional/variant/string_view/structured bindings;C++20: Concepts/Ranges/Coroutines/Modules;C++23: expected/print/deducing this
  2. Concepts相比SFINAE的优势? 语法清晰、错误信息友好、可组合、在函数签名处约束
  3. Ranges的懒求值是怎么实现的? views返回的是proxy对象,只在迭代时才实际计算,通过 begin()/end() 返回特殊迭代器
  4. 协程的状态保存在哪? 保存在堆上的协程帧(coroutine frame),包含局部变量、挂起点等信息
  5. std::expected vs 异常 vs 错误码? expected零成本抽象+类型安全,异常有栈展开开销但适合罕见错误,错误码容易被忽略
  6. string_view的坑? 不拥有数据,若原字符串被销毁则悬垂引用
  7. constexpr vs consteval vs constinit? constexpr编译期/运行期均可;consteval仅编译期(立即函数);constinit保证编译期初始化但变量可运行期修改
  8. std::format vs printf vs iostream? format类型安全+可扩展+好用;printf快但类型不安全;iostream类型安全但语法啰嗦

💡 本章小结

现代C++(C++17/20/23)带来了翻天覆地的变化。C++17让日常编码更舒适(optional、structured bindings、string_view);C++20是革命性升级(Concepts让模板可读,Ranges让数据处理优雅,Coroutines支持异步/生成器);C++23继续打磨(expected、print、deducing this等实用特性)。掌握这些现代特性不仅能写出更好的代码,也是面试中展示自己C++功底的有力加分项。下一章将学习CMake构建系统与C++工程最佳实践。