Skip to content

C++17/20 常用深度:string_view / span / format / 三向比较 / module

这些是 C++ 现代特性里的“零拷贝 / 工程化”话题,Infra/系统编程常被考到,因为它们关系到避免复制与类型安全

一、std::string_view(C++17):只读字符串的“视图”

  • 持有 {const char*, size_t}(或迭代器对),不拥有内存、不拷贝,只读引用一段字符。
  • 可从 std::stringconst 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")); // 也行

关键坑(必考)

  1. string_view 不拥有,指向的内存必须比 view 活得久
  2. 不要 return 一个绑定到临时 string 的 string_view(临时销毁后悬垂);const char* 到 string 临时同理。
  3. std::string s; auto v=std::string_view(s); return v; 若 s 结束 → 悬垂。
  4. sv.data() 不一定以 '\0' 结尾(可能只指向子串)——别直接当 C 字符串传。
  5. 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::equal etc.

五、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;记得生命期与不能扩容。

七、自测

  1. span 与 string_view 别跨界(字符串 vs 任意元素)。给出“vector 扩容后 span 失效”如何避免——拷前不用旧 span / reserve 后再取。
  2. format 与 printf 区别?(类型安全/缓冲)
  3. <=> default 能为成员含 std::string 生成 < 吗?—— string 自己已支持三向比较,可以。

C++ 面试八股 · VitePress 版