Appearance
C++17/20 常用深度:string_view / span / format / 三向比较 / module
这些是 C++ 现代特性里的“零拷贝 / 工程化”话题,Infra/系统编程常被考到,因为它们关系到避免复制与类型安全。
一、std::string_view(C++17):只读字符串的“视图”
- 持有
{const char*, size_t}(或迭代器对),不拥有内存、不拷贝,只读引用一段字符。 - 可从
std::string、const char*、char[]构造,临时产生零拷贝。
cpp
void parse(std::string_view sv) { // 只读,接受各种来源
sv.remove_prefix(4); sv.remove_suffix(2);
std::string_view sub = sv.substr(1,3); // O(1),不复制
}
parse("hello"); // 字面量
parse(std::string("hi")); // 也行关键坑(必考):
string_view不拥有,指向的内存必须比 view 活得久。- 不要
return一个绑定到临时 string 的 string_view(临时销毁后悬垂);const char*到 string 临时同理。 - 从
std::string s; auto v=std::string_view(s); return v;若 s 结束 → 悬垂。 sv.data()不一定以 '\0' 结尾(可能只指向子串)——别直接当 C 字符串传。- string_view 不是 string:无 '\0' 语义、不能改底层。
优势:传参只读字符串用 string_view 免拷贝字符串(避免 SSO 大小里分配/拷贝),函数里子串切片 O(1)。
二、std::span(C++20):连续数组的“视图”
- 不拥有的连续元素范围:底层
{T*, count}。 - 可对任意连续 buffer(std::vector、std::array、C 数组)取
std::span<T>,避免“数组退化指针丢长度”。
cpp
void sum(std::span<const double> s); // 只读任意连续容器
double a[8];
std::vector<double> v(100);
sum(a); sum(v); // 直接传,auto 推导长度
std::span<int> sp(v); // std::span<int>
sp[3] = 5; // 修改原 v(不拥有但可借用写)要点:
span<T>可写传入容器;span<const T>只读——自动区分。- span 不拥有、不扩容:若底层 vector 扩容(重新分配),旧 span 悬垂(拷到别处失效)。这是常见误用。
- dynamic_extent vs 固定 extent:
std::span<int, 8>编译期定长,迭代器更快。 - 无边界检查,需由调用者保证范围(安全时用
at/自定义)。
三、std::format(C++20):安全格式化
cpp
#include <format>
std::string s = std::format("val {} and {:.2f}", 42, 3.14);- 类型安全(不像 printf 靠格式串类型漏洞)、可读、可自定义格式化器(
std::formatter<T>)。 - 比
sprintf/流式更安全更高效(一次缓冲)。 - 也能
std::print(C++23) 直接输出。 - HPC 注意:热路径避免格式化(I/O 上界),序列化协议例外用二进制。
四、三向比较 <=>(C++20 spaceship operator)
cpp
struct Pt { int x,y;
auto operator<=>(const Pt&) const = default; // 自动生成 ==,!=,<,<=,>,>=
};
Pt a{1,2}, b{1,3}; if (a < b) {}- 返回
std::strong_ordering/weak_ordering/partial_ordering(浮点)。a<=>b<0等做比较。 auto ...=default让编译器按成员序(声明序)逐字段比较,自动补出六个比较运算符。- 为什么好:少写一坨重载;自定义类排序天然正确。注意若是自写
operator<=>,记得std::strong_ordering::equaletc.
五、module(C++20,简了解)
- 替代部分 include 体系:模块化编译、隔离实现、更快增量编译与更好封装。
- 语法:
export module foo;/import foo;——模块里export符号对 import 可见。 - 现状:主流编译器支持度逐步完善,但生态库迁移延迟;面试多半问“了解 module 是否改善 include 头/重编译问题”,答机制 + 尚未大范围采用即可。
六、工程取舍(讲给 HPC/Infra):零拷贝传参矩阵用法
| 场景 | 用 |
|---|---|
| 只读字符串传参/切片 | std::string_view |
| 连续 buffer 只读/借用写、不搬所有权 | std::span |
| 真正所有权/可扩容 | std::string/std::vector 保持 |
| 二进制序列化缓冲 | 手写 buffer 或 span<byte> |
| 需打印 | std::format(别拼 sprintf %) |
一句话:string_view/span 是“不拥有安全的借用视图”,用来免复制传参、切片、缓冲区 API;记得生命期与不能扩容。
七、自测
- span 与 string_view 别跨界(字符串 vs 任意元素)。给出“vector 扩容后 span 失效”如何避免——拷前不用旧 span / reserve 后再取。
- format 与 printf 区别?(类型安全/缓冲)
<=>default 能为成员含 std::string 生成<吗?—— string 自己已支持三向比较,可以。