add格式化输出

pull/107/head
sunface 3 years ago
parent c04cb28846
commit 6edaa7364a

@ -58,7 +58,7 @@
- [闭包closure](advance/functional-programing/closure.md) - [闭包closure](advance/functional-programing/closure.md)
- [迭代器iterator](advance/functional-programing/iterator.md) - [迭代器iterator](advance/functional-programing/iterator.md)
- [深入类型之newtype和Sized](advance/custom-type.md) - [深入类型之newtype和Sized](advance/custom-type.md)
- [格式化输出 todo](advance/formatted-output.md) - [格式化输出](advance/formatted-output.md)
- [文档注释 todo](advance/comment.md) - [文档注释 todo](advance/comment.md)
- [包和模块 todo](advance/crate-module.md) - [包和模块 todo](advance/crate-module.md)
- [智能指针 todo](advance/smart-pointer.md) - [智能指针 todo](advance/smart-pointer.md)
@ -167,6 +167,7 @@
- [Benchmark性能测试(todo)](performance/benchmark.md) - [Benchmark性能测试(todo)](performance/benchmark.md)
- [减少Runtime check(todo)](performance/runtime-check.md) - [减少Runtime check(todo)](performance/runtime-check.md)
- [CPU缓存性能优化](performance/cpu-cache.md) - [CPU缓存性能优化](performance/cpu-cache.md)
- [计算性能优化](performance/calculate.md)
- [堆和栈](performance/heap-stack.md) - [堆和栈](performance/heap-stack.md)
- [常用性能测试工具](performance/tools.md) - [常用性能测试工具](performance/tools.md)

