【图书介绍】《Rust编程与项目实战》-CSDN博客
《Rust编程与项目实战》(朱文伟,李建英)【摘要 书评 试读】- 京东图书 (jd.com)
Rust编程与项目实战_夏天又到了的博客-CSDN博客
我们编写程序的目的就是对输入进行处理,然后将处理结果反馈给用户,对于初学者一般是将处理结果直接显示在屏幕上。
Rust语言的打印操作主要是通过在std::fmt中定义一系列宏来处理。主要包括:
- format!:将格式化文本存入字符串。
- print!:与format!类似,但是把文本输出到控制台(io::stdout)。
- println!:与print!类似,但是输出结果末尾会追加换行符。
- eprint!:与format!类似,但是把文本输出到标准错误(std::stderr)。
- eprintln!:与eprint!类似,但是输出结果末尾会追加换行符。
- write!:与format!类似,但是把文本输出到&mut io::Write。
- writeln!:与write!类似,但是输出结果末尾会追加换行符。
Rust中的主要输出靠宏print!或println!,两者唯一不同的地方在于print!会将要输出的内容打印到控制台,println!会在输出的内容打印到控制台后进行换行。
print!接受的是可变参数,第一个参数是一个字符串常量,它表示最终输出的字符串的格式,第一个参数可以看到类似于{}的符号,它相当于占位符,可以在最终的结果中按照指定的规则进行替换。比如:
print!("{}{}",1,2); //输出:"12"
其实我们已经多次接触过输出print!了,已经知道了可以用它来输出字符串、数字等,其实还有很多格式化输出的方式。下面一起来了解一下。
4.2.1 默认输出
默认输出就是直接输出字符串常量。比如:
println("hello wolrd"); //输出字符串hello wolrd
4.2.2 通配符{}
在字符串字面量中使用{}通配符代指即将输出的值,后面依次对应输出的值。如果有多个值,则中间需使用英文逗号“,”分隔。示例代码如下:
println!("今天是 {} 年 {} 月 {} 日", 2022, 6, 18); //输出:今天是 2022 年 6 月 18 日
4.2.3 通配符和位置
输出时可以在通配符{}中添加要输出值的位置(从0开始),来代指当前要输出哪个位置的值。示例代码如下:
println!("{0} 的平方是 {0}, {0} 的相反数是 {1}", 1, -1);
输出结果如下:
1 的平方是 1, 1 的相反数是 -1
又比如:
fn main() {
// 按照顺序进行格式化
// 输出: 10 is a number, 'hello world' is a string
println!("{} is a number, '{}' is a string", 10, "hello world");
// 按照索引进行格式化
// 输出:10 is a number, 'hello world' is a string
println!("{1} is a number, '{0}' is a string", "hello world", 10);
// 按照顺序与索引混合进行格式化
// 没有索引的{}会按照迭代顺序进行格式化
// 输出:2 1 1 2
println!("{1} {} {0} {}", 1, 2)
}
输出结果如下:
10 is a number, 'hello world' is a string
10 is a number, 'hello world' is a string
2 1 1 2
又比如:
print!("{1}{0}",1,2); //输出:21
print!("{1}{}{0}{}",1,2); //输出:2112
4.2.4 通配符和命名参数
输出时可以在通配符{}中添加要输出值的命名参数,来代指当前要输出哪个命名参数的值。示例代码如下:
println!("我的名字叫{name}, 今年{age}岁, 喜欢{hobby}", hobby = "打篮球", name = "张三", age = 18);
输出结果如下:
我的名字叫张三, 今年18岁, 喜欢打篮球
又比如:
print!("{arg}",arg = "tyest"); //=>"test"
print!("{name} {}",1, name =2); //=>"2 1"
print!("{a} {c} {b}",a ="a",b ='b', c=3); //=>"a 3 b"
需要注意的是,带名字的参数必须放在不带名字的参数的后面,以下例子无法通过编译:
print!("{abc} {1}", abc = "def", 2);
4.2.5 输出不同的进制数
可以在通配符{}中添加不同的符号来实现二进制、八进制、十六进制数的输出。常用符号如表4-5所示。
表4-5 常用符号
格 式 | 说 明 |
{:b} | 输出结果转为二进制 |
{:o} | 输出结果转为八进制 |
{:x} | 输出结果转为十六进制(小写) |
{:X} | 输出结果转为十六进制(大写) |
{:e} | 科学记数(小写) |
{:E} | 科学记数(大写) |
{:p} | 输出指针 |
(续表)
格 式 | 说 明 |
{:?} | 打印Debug |
{:+} | 如果数值类型是整数,则前置打印+号 |
还可以添加0,用来在整数格式化时填充宽度,格式说明如表4-6所示。
表4-6 添加0,用来在整数格式化时填充宽度
格 式 | 说 明 |
{:08b} | 输出8位二进制数,不足8位使用0填充 |
{:08o} | 输出8位八进制数,不足8位使用0填充 |
{:016x} | 输出8位十六进制数,不足16位使用0填充 |
还有个#要注意一下,这应该算是一个补充标记符,常与其他字符连用,格式说明如表4-7所示。
表4-7 #与其他字符连用格式说明
格 式 | 说 明 |
{:#b} | 在输出的二进制数前添加0b |
{:#o} | 在输出的八进制数前添加0o |
{:#x} | 在输出的十六进制数前添加0x(x是小写) |
{:#X} | 在输出的十六进制数前添加0x(x是小写) |
{:#?} | 带换行和缩进的Debug打印 |
示例代码如下:
fn main() {
println!("{},{}", 1, 2); //输出十进制数
//输出二进制数
println!("{:b},{:b},{:b}", 0b11_01,0b1100,0b111111111000000001);
println!("{:b},{:b}", 8,16);
println!("{:b},{:b}", 0xF,0xe);
//输出八进制数
println!("0o{:o}", 10); // 0o12
println!("{:#o}", 10); // 0o12
//十六进制小写
println!("0x{:x}", 0xFF); //0xff
println!("{:#x}", 0xFF); //0xff
//十六进制大写
println!("0x{:X}", 0xFF); // 0xFF
println!("{:#X}", 0xFF); // 0xFF
}
二进制常数需要以0b开头,十六进制常数需要以0x开头。输出结果如下:
1,2
1101,1100,111111111000000001
1000,10000
1111,1110
0o12
0o12
0xff
0xff
0xFF
0xFF
又比如:
fn main() {
let a = 31;
println!("二进制 {:b}", a);
println!("八进制 {:o}", a);
println!("十六进制(小写){:x}", a);
println!("十六进制(大写){:X}", a);
println!("输出标点 {:+}", 5);
println!("前置符二进制 {:#b}", a);
println!("前置符八进制 {:#o}", a);
println!("前置符十六进制(小写){:#x}", a);
println!("前置符十六进制(大写){:#X}", a);
println!("二进制8位补零 {:08b}", a);
println!("八进制8位补零 {:08o}", a);
println!("十六进制16位补零 {:016b}", a);
}
输出结果如下:
二进制 11111
八进制 37
十六进制(小写) 1f
十六进制(大写) 1F
输出标点 +5
前置符二进制 0b11111
前置符八进制 0o37
前置符十六进制(小写) 0x1f
前置符十六进制(大写) 0x1F
二进制8位补零 00011111
八进制8位补零 00000037
十六进制16位补零 0000000000011111
4.2.6 指定宽度
指定宽度的形式如下。
- {:n}:通过数字直接指定宽度,比如{:5}。
- {:n$}:通过参数索引指定宽度,比如{:1$}。
- {:name$}:通过具名参数指定宽度,比如{:width$}。
需要注意的是,这里指定的是最小宽度,如果字符串不足宽度会进行填充,如果超出并不会截断。示例代码如下:
fn main() {
// 不足长度5,填充空格
// 输出: Hello a !
println!("Hello {:5}!", "a");
// 超出长度1,仍然完整输出
// 输出: Hello abc!
println!("Hello {:1}!", "abc");
// 因为未指定索引,所以按照顺序位置引用的是"abc"
// 通过$符指定了宽度的索引为1,即宽度为5
// 输出: Hello abc !
println!("Hello {:1$}!", "abc", 5);
// 指定了位置索引为1,使用$符指定了宽度的索引为0,即宽度为5
// 输出: Hello abc !
println!("Hello {1:0$}!", 5, "abc");
// 通过具名参数指定宽度为5
// 输出:Hello abc !
println!("Hello {:width$}!", "abc", width = 5);
let width = 5;
println!("Hello {:width$}!", "abc");
}
输出结果如下:
Hello a !
Hello abc!
Hello abc !
Hello abc !
Hello abc !
Hello abc !
4.2.7 填充与对齐
如果不指定对齐方式,Rust会用默认的方式进行填充和对齐。对于非数字,采用的是空格填充左对齐,数字采用的是空格填充右对齐。在格式化字符串内,一般按照“:XF”来排列,其中X表示要填充的字符,F表示对齐方式,有三种对齐方式:<表示左对齐,^表示居中对齐,>表示右对齐。比如“:-<7”,-表示填充字符,<表示左对齐,7表示总宽度是7。
特别要注意的是,有些类型可能不会实现对齐。特别是对于Debug trait,通常不会实现该功能。确保应用填充的一种好方法是格式化输入,再填充此结果字符串以获得输出。示例代码如下:
fn main() {
// 非数字默认左对齐,空格填充
assert_eq!(format!("Hello {:7}!", "abc"), "Hello abc !");
println!("Hello {:7}!", "abc");
// 左对齐
assert_eq!(format!("Hello {:<7}!", "abc"), "Hello abc !");
println!("Hello {:<7}!", "abc");
// 左对齐,使用-填充
assert_eq!(format!("Hello {:-<7}!", "abc"), "Hello abc----!");
println!("Hello {:-<7}!", "abc");
// 右对齐,使用-填充
assert_eq!(format!("Hello {:->7}!", "abc"), "Hello ----abc!");
println!("Hello {:->7}!", "abc");
// 中间对齐,使用-填充
assert_eq!(format!("Hello {:-^7}!", "abc"), "Hello --abc--!");
println!("Hello {:-^7}!", "abc");
// 数字默认右对齐,使用空格填充
assert_eq!(format!("Hello {:7}!", 7), "Hello 7!");
println!("Hello {:7}!", 7);
// 左对齐
assert_eq!(format!("Hello {:<7}!", 7), "Hello 7 !");
println!("Hello {:<7}!", 7);
// 居中对齐
assert_eq!(format!("Hello {:^7}!", 7), "Hello 7 !");
println!("Hello {:^7}!", 7);
// 填充0
assert_eq!(format!("Hello {:07}!", 7), "Hello 0000007!");
println!("Hello {:07}!", 7);
// 负数填充0,负号会占用一位
assert_eq!(format!("Hello {:07}!", -7), "Hello -000007!");
println!("Hello {:07}!", -7);
}
assert_eq!用于判断两者是否相等。如果没有报错输出,则说明全部正确。输出结果如下:
Hello abc !
Hello abc !
Hello abc----!
Hello ----abc!
Hello --abc--!
Hello 7!
Hello 7 !
Hello 7 !
Hello 0000007!
Hello -000007!
4.2.8 指定小数的精确值
常用的3种指定小数精确值的方式如表4-8所示。
表4-8 常用的3种指定小数精确值的方式
格 式 | 说 明 |
{:.3} | 小数点后精确度保留3位(不足补零,多余截断),这种方式精度写死了,不灵活 |
{1:.0$} | “1”和“.0”分别对应两个数字,一个是精度(小数位数),另一个是要显示的小数。其中,“1”相当于一个占位符,其对应的数字用来指定小数的位数(不足补零,多余截断)。“.0”所对应的数字就是要显示的小数,比如println!("小数保留位数 {1:.0$} ", 3, 0.01);。这种方式比上一种方式灵活,精度值相当于一个变量,可以自由控制 |
{:.*} | 这种格式也对应两个数字,一个是精度(小数位数,不足补零,多余截断),另一个是具体小数。这种格式书写更加简洁 |
示例代码如下:
fn main() {
println!("科学记数(小写){:e}", 100000_f32);
println!("科学记数(大写){:E}", 100000_f32);
println!("小数保留位数 {:.3} ", 0.01);
println!("小数保留位数 {1:.0$} ", 3, 0.01);
println!("{}小数保留3位数 {:.*} --- 保留4位数 {:.*} ", 0.01, 3, 0.01, 4, 0.10);//3和0.01是一对
}
输出结果如下:
科学记数(小写)1e5
科学记数(大写)1E5
小数保留位数 0.010
小数保留位数 0.010
0.01小数保留3位数 0.010 --- 保留4位数 0.1000
4.2.9 输出{和}
有时可能在字符串中包含{和},这里可以通过{{输出{,}}输出}。示例代码如下:
fn main() {
println!("左边的括号 {{");
println!("右边的括号 }}");
println!("全括号 {{}}");
}
输出结果如下:
左边的括号 {
右边的括号 }
全括号 {}
4.2.10 格式化宏format!
格式化的用法和输出的用法基本相同,它的用途是写格式化文本到字符串。上一小节代码的所有println!都可以换成format!,使用format!常用于格式化多个变量为字符串,println则会直接输出到屏幕。比如:
fn main() {
let mut s = format!("左边的括号 {{");
println!("{}",s);
s = format!("右边的括号 }}");
println!("{}",s);
println!("{}",format!("全括号 {{}}")); //把format!写在println!中,更加简洁
}
输出结果如下:
左边的括号 {
右边的括号 }
全括号 {}
又比如:
fn main() {
let s1="I";
let s2="love";
let s3="China";
let s = format!("{}-{}-{}",s1,s2,s3);
println!("{}",s);
println!("{}",format!("{}-{}-{}","I","love","you"));
}
输出结果如下:
I-love-China
I-love-you