文章目录
- [第二章 C++20标准库特性](#第二章 C++20标准库特性)
-
- [2.1 std::format](#2.1 std::format)
-
- [2.1.1 基本语法](#2.1.1 基本语法)
- [2.1.2 位置参数](#2.1.2 位置参数)
- [2.1.3 格式说明符](#2.1.3 格式说明符)
- [2.1.4 总结](#2.1.4 总结)
- [2.2 std::span](#2.2 std::span)
-
- [2.2.1 span原理](#2.2.1 span原理)
- [2.2.2 切片功能](#2.2.2 切片功能)
- [2.2.3 读取字节](#2.2.3 读取字节)
- [2.2.4 悬空引用](#2.2.4 悬空引用)
- [2.2.5 span总结](#2.2.5 span总结)
本文记录C++20新特性之std::format和span。
第二章 C++20标准库特性
2.1 std::format
在C++20之前,文本格式化主要依赖于 C 风格的 printf函数或 C++ 的 iostream 流操作。printf 缺乏类型安全且难以扩展,而 iostream 虽然类型安全但语法繁琐且性能不高。
C++20引入了库,提供了一个类型安全、高效且语法类似于 Python 字符串格式化的现代解决方案。
2.1.1 基本语法
std::format 的核心思想是使用花括号 {} 作为占位符。它返回一个 std::string。
示例1:{}用法说明。
cpp
void test()
{
std::string name = "wangandy";
int id = 330;
std::string message = std::format("我叫 {}!,id 为 {}", name, id);
std::cout << message << endl;
// 我叫 wangandy!,id 为 330
}
需要注意的是,{}中不能加空格等不符合语法的字符。
2.1.2 位置参数
可以通过在花括号中指定索引来控制参数的参入顺序。
示例2:下面的示例中在{}中指定参数顺序。
cpp
void test()
{
std::string name = "wangandy";
int id = 330;
std::string message = std::format("我叫 {0}!,id 为 {1}", name, id);
std::cout << message << endl;
// 我叫 wangandy!,id 为 330
std::string message2 = std::format("我叫 {1}!,id 为 {0}", name, id);
std::cout << message2 << endl;
// 我叫 330!,id 为 wangandy
}
2.1.3 格式说明符
std::format支持丰富的格式化选项,语法如下:
cpp
{:[fill] [align] [sign] [#] [0] [width] [.precision] [type] }
{} : 替换字符的标志,其中包含要格式化的参数索引和格式说明。
":" : 分割符,用于分隔参数索引和格式说明。

示例:下面测试每个符号的用法。
cpp
void test()
{
// 1. width 和 align (宽度和对齐)
// >10 表示右对齐,总宽度为10
std::cout << std::format("右对齐: |{:>10}|", "hi") << std::endl;
// 输出: 右对齐: | hi|
// 2. fill 和 align (填充和对齐)
// *^10 表示居中对齐,总宽度为10,用*填充
std::cout << std::format("居中填充: |{:*^10}|", "hi") << std::endl;
// 居中填充: |****hi****|
// 3. sign (符号)
std::cout << std::format("显示符号: {:+} 和 {:+}", 12, -12) << std::endl;
// 输出: 显示符号: +12 和 -12
std::cout << std::format("空格符号: {:} 和 {:} ", 12, -12) << std::endl;
// 输出: 空格符号: 12 和 -12
// 4. # (替代形式) 和 type (类型)
std::cout << std::format("十六进制: {0:x}, {0:#x}, {0:#X}", 12) << std::endl;
// 输出: 十六进制: c, 0xc, 0XC
std::cout << std::format("二进制: {0:b}, {0:#b}", 12) << std::endl;
// 输出: 二进制: 1100, 0b1100
// 5. 0 (零填充)
std::cout << std::format("零填充: {:08d}", 123) << std::endl;
// 输出: 零填充: 00000123
// 6. .precision (精度)
std::cout << std::format("浮点数精度: {:.2f}", 3.14159) << std::endl;
// 输出: 浮点数精度: 3.14
std::cout << std::format("字符串精度: {:.5s}", "hello world") << std::endl;
// 输出: 字符串精度: hello
// 7. 综合示例
double temperature = 25.6789;
std::cout << std::format("温度: |{:+010.2f}|", temperature) << std::endl;
// 输出: 温度: |+000025.68|
// 解释:
// + : 显示正号
// 0 : 使用0填充
// 10: 总宽度为10
// .2: 小数点后保留2位
// f : 浮点数类型
}
2.1.4 总结
性能:format的性能优于iostream,在某些情况下超过 printf.
语法:format结合了python风格的易用性和C++的高性能,是现代C++开发中处理字符串的首选工具。
2.2 std::span
在C++20之前,我们需要连续内存块保存数据时,通常使用vector或者C风格的数组。编写一个函数处理这个数组时,在函数中传递这个数组,如下:
cpp
// 方式 A: 只能接受 vector,不能接受原生数组或 std::array
void print(const std::vector<int>& v);
// 方式 B: C 风格 传递指针 和 长度
void print(const int* ptr, size_t size);
C++20中,可以直接使用span输出,它充当了不同容器类型之间的通用接口。
cpp
void print(std::span<int> s)
{
for (int x : s)
{
std::cout << x << " ";
}
std::cout << std::endl;
}
void test()
{
int arr[] = { 1,2,3 };
vector<int> vec = { 4,5,6,7 };
std::array<int, 3> stdarr = { 8,9,10 };
print(arr); // 从原生数组创建 span
print(vec); // 从 vector 创建 span
print(stdarr); // 从 std::array 创建 span
}
2.2.1 span原理
std::span 有两个模板参数:
cpp
std::span<T, Extent>
std::dynamic_extent (默认): 长度在运行时确定。这是最常用的形式,如 std::span。
静态长度: 长度在编译时确定。如 std::span<int, 5>。这允许编译器进行更激进的优化,并强制长度检查。
2.2.2 切片功能
std::span 最强大的功能之一是能够轻松创建子视图,而无需复制数据。这类似于 Python 的切片或 Go 的 slice。
cpp
void test()
{
std::vector<int> data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
std::span<int> s = data;
// 获取前3个元素
std::span<int> first3 = s.first<3>();
for (int x : first3)
{
std::cout << x << " ";
}
cout << endl;
// 获取后3个元素
std::span<int> last3 = s.last<3>();
for (int x : last3)
{
std::cout << x << " ";
}
cout << endl;
// 获取从索引2开始的4个元素
std::span<int> subspan = s.subspan(2, 4);
for (int x : subspan)
{
std::cout << x << " ";
}
cout << endl;
/*
0 1 2
7 8 9
2 3 4 5
*/
}
2.2.3 读取字节
std::as_bytes 和 std::as_writable_bytes 可以将任何 span 转换为字节视图(std::span),这在序列化或网络编程中非常有用。
cpp
void send_data(std::span<const std::byte> buffer) {
// 发送 buffer.size() 字节...
}
int main() {
double values[] = {1.1, 2.2};
// 自动将 double 数组视为字节序列
send_data(std::as_bytes(std::span(values)));
}
2.2.4 悬空引用
因为 std::span 不拥有内存,所以必须确保 span 的生命周期不超过它指向的数据的生命周期。
cpp
std::span<int> get_dangling() {
std::vector<int> v = {1, 2, 3};
return v; // 错误方式,v 在函数结束时被销毁,返回的 span 指向无效内存。
}
2.2.5 span总结
span优点:零拷贝,统一接口(连续内存都可以使用span处理),类型安全,支持切片。
需要注意的是,使用span时,避免悬空引用。