@ -1 +1,348 @@
# formatted-output.md # 格式化输出
提到格式化输出,可能很多人立刻就想到`"{}"`但是Rust能做到的远比这个多的多本章节我们将深入讲解格式化输出的各个方面。
## 满分初印象
先来一段代码,看看格式化输出的初印象:
```rust
println!("Hello"); // => "Hello"
println!("Hello, {}!", "world"); // => "Hello, world!"
println!("The number is {}", 1); // => "The number is 1"
println!("{:?}", (3, 4)); // => "(3, 4)"
println!("{value}", value=4); // => "4"
println!("{} {}", 1, 2); // => "1 2"
println!("{:04}", 42); // => "0042" with leading zeros
```
可以看到`println!`宏接受的是可变参数,第一个参数是一个字符串常量,它表示最终输出字符串的格式, 包含其中形如`{}`的符号是**占位符**, 会被`println!`后面的参数依次替换。
## `print!`, `println!`, `format!`
它们是Rust中用来格式化输出的三大金刚用途如下
- `print!`, 将格式化文本输出到标准输出,不带换行符
- `println!`, 同上,但是在行的末尾添加换行符
- `format!`, 将格式化文本输出到`String`字符串
在实际项目中,最常用的是`println!`及`format!`,前者常用来调试输出,后者用来生成格式化的字符串:
```rust
fn main() {
let s = "hello";
println!("{}, world",s);
let s1 = format!("{}, world", s);
print!("{}",s1);
print!("{}\n","!");
}
```
其中, `s1`是通过`format!`生成的`String`字符串,最终输出如下:
```console
hello, wolrd
hello, world!
```
#### `eprint!`, `eprintln!`
除了三大金刚外,还有两大护法,使用方式跟`print!`,`println!`很像,但是它们输出到标准错误输出:
```rust
eprintln!("Error: Could not complete task")
```
它们仅应该被用于输出错误信息和进度信息,其它场景都应该使用`print!`系列。
## {}与{:?}
与其它语言常用的`%d`,`%s`不同Rust特立独行的选择了`{}`作为格式化占位符(说到这个有点想吐槽下Rust中自创的概念其实还挺多的真不知道该夸奖还是该吐槽- , -),事实证明,这种选择非常正确,它帮助用户减少了很多使用成本,你无需再为特定的类型选择特定的占位符,统一用`{}`来替代即可剩下的类型推导等细节只要交给Rust去做。
与`{}`类似,`{:?}`也是占位符:
- `{}`适用于实现了`std::fmt::Display`特征的类型,用来以更优雅、更友好的方式格式化文本,例如展示给用户
- `{:?}`适用于实现了`std::fmt::Debug`特征的类型,用于调试场景
其实两者的选择很简单,当你在写代码需要调试时,使用`{:?}`,剩下的场景,选择`{}`。
#### `Debug`特征
事实上为了方便我们调试大多数Rust类型都实现了`Debug`特征或者支持派生该特征:
```rust
#[derive(Debug)]
struct Person {
name: String,
age: u8
}
fn main() {
let i = 3.1415926;
let s = String::from("hello");
let v = vec![1,2,3];
let p = Person{name: "sunface".to_string(),age: 18};
println!("{:?}, {:?}, {:?},{:?}",i,s,v,p);
}
```
对于数值、字符串、数组,可以直接使用`{:?}`进行输出,但是对于结构体,需要[派生`Debug`](../appendix/derive.md)特征后,才能进行输出,总之很简单.
#### `Display`特征
与大部分类型实现了`Debug`不同,实现了`Display`特征的Rust类型并没有那么多往往需要我们自定义想要的格式化方式:
```rust
let i = 3.1415926;
let s = String::from("hello");
let v = vec![1,2,3];
let p = Person{name: "sunface".to_string(),age: 18};
println!("{}, {}, {},{}",i,s,v,p);
```
运行后可以看到`v`和`p`都无法通过编译,因为没有实现`Display`特征,但是你又不能像派生`Debug`一般派生`Display`,只能另寻他法:
- 使用`{:?}`或`{:#?}`
- 为自定义类型实现`Display`特征
- 使用`newtype`为外部类型实现`Display`特征
下面来一一看看这三种方式。
#### {:#?}
`{:#?}`与`{:?}`几乎一样,唯一的区别在于它能更优美的输出内容:
```console
// {:?}
[1, 2, 3],Person { name: "sunface", age: 18 }
// {:#?}
[
1,
2,
3,
],Person {
name: "sunface",
}
```
因此对于`Display`不支持的类型,可以考虑使用`{:#?}`进行格式化,虽然理论上它更适合进行调试输出。
#### 为自定义类型实现`Display`特征
如果你的类型是定义在当前作用域中的,那么可以为其实现`Display`特征,即可用于格式化输出:
```rust
struct Person {
name: String,
age: u8
}
use std::fmt;
impl fmt::Display for Person {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "大佬在上,请受我一拜,小弟姓名{},年芳{},家里无田又无车,生活苦哈哈",self.name,self.age)
}
}
fn main() {
let p = Person{name: "sunface".to_string(),age: 18};
println!("{}", p);
}
```
如上所示,只要实现`Display`特征中的`fmt`方法,即可为自定义结构体`Person`添加自定义输出:
```console
大佬在上请受我一拜小弟姓名sunface年芳18家里无田又无车生活苦哈哈
```
#### 为外部类型实现`Display`特征
在Rust中无法直接为外部类型实现外部特征但是可以使用[`newtype`](./custom-type.md#newtype)解决此问题:
```rust
struct Array(Vec<i32>);
use std::fmt;
impl fmt::Display for Array {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "数组是:{:?}",self.0)
}
}
fn main() {
let arr = Array(vec![1, 2, 3]);
println!("{}", arr);
}
```
`Array`就是我们的`newtype`,它将想要格式化输出的`Vec`包裹在内,最后只要为`Arraw`实现`Display`特征,即可进行格式化输出:
```console
数组是:[1, 2, 3]
```
至此,关于`{}`与`{:?}`的内容已介绍完毕,下面让我们正式开始格式化输出的旅程。
## 指定位置参数
除了按照依次顺序使用值去替换占位符之外,还能让指定位置的参数去替换某个占位符,例如`{1}`,表示用第二个参数替换该占位符(索引从0开始):
```rust
fn main() {
println!("{}{}",1,2); // =>"12"
println!("{1}{0}",1,2); // =>"21"
// => Alice, this is Bob. Bob, this is Alice
println!("{0}, this is {1}. {1}, this is {0}", "Alice", "Bob");
println!("{1}{}{0}{}",1,2); // => 2112
}
```
## 带名称的变量
除了像上面那样指定位置外,我们还可以为参数指定名称:
```rust
fn main() {
println!("{argument}", argument = "test"); // => "test"
println!("{name} {}", 1, name = 2); // => "2 1"
println!("{a} {c} {b}", a = "a", b = 'b', c = 3); // => "a 3 b"
}
```
需要注意的是: **带名称的参数必须放在不带名称参数的后面**,例如下面代码将报错:
```rust
println!("{abc} {1}", abc = "def", 2);
```
## 格式化参数
格式化输出,意味着对输出格式会有更多的要求,例如只输出浮点数的小数点后两位:
```rust
fn main() {
let v = 3.1415926;
// Display => 3.14
println!("{:.2}",v);
// Debug => 3.14
println!("{:.2?}",v);
}
```
上面代码只输出小数点后两位。同时我们还展示了`{}`和`{:?}`的用法,后面如无特殊区别,就只针对`{}`提供格式化参数说明。
接下来让我们一起来看看Rust中有哪些格式化参数。
#### 宽度
宽度用来指示输出目标的长度,如果长度不够,则进行填充和对齐:
##### 字符串填充
字符串格式化默认使用空格进行填充,并且进行左对齐.
```rust
fn main() {
//-----------------------------------
// 以下全部输出 "Hello x !"
// 为"x"后面填充空格补齐宽度5
println!("Hello {:5}!", "x");
// 使用参数5来指定宽度
println!("Hello {:1$}!", "x", 5);
// 使用x作为占位符输出内容同时使用5作为宽度
println!("Hello {1:0$}!", 5, "x");
// 使用有名称的参数作为宽度
println!("Hello {:width$}!", "x", width = 5);
//-----------------------------------
// 使用参数5为参数x指定宽度,同时在结尾输出参数5 => Hello x !5
println!("Hello {:1$}!{}", "x", 5);
}
```
##### 数字填充:符号和0
数字格式化默认也是使用空格进行填充,但与字符串左对齐不同的是,数字是右对齐。
```rust
fn main() {
// 宽度是5 => Hello 5!
println!("Hello {:5}!", 5);
// 显式的输出正号 => Hello +5!
println!("Hello {:+}!", 5);
// 宽度5使用0进行填充 => Hello 00005!
println!("Hello {:05}!", 5);
// 负号也要占用一位宽度 => Hello -0005!
println!("Hello {:05}!", -5);
}
```
##### 对齐
```rust
fn main() {
// 以下全部都会补齐5个字符的长度
// 左对齐 => Hello x !
println!("Hello {:<5}!","x");
// 右对齐 => Hello x
println!("Hello {:>5}!","x");
// 居中对齐 => Hello x !
println!("Hello {:^5}!","x");
// 对齐并使用指定符号填充 => Hello x&&&&!
// 指定符号填充的前提条件是必须有对齐字符
println!("Hello {:&<5}!", "x");
}
```
#### 精度
精度可以用于控制浮点数的精度或者字符串的长度
```rust
fn main() {
let v = 3.1415926;
// 保留小数点后两位 => 3.14
println!("{:.2}",v);
// 带符号保留小数点后两位 => +3.14
println!("{:+.2}",v);
// 不带小数 => 3
println!("{:.0}",v);
// 通过参数来设定精度 => 3.1416,相当于{:.4}
println!("{:.1$}", v, 4);
let s = "hi我是Sunface孙飞";
// 保留字符串前三个字符 => hi我
println!("{:.3}", s);
// {:.*}接收两个参数,第一个是精度,第二个是被格式化的值 => Hello abc!
println!("Hello {:.*}!", 3, "abcdefg");
}
```
#### 进制
可以使用`#`号来控制数字的进制输出:
- #b, 二进制
- #o, 八进制
- #x, 小写十六进制
- #X, 大写十六进制
- x, 不带前缀的小写十六进制
```rust
fn main() {
// 二进制 => 0b11011!
println!("{:#b}!", 27);
// 八进制 => 0o33!
println!("{:#o}!", 27);
// 十进制 => 27!
println!("{}!", 27);
// 小写十六进制 => 0x1b!
println!("{:#x}!", 27);
// 大写十六进制 => 0x1B!
println!("{:#X}!", 27);
// 不带前缀的十六进制 => 1b!
println!("{:x}!", 27);
// 使用0填充二进制宽度为10 => 0b00011011!
println!("{:#010b}!", 27);
}
```
#### 指数
```rust
fn main() {
println!("{:2e}", 1000000000); // => 1e9
println!("{:2E}", 1000000000); // => 1E9
}
```
#### 指针地址
```rust
let v= vec![1,2,3];
println!("{:p}",v.as_ptr()) // => 0x600002324050
```
#### 转义
有时需要输出`{`和`}`,但这两个字符是特殊字符,需要进行转义:
```rust
fn main() {
// {使用{转义,}使用} => Hello {}
println!("Hello {{}}");
// 下面代码会报错,因为占位符{}只有一个右括号},左括号被转义成字符串的内容
// println!("{{ Hello }");
}
```
## 总结
把这些格式化都牢记在脑中是不太现实的也没必要我们要做的就是知道Rust支持相应的格式化输出在需要之时读者再来查阅本文即可。
还是那句话,[<<Rust>>](https://github.com/sunface/rust-course)不仅仅是Rust学习书籍还是一本厚重的工具书

@ -1,5 +1,7 @@
# match和if let # match和if let
在Rust中模式匹配最常用的就是`match`和`if let`,本章节将对两者及相关的概念进行详尽介绍。
先来看一个关于`match`的简单例子: 先来看一个关于`match`的简单例子:
```rust ```rust
enum Direction { enum Direction {
@ -264,6 +266,39 @@ if let Some(3) = some_u8_value {
这两种匹配对于新手来说,可能有些难以抉择,但是只要记住一点就好: **当你只要匹配一个条件,且忽略其他条件时就用`if let`否则都用match**. 这两种匹配对于新手来说,可能有些难以抉择,但是只要记住一点就好: **当你只要匹配一个条件,且忽略其他条件时就用`if let`否则都用match**.
## matches!宏
Rust标准库中提供了一个非常实用的宏:`matches!`,它可以将一个表达式跟模式进行匹配,然后返回匹配的结果`true` or `false`.
例如,有一个动态数组,里面存有以下枚举:
```rust
enum MyEnum {
Foo,
Bar
}
fn main() {
let v = vec![MyEnum::Foo,MyEnum::Bar,MyEnum::F
}
```
现在如果想对`v`进行过滤,只保留类型是`MyEnum::Foo`的元素,你可能想这么写:
```rust
v.iter().filter(|x| x == MyEnum:::Foo);
```
但是,实际上这行代码会保存,因为你无法将`x`跟一个类型进行比较。好在,你可以使用`match`来完成,但是会导致代码更为啰嗦,是否有更简洁的方式?答案是使用`matches!`:
```rust
v.iter().filter(|x| matches!(x, MyEnum::Foo));
```
很简单也很简洁,再来看看更多的例子:
```rust
let foo = 'f';
assert!(matches!(foo, 'A'..='Z' | 'a'..='z'));
let bar = Some(4);
assert!(matches!(bar, Some(x) if x > 2));
```
## 变量覆盖 ## 变量覆盖
无论是是`match`还是`if let`,他们都可以在模式匹配时覆盖掉老的值,绑定新的直: 无论是是`match`还是`if let`,他们都可以在模式匹配时覆盖掉老的值,绑定新的直:

@ -351,6 +351,9 @@ fn main() {
在泛型参数之前Rust完全不适合复杂矩阵的运算自从有了const泛型一切即将改变。 在泛型参数之前Rust完全不适合复杂矩阵的运算自从有了const泛型一切即将改变。
## const fn
@todo
## 泛型的性能 ## 泛型的性能
在Rust中泛型是零消耗的抽象意味着你在使用泛型时完全不用担心性能上的问题。 在Rust中泛型是零消耗的抽象意味着你在使用泛型时完全不用担心性能上的问题。

@ -1 +1,3 @@
# 优化编译速度 # 优化编译速度
https://www.reddit.com/r/rust/comments/rnkyc0/why_does_my_code_compile_faster_on_nightly/

@ -1,2 +1,4 @@
https://www.reddit.com/r/rust/comments/rjumsg/any_good_resources_for_learning_rust_macros/ https://www.reddit.com/r/rust/comments/rjumsg/any_good_resources_for_learning_rust_macros/
https://www.reddit.com/r/rust/comments/roaofg/procedural_macros_parsing_custom_syntax/

@ -0,0 +1,138 @@
# 计算性能优化
https://www.reddit.com/r/rust/comments/rn7ozz/find_perfect_number_comparison_go_java_rust/
```go
package main
import (
"fmt"
"math"
"time"
)
func main() {
n := 320000
nums := make(map[int][]int)
start := time.Now()
calPerfs(n, nums)
fmt.Printf("runtime: %s\n", time.Since(start))
}
func calPerfs(n int, nums map[int][]int) {
for i := 1; i <= n; i++ {
d := divs(i)
if sum(d) == i {
nums[i] = all(d)
}
}
}
func divs(num int) map[int]struct{} {
r := make(map[int]struct{})
r[1] = struct{}{}
mid := int(math.Sqrt(float64(num)))
for i := 2; i <= mid; i++ {
if num%i == 0 {
r[i] = struct{}{}
r[num/i] = struct{}{}
}
}
return r
}
func sum(ds map[int]struct{}) int {
var n int
for k := range ds {
n += k
}
return n
}
func all(ds map[int]struct{}) []int {
var a []int
for k := range ds {
a = append(a, k)
}
return a
}
```
## 120ms
```rust
use std::time::Instant;
const N: usize = 320_000 ;
fn is_perfect(n: usize) -> bool {
//println!("{:?}", n);
let mut sum = 1;
let end = (n as f64).sqrt() as usize;
for i in 2..end + 1{
if n % i == 0 {
if i * i == n {
sum += i;
}
else {
sum += i + n / i;
}
}
}
sum == n
}
fn find_perfs(n: usize) -> Vec<usize> {
let mut perfs:Vec<usize> = vec![];
for i in 2..n + 1 {
if is_perfect(i) {
perfs.push(i)
}
}
perfs
}
fn main() {
let start = Instant::now();
let perfects = find_perfs(N);
println!("{:?}", start.elapsed());
println!("{:?}, in {:?}", perfects, N);
}
```
## 90ms
```rust
use {
std::{time::Instant},
};
const N: usize = 320000;
// Optimized, takes about 320ms on an Core i7 6700 @ 3.4GHz
fn cal_perfs2(n: usize) -> Vec<usize> {
(1..=n)
.into_iter()
.filter(|i| cal2(*i) == *i)
.collect::<Vec<_>>()
}
fn cal2(n: usize) -> usize {
(2..=(n as f64).sqrt() as usize)
.into_iter()
.filter_map(|i| if n % i == 0 { Some([i, n / i]) } else { None })
.map(|a| a[0] + a[1])
.sum::<usize>()
+ 1
}
fn main() {
let start = Instant::now();
let perf2 = cal_perfs2(N);
println!("{:?}",perf2);
println!("Optimized: {:?}", start.elapsed());
}
```

@ -20,3 +20,10 @@ for item in collection {
第一种方式是循环索引,然后通过索引下标去访问集合,第二种方式是直接循环迭代集合中的元素,优劣如下: 第一种方式是循环索引,然后通过索引下标去访问集合,第二种方式是直接循环迭代集合中的元素,优劣如下:
- **性能**:第一种使用方式中`collection[index]`的索引访问,会因为边界检查(bounds checking)导致运行时的性能损耗 - Rust会检查并确认`index`是落在集合内也就是合法的,但是第二种直接迭代的方式就不会触发这种检查,因为编译器会在编译时就完成分析并证明这种访问是合法的` - **性能**:第一种使用方式中`collection[index]`的索引访问,会因为边界检查(bounds checking)导致运行时的性能损耗 - Rust会检查并确认`index`是落在集合内也就是合法的,但是第二种直接迭代的方式就不会触发这种检查,因为编译器会在编译时就完成分析并证明这种访问是合法的`
## Box::leak
https://www.reddit.com/r/rust/comments/rntx7s/why_use_boxleak/
## bounds check
https://www.reddit.com/r/rust/comments/rnbubh/whats_the_big_deal_with_bounds_checking/

@ -1,3 +1,5 @@
# 最佳实践 # 最佳实践
https://www.reddit.com/r/rust/comments/rgjsbt/whats_your_top_rust_tip_crate_tool_other_for/ https://www.reddit.com/r/rust/comments/rgjsbt/whats_your_top_rust_tip_crate_tool_other_for/
https://www.reddit.com/r/rust/comments/rnmmqz/question_how_to_keep_code_dry_when_many_similar/

@ -22,3 +22,5 @@
11. [rustc开发者之书](https://rustc-dev-guide.rust-lang.org/method-lookup.html) 11. [rustc开发者之书](https://rustc-dev-guide.rust-lang.org/method-lookup.html)
12. [Rust Style](https://doc.rust-lang.org/1.6.0/style/README.html) 12. [Rust Style](https://doc.rust-lang.org/1.6.0/style/README.html)
13. [Learning Rust](https://learning-rust.github.io/docs/a1.why_rust.html)

@ -1,7 +0,0 @@
一些关于println的技巧
## 打印对象地址
```rust
let v= vec![1,2,3];
println!("{:p}",v.as_ptr())
```
Loading…
Cancel
Save