现代C++(17/20/23)新特性¶
🎯 学习目标¶
完成本章学习后,你将能够: - 掌握C++17的核心语言特性和标准库扩展 - 深入理解C++20四大革命性特性:Concepts、Ranges、Coroutines、Modules - 了解C++23的最新实用特性 - 能够在实际工程中合理选择和使用现代C++特性 - 掌握面试中高频考察的现代C++知识点
上图按版本阶段整理了现代 C++ 的学习路径,帮助你分层掌握特性演进。
一、C++17 核心特性¶
1.1 结构化绑定(Structured Bindings)📋¶
允许用一条声明语句同时绑定多个变量到结构体/tuple/数组的成员。
#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 初始化语句¶
#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 📋¶
#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 📋¶
#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 —— 编译期分支 📋¶
#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)¶
#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¶
#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)¶
#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最重要的特性之一,提供了对模板参数的约束,让模板错误信息变得可读。
#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库提供了懒求值的管道式操作,极大提升代码可读性。
#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)。
#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)¶
// === 旧写法:头文件 + 源文件 ===
// 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)📋¶
#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 格式化库¶
#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 —— 连续内存视图¶
#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¶
#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 —— 零成本错误处理 📋¶
#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
3.2 std::print / std::println¶
#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 —— 显式对象参数 📋¶
#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 多维下标运算符¶
#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协程生成器)¶
#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¶
#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++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::variant 和 std::visit 实现一个简单的有限状态机(如交通信号灯:红->绿->黄->红)。
📋 面试要点¶
高频问题¶
- 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
- Concepts相比SFINAE的优势? 语法清晰、错误信息友好、可组合、在函数签名处约束
- Ranges的懒求值是怎么实现的? views返回的是proxy对象,只在迭代时才实际计算,通过
begin()/end()返回特殊迭代器 - 协程的状态保存在哪? 保存在堆上的协程帧(coroutine frame),包含局部变量、挂起点等信息
- std::expected vs 异常 vs 错误码? expected零成本抽象+类型安全,异常有栈展开开销但适合罕见错误,错误码容易被忽略
- string_view的坑? 不拥有数据,若原字符串被销毁则悬垂引用
- constexpr vs consteval vs constinit? constexpr编译期/运行期均可;consteval仅编译期(立即函数);constinit保证编译期初始化但变量可运行期修改
- 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++工程最佳实践。