字符串格式化
format!()
宏用于生成格式化的字符串, 它类似于 C 语言中的 printf()
以及 Python 中的 str.format()
函数.
#![allow(unused)] fn main() { let name = "Julia"; let greating = format!("Hello, {}", name); }
如果是结构体的话, 使用 {:#?}
可以实现类似 "pretty printing" 的效果, 多行打印结构体信息:
#![allow(unused)] fn main() { let value = [1, 1, 2, 3, 5]; println!("value: {:#?}", value); }
位置参数 Positional parameters
使用参数的位置索引, 比如 {0}
, {1}
, {2}
这种的.
如果省略了里面的位置标记, 那就会提供一个计数器, 从0开始计数.
看一个例子:
fn main() { let s = format!("{} {} {}", 1, 2, 3); assert_eq!(s, "1 2 3"); // `{}` 从 0 开始计数 let s = format!("{1} {} {2}", 1, 2, 3); assert_eq!(s, "2 1 3"); let s = format!("{1} {2} {0}", 1, 2, 3); assert_eq!(s, "2 3 1"); }
具名参数 Named parameters
还支持类似于 python 中的那种格式化, 使用参数名称, 实现更灵活更易读的参数引用:
fn main() { println!("Jolia {age}", age = 25); let value = [1, 1, 2, 3, 5]; println!("value: {value:?}"); }
格式化参数 Formatting parameters
宽度 Width
可以指定参数所占用的最小宽度 (最少字符数), 如果宽度不够, 可以使用空格或者指定的字符来填充.
指定参数占用的宽度值, 该值需要是 usize
类型, 比如 {name:width$}
, 这里的 name
是要被
格式化的参数, 而 width
参数就指定了它占用的宽度, 注意 width
后面那个 $
是作为后缀存在的.
fn main() { let expected = "Hello x !"; // 注意这里是直接指定宽度值 5 let s = format!("Hello {:5}!", 'x'); assert_eq!(s, expected); // 这里 `{:1$}` 引用了位置参数 1 的值, 作为字符串的宽度值 let s = format!("Hello {0:1$}!", "x", 5); assert_eq!(s, expected); // 这里交换了被格式化的参数与位置参数的索引位置. let s = format!("Hello {1:0$}!", 5, 'x'); assert_eq!(s, expected); // 使用了具名参数作为宽度值 let s = format!("Hello {:width$}!", 'x', width = 5); assert_eq!(s, expected); let width: usize = 5; // 使用了具名参数作为宽度值 let s = format!("Hello {:width$}!", 'x'); assert_eq!(s, expected); }
对齐方式与填充字符 Alignment and fill
在设定了字符串的宽度值时, 同时可以指定字符串的对齐方式以及多余空间要填充的字符.
字符串的对齐方式有三种:
<
左对齐, 如果不指定对齐方式, 默认就是左对齐^
居中>
右对齐
字符串宽度与对齐方式的语法是 string:char<width
, 可以这样解析:
- 左对齐, 也可以是右中对齐或者右对齐, 要注意的是这里并没有考虑有些语言中从右到左的布局方式 (R2L), 我们默认是L2R
- 字符串的宽度值是
width
, 比如5
- 空白处要填充的字符是
char
, 比如-
或者.
- 如果指定了要填充的字符, 则必须同时指定对齐方式
- 如果没有指定对齐方式, 默认是左对齐
- 默认的填充字符是空格
看一些代码示例:
fn main() { let width: usize = 5; // 左对齐 let s = format!("Hello {:<width$}!", 'x'); assert_eq!(s, "Hello x !"); // 居中对齐 let s = format!("Hello {:^width$}!", 'x'); assert_eq!(s, "Hello x !"); // 右对齐 let s = format!("Hello {:>width$}!", 'x'); assert_eq!(s, "Hello x!"); // 左对齐, 使用 `.` 填充 let s = format!("Hello {:.<width$}!", 'x'); assert_eq!(s, "Hello x....!"); // 居中对齐, 使用 `-` 填充 let s = format!("Hello {:-^width$}!", 'x'); assert_eq!(s, "Hello --x--!"); // 右对齐, 用 `0` 填充 let s = format!("Hello {:0>5}", 'x'); assert_eq!(s, "Hello 0000x"); // 整数的十六进制形式, 右对齐, 字符串宽度值为8, 使用 `0` 填充空位 let s = format!("Hello 0x{:0>8x}", 12345678); assert_eq!(s, "Hello 0x00bc614e"); }
数值的符号与填充
上面讲到的对齐与字符宽度等, 都是通用的格式化手段. 这里要介绍的是数值类型特有的格式化手段.
+
, 默认情况下, 只要负数才会被打印-
符号, 而无符号数以及正数, 都会忽略掉符号位. 使用+
可以强制打印数值的符号-
, 目前不支持, 会被忽略掉0
, 使用0
进行数值填充, 而且对齐方式被强制为左对齐#
表示要使用另外一种格式化形式:?
, 使用 pretty-printing 形式调用fmt::Debug
trait, 会添加换行符和缩进x
, 使用小写的十六进制格式, 并且添加0x
前缀X
, 使用大写的十六进制格式, 并且添加0X
前缀b
, 使用小写的二进制格式, 并且添加0b
前缀o
, 使用八进制格式, 并且添加0o
前缀
看下面的示例代码:
fn main() { let x: i32 = 42; // 强制指定 `+` 符号 let s = format!("Hello, {x:+}"); assert_eq!(s, "Hello, +42"); // 使用 `0` 作为填充字符, 需要指定字符串的宽度值 let s = format!("Hello, {x:#04}"); assert_eq!(s, "Hello, 0042"); // 小写的十六进制 let s = format!("Hello, {x:#x}"); assert_eq!(s, "Hello, 0x2a"); // 大写的十六进制 let s = format!("Hello, {x:#X}"); assert_eq!(s, "Hello, 0x2A"); // 二进制 let s = format!("Hello, {x:#b}"); assert_eq!(s, "Hello, 0b101010"); // 八进制 let s = format!("Hello, {x:#o}"); assert_eq!(s, "Hello, 0o52"); }
数值精度 Precision
常用格式化符号 Formatting Traits
println!("{formatting}", arg);
, 这里的 formatting
就是本节要讨论的 Formatting traits,
标准库里定义了一些 traits, 用于修饰被格式化的参数, 以得到期望的形式.
符号 | 描述 |
---|---|
空白 | 调用 fmt::Display trait |
:? | 调用 fmt::Debug trait |
:x? | 调用 fmt::Debug trait, 并使用小写的十六进制整数 |
:X? | 调用 fmt::Debug trait, 并使用大写的十六进制整数 |
:o | 调用 fmt::Octal trait, 转换成八进制 |
:x | 调用 fmt::LowerHex trait, 转换成小写的十六进制 |
:X | 调用 fmt::UpperHex trait, 转换成大写的十六进制 |
:b | 调用 fmt::Binary trait, 转换成二进制 |
:e | 调用 fmt::LowerExp trait, 转换成小写的科学计数法格式 |
:E | 调用 fmt::UpperExp trait, 转换成大写的科学计数法格式 |
:p | 调用 fmt::Pointer trait, 转换成指针形式 |
在标准库中已经为很多基础数据类型实现了, 上表中列出来的 fmt
模块中的各个 traits.
下面的代码示例展示了如何使用格式化参数:
fn main() { let x = 42; println!("八进制: 0o{:o}", x); println!("十进制: {}", x); println!("小写的十六进制: 0x{:x}", x); println!("大写的十六进制: 0x{:X}", x); println!("二进制: 0b{:b}", x); println!("x 的地址: {:p}", &x); let x2 = 1234567890; println!("小写的科学计数法: {:e}", x2); println!("大写的科学计数法: {:E}", x2); let s = "Hello, Rust"; println!("Display: {}", s); println!("Debug: {:?}", s.as_bytes()); }
相关的宏定义
标准库中定义了一系列与字符串格式化相关的宏, 它们分别是:
format!
write!
writeln!
print!
println!
eprint!
eprintln!
format_args!
format!
宏
TODO