admin 管理员组

文章数量: 1184232


本文记录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:{}用法说明。

voidtest(){
        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:下面的示例中在{}中指定参数顺序。

voidtest(){
        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支持丰富的格式化选项,语法如下:

{:[fill][align][sign][#][0][width][.precision][type]}

{} : 替换字符的标志,其中包含要格式化的参数索引和格式说明。
“:” : 分割符,用于分隔参数索引和格式说明。

voidtest(){// 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风格的数组。编写一个函数处理这个数组时,在函数中传递这个数组,如下:

// 方式 A: 只能接受 vector,不能接受原生数组或 std::arrayvoidprint(const std::vector<int>& v);// 方式 B: C 风格  传递指针 和 长度voidprint(constint* ptr, size_t size);

C++20中,可以直接使用span输出,它充当了不同容器类型之间的通用接口。

voidprint(std::span<int> s){for(int x : s){
			std::cout << x <<" ";}
		std::cout << std::endl;}voidtest(){int arr[]={1,2,3};
		vector<int> vec ={4,5,6,7};
		std::array<int,3> stdarr ={8,9,10};print(arr);// 从原生数组创建 spanprint(vec);// 从 vector 创建 spanprint(stdarr);// 从 std::array 创建 span}

2.2.1 span原理

std::span 有两个模板参数:

std::span<T, Extent>

std::dynamic_extent (默认): 长度在运行时确定。这是最常用的形式,如 std::span。
静态长度: 长度在编译时确定。如 std::span<int, 5>。这允许编译器进行更激进的优化,并强制长度检查。

2.2.2 切片功能

std::span 最强大的功能之一是能够轻松创建子视图,而无需复制数据。这类似于 Python 的切片或 Go 的 slice。

voidtest(){
        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),这在序列化或网络编程中非常有用。

voidsend_data(std::span<const std::byte> buffer){// 发送 buffer.size() 字节...}intmain(){double values[]={1.1,2.2};// 自动将 double 数组视为字节序列send_data(std::as_bytes(std::span(values)));}

2.2.4 悬空引用

因为 std::span 不拥有内存,所以必须确保 span 的生命周期不超过它指向的数据的生命周期。

std::span<int>get_dangling(){
    std::vector<int> v ={1,2,3};return v;//  错误方式,v 在函数结束时被销毁,返回的 span 指向无效内存。}

2.2.5 span总结

span优点:零拷贝,统一接口(连续内存都可以使用span处理),类型安全,支持切片。
需要注意的是,使用span时,避免悬空引用。

本文标签: 系统 总宽度为 编程