14-C++23 新特性:标准库更好用与语法补强(入门)¶
目录页:见 C++ 小白教程目录
目标¶
- 了解 C++23 相对 C++20 的“高频提升点”在哪里
- 会用
std::expected表达“可能失败但不想抛异常”的返回值 - 会用
std::ranges::to让 Ranges 的结果更容易落地成容器
先决知识(建议)¶
- C++20 的 Ranges 基本用法(见 13 章 2)
std::optional/std::variant的直觉(你可以先把它们当作“可空值 / 多选一”)
0. 先给结论:C++23 的关键词是“补齐与打磨”¶
和 C++20 那种“大功能上场”不同,C++23 更像是:
- 标准库补齐:让很多“日常就想要”的工具变成标配
- 语法打磨:零碎但顺手,减少样板代码
- Ranges 继续增强:新增 view 让流水线更好用
现实里你最可能最先用到的是:expected、ranges::to、string::contains、一些新的 views(比如 zip / enumerate)。
1. std::expected:把“错误”变成返回值的一部分¶
- 专业名称:
std::expected<T, E>(expected) - 类比:快递单:要么拿到包裹(T),要么拿到“失败原因”(E)
- 作用:不靠异常也能表达失败,并且强迫调用方处理
- 规则/坑点:它不是万能;对于“真正不可恢复”的错误,异常仍然有价值
1.1 基本用法:要么值,要么错误¶
#include <expected>
#include <iostream>
#include <string>
std::expected<int, std::string> parsePositiveInt(const std::string& s) {
if (s.empty()) return std::unexpected("empty");
int x = 0;
for (char c : s) {
if (c < '0' || c > '9') return std::unexpected("not a number");
x = x * 10 + (c - '0');
}
if (x <= 0) return std::unexpected("not positive");
return x;
}
int main() {
auto r = parsePositiveInt("123");
if (!r) {
std::cout << "error=" << r.error() << '\n';
return 0;
}
std::cout << "value=" << *r << '\n';
return 0;
}
直觉对比:
T:代表“这里通常会成功”std::optional<T>:代表“可能没有值,但不关心原因”std::expected<T, E>:代表“可能失败,而且你需要原因”
1.2 什么时候用 expected,什么时候用异常¶
更实用的判断方式是看“失败是不是业务的一部分”:
- 更适合
expected:解析失败、用户输入不合法、文件不存在、网络超时(调用方通常能处理) - 更适合异常:违反前置条件、逻辑不可能发生、资源耗尽等“不可恢复/不该吞掉”的错误
2. std::ranges::to:把 view 的结果一键收集成容器¶
- 专业名称:
std::ranges::to<Container>(Ranges 收集器) - 类比:流水线上最后加一个“打包机”,把流里的东西装进箱子里
- 作用:解决 C++20 Ranges 常见痛点:结果想变回
vector很麻烦 - 规则/坑点:这是 C++23;如果你用的是 C++20,需要手动
for或用第三方库
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> v{1, 2, 3, 4, 5, 6};
auto out = v
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; })
| std::ranges::to<std::vector<int>>();
for (int x : out) std::cout << "x=" << x << '\n';
return 0;
}
3. std::print:更顺手的输出(搭配 format)¶
- 专业名称:
std::print/std::println - 类比:把“格式化 + 输出”打包成一个动作
- 作用:少写
<<拼接,输出更直观 - 规则/坑点:依赖实现对
<print>的支持情况;如果编译器/标准库较旧,可能不可用
如果你的环境还没支持 <print>,你可以继续用 std::cout,或者在支持良好的环境下再切换。
4. string::contains:判断子串更直觉¶
#include <iostream>
#include <string>
int main() {
std::string s = "hello world";
std::cout << "has_hello=" << s.contains("hello") << '\n';
return 0;
}
5. 新的 Ranges views:zip / enumerate(C++23)¶
很多人学 Ranges 最大的痛点是“能 filter/transform,但组合不够丰富”。C++23 增加了一批更常用的 view。
5.1 views::enumerate:遍历时同时拿下标与元素¶
#include <iostream>
#include <ranges>
#include <vector>
int main() {
std::vector<int> v{10, 20, 30};
for (auto [i, x] : std::views::enumerate(v)) {
std::cout << "i=" << i << " x=" << x << '\n';
}
return 0;
}
5.2 views::zip:并行遍历两个序列¶
#include <iostream>
#include <ranges>
#include <string>
#include <vector>
int main() {
std::vector<std::string> names{"alice", "bob"};
std::vector<int> scores{90, 80};
for (auto [name, score] : std::views::zip(names, scores)) {
std::cout << "name=" << name << " score=" << score << '\n';
}
return 0;
}
规则/坑点(先记结论就行):
- zip 会按“最短序列”截断
- enumerate 里的下标类型是某种无符号整数类型(用它做减法时要小心)
6. C++23 的语法补强(挑最常见的讲)¶
6.1 if consteval:区分“编译期路径”和“运行期路径”¶
#include <iostream>
consteval int f_compiletime(int x) { return x + 1; }
constexpr int f(int x) {
if consteval {
return f_compiletime(x);
} else {
return x + 1;
}
}
int main() {
constexpr int a = f(1);
std::cout << "a=" << a << '\n';
return 0;
}
你可以把它理解成:在 constexpr 函数里,某些逻辑你只想在编译期做(比如预计算、校验),运行期就走普通路径。
6.2 Deducing this(你会在写链式 API/值类别转发时遇到)¶
它能让成员函数“根据对象是左值/右值”做不同返回,或者更准确地转发。学习阶段可以先知道它是“高级用法”,等你写库/写通用组件再深入。
7. 其他值得知道的标准库小工具(C++23)¶
这一节的目标是:让你看到“C++23 不只是 expected/print”,标准库也补了不少顺手的小能力。
7.1 std::to_underlying:从 enum class 拿到底层整数¶
#include <cstdint>
#include <iostream>
#include <utility>
enum class Color : std::uint8_t { red = 1, green = 2 };
int main() {
std::cout << "red=" << std::to_underlying(Color::red) << '\n';
return 0;
}
7.2 std::byteswap:字节序翻转(做协议/二进制时常用)¶
#include <bit>
#include <cstdint>
#include <iostream>
int main() {
std::uint32_t x = 0x11223344u;
std::uint32_t y = std::byteswap(x);
std::cout << "y=" << y << '\n';
return 0;
}
7.3 std::stacktrace:打印调用栈(支持情况依赖环境)¶
#include <iostream>
#include <stacktrace>
int main() {
std::cout << std::stacktrace::current() << '\n';
return 0;
}
如果你的编译器/标准库还没实现 <stacktrace>,这段会编译失败,这不是你写错,是工具链支持度问题。
8. 这一章你该怎么用(现实建议)¶
- 学习与竞赛/刷题:优先把 C++20 的 Ranges/Concepts 学会;C++23 的
expected很值得补 - 工程与项目:先看你编译器与标准库版本是否“真的到位”,再决定能不能用
<print>、<expected>
常见坑¶
- 错误:把
expected当成“异常替代品”并滥用 → 结果:到处传播错误码,噪音大 → 正确:只在“可预期失败”的边界用它(解析/IO/校验) - 错误:不检查
expected直接*r→ 结果:未定义行为/崩溃 → 正确:先判断if (!r)再取值 - 错误:看到 C++23 就默认环境支持 → 结果:编译失败 → 正确:先确认工具链与
-std=c++23(或对应开关)
小练习¶
练习 1:expected 的错误信息¶
- 输入/输出:读入一行字符串,输出解析到的整数;失败则输出原因
- 约束:返回类型必须是
std::expected<int, std::string> - 提示:可以参考 1.1 的
parsePositiveInt - 目标:训练“先判断再取值”的习惯
练习 2:ranges::to 收集¶
- 输入/输出:读入 N 个整数,输出所有奇数的绝对值(按原顺序)
- 约束:必须用 view 管道,最后用
std::ranges::to<std::vector<int>> - 提示:绝对值可以写成一个
transform - 目标:把“流水线”与“落地”连起来
小结¶
- C++23 里最“日常有感”的提升主要来自标准库:
expected、ranges::to、print、新的 views 等 - 是否立刻用 C++23,取决于你的编译器与标准库版本成熟度
上一篇:见 13-C++20 新特性:Concepts、Ranges、Coroutines、Modules(入门) 下一篇:见 15-C++11 到 C++23:标准演进路线图(你该学什么)