diff --git a/course-book/contents/basic/collections/hashmap.md b/course-book/contents/basic/collections/hashmap.md index c15c6467..77d08665 100644 --- a/course-book/contents/basic/collections/hashmap.md +++ b/course-book/contents/basic/collections/hashmap.md @@ -27,6 +27,8 @@ my_gems.insert("河边捡的误以为是宝石的破石头", 18); 所有的集合类型都是动态的,意味着它们没有固定的内存大小,因此它们底层的数据都存储在内存堆上,然后通过一个存储在栈中的引用类型来访问。同时,跟其它集合类型一致,`HashMap`也是内聚性的, 即所有的`K`必须拥有同样的类型,`V`也是如此。 +> 跟Vec一样,如果预先知道要存储的KV对个数,可以使用`HashMap::with_capacity(capacity)`创建指定大小的HashMap,避免频繁的内存分配和拷贝,提升性能 + #### 使用迭代器和collect方法创建 在实际使用中,不是所有的场景都能`new`一个哈希表后,然后悠哉悠哉的依次插入对应的键值对, 而是可能会从另外一个数据结构中,获取到对应的数据,最终生成`HashMap`. @@ -170,7 +172,7 @@ let score: Option<&i32> = scores.get(&team_name); 上面有几点需要注意: - `get`方法返回一个`Option<&i32>`类型:当查询不到时,会返回一个`None`,查询到时返回`Some(&i32)` -- `&32`是对`HashMap`中值的借用,如果不使用借用,可能会发生所有权的转移 +- `&i32`是对`HashMap`中值的借用,如果不使用借用,可能会发生所有权的转移 还可以通过循环的方式依次遍历`KV`对: ```rust @@ -254,7 +256,7 @@ println!("{:?}", map); 好了,理解完这个,再来设想一点,若一个复杂点的类型作为Key,那怎么在底层对它进行存储,怎么使用它进行查询和比较? 是不是很棘手?好在我们有哈希函数:通过它把`Key`计算后映射为哈希值,然后使用该哈希值来进行存储、查询、比较等操作。 -但是问题又来了,如何保证不同`Key`通过哈希后的两个值不会相同?如果相同,那意味着我们使用不同的·,却查到了同一个结果,这种明显是错误的行为。 +但是问题又来了,如何保证不同`Key`通过哈希后的两个值不会相同?如果相同,那意味着我们使用不同的Key,却查到了同一个结果,这种明显是错误的行为。 此时,就涉及到安全性跟性能的取舍了。 若要追求安全,尽可能减少冲突,同时防止拒绝服务(Denial of Service, DoS)攻击,就要使用密码学安全的哈希函数,`HashMap`就是使用了这样的哈希函数。反之若要追求性能,就需要使用没有那么安全的算法。 diff --git a/course-book/contents/basic/collections/intro.md b/course-book/contents/basic/collections/intro.md index 4f399699..f18292e7 100644 --- a/course-book/contents/basic/collections/intro.md +++ b/course-book/contents/basic/collections/intro.md @@ -8,7 +8,7 @@ 紧接着,第二个集合在全场的嘘声和羡慕眼光中闪亮登场,只见里面的元素排成一对一对的,彼此都手牵着手,非对方莫属,这种情深深雨蒙蒙的样子真是...挺欠扁的。 它就是`HashMap`类型,该类型允许你在里面存储`KV`对,每一个`K`都有唯一的`V`与之配对。 -最后,请用热恋的掌声迎接我们的`String`集合,哦,抱歉,`String`集合天生低调,见不得前两个那样,因此被气走了,你可以去[这里](../compound-type/string-slice)找它. +最后,请用热烈的掌声迎接我们的`String`集合,哦,抱歉,`String`集合天生低调,见不得前两个那样,因此被气走了,你可以去[这里](../compound-type/string-slice)找它. 言归正传,本章所讲的`Vector`、`HashMap`再加上之前的`String`类型,是标准库中最最常用的集合类型,可以说,几乎任何一段代码中都可以找到它们的身影,那么先来看看`Vector`. \ No newline at end of file diff --git a/course-book/contents/basic/collections/vector.md b/course-book/contents/basic/collections/vector.md index ced2444b..330d63d8 100644 --- a/course-book/contents/basic/collections/vector.md +++ b/course-book/contents/basic/collections/vector.md @@ -21,7 +21,9 @@ let mut v = Vec::new(); v.push(1); ``` -此时,`v`就无需手动声明类型,因为编译器通过`v.push(1)`,推测出`v`中的元素类型是`i32`,因此推导出`v`的类型是`Vec`. +此时,`v`就无需手动声明类型,因为编译器通过`v.push(1)`,推测出`v`中的元素类型是`i32`,因此推导出`v`的类型是`Vec`. + +> 如果预先知道要存储的元素个数,可以使用`Vec::with_capacity(capacity)`创建动态数组,这样可以避免因为插入大量新数据导致频繁的内存分配和拷贝,提升性能 #### vec![] 还可以使用宏`vec!`来创建数组,与`Vec::new`有所不同,前者能在创建同时给予初始化值: @@ -38,7 +40,7 @@ let mut v = Vec::new(); v.push(1); ``` -与其它类型一样,必须将`v`声明为`mut`后,才能进行修改,. +与其它类型一样,必须将`v`声明为`mut`后,才能进行修改。 ## Vector与其元素共存亡 @@ -192,7 +194,7 @@ impl IpAddr for V6 { fn main() { let v: Vec> = vec![ Box::new(V4("127.0.0.1".to_string())), - Box::new(V6("127.0.0.1".to_string())), + Box::new(V6("::1".to_string())), ]; for ip in v { diff --git a/course-book/contents/basic/converse.md b/course-book/contents/basic/converse.md index 1e020997..925532c8 100644 --- a/course-book/contents/basic/converse.md +++ b/course-book/contents/basic/converse.md @@ -159,7 +159,7 @@ error[E0277]: the trait bound `&mut i32: Trait` is not satisfied #### 点操作符 方法调用的点操作符看起来简单,实际上非常不简单,它在调用时,会发生很多魔法般的类型转换,例如:自动引用、自动解引用,强制类型转换直到类型能匹配等。 -假设有一个方法`foo`,它有一个接收器(接收器就是`self`、`&sef`、`&mut self`参数)。如果调用`value.foo()`,编译器在调用`foo`之前,需要决定到底使用哪个`Self`类型来调用。现在假设`value`拥有类型`T`. +假设有一个方法`foo`,它有一个接收器(接收器就是`self`、`&self`、`&mut self`参数)。如果调用`value.foo()`,编译器在调用`foo`之前,需要决定到底使用哪个`Self`类型来调用。现在假设`value`拥有类型`T`. 再进一步,我们使用[完全限定语法](https://course.rs/basic/trait/advance-trait.html#完全限定语法)来进行准确的函数调用: 1. 首先,编译器检查它是否可以直接调用`T::foo(value)`, 称之为**值方法调用** @@ -267,6 +267,6 @@ impl Clone for Container { 你以为你之前凝视的是深渊吗?不,你凝视的只是深渊的大门。`mem::transmute_copy`才是真正的深渊,它比之前的还要更加危险和不安全。它从`T`类型中拷贝出`U`类型所需的字节数,然后转换成`U`。`mem::transmute`尚有大小检查,能保证两个数据的内存大小一致,现在这哥们干脆连这个也丢了,只不过`U`的尺寸若是比`T`大,会是一个未定义行为。 -当然,你也可以通过原生指针转换获得`unions`(todo!)获得所有的这些功能,但是你将无法获得任何编译提示或者检查。原生指针转换和`unions`也不是魔法,无法逃避上面说的规则。 +当然,你也可以通过原生指针转换和`unions`(todo!)获得所有的这些功能,但是你将无法获得任何编译提示或者检查。原生指针转换和`unions`也不是魔法,无法逃避上面说的规则。 diff --git a/course-book/contents/basic/result-error/intro.md b/course-book/contents/basic/result-error/intro.md index ce376bc3..e86b8fa8 100644 --- a/course-book/contents/basic/result-error/intro.md +++ b/course-book/contents/basic/result-error/intro.md @@ -4,7 +4,7 @@ 社会演变至今,这种思想依然没变,甚至来到计算中的微观世界,也是如此。及时、准确的获知系统在发生什么,是程序设计的重中之重。因此能够准确的分辨函数返回值是正确的还是错误的、以及在发生错误时该怎么快速处理,成了程序设计语言的必备功能。 -Go语言为人诟病的其中一点就是`if err != nil {}`的大量使用,缺乏一些程序设计的美感,不过我倒是觉得这种简单的方式也有其好处,就是阅读代码时的流畅感很强,你不需要过多的思考各种语法是什么意思。与Go语言不同,Rust博采众家之长,整出了颇具自身色彩的返回值和错误处理体系,本章我们就从高屋建瓴的角度来学习,更加深入的讲解见[此章](../errors/intro.md). +Go语言为人诟病的其中一点就是`if err != nil {}`的大量使用,缺乏一些程序设计的美感,不过我倒是觉得这种简单的方式也有其好处,就是阅读代码时的流畅感很强,你不需要过多的思考各种语法是什么意思。与Go语言不同,Rust博采众家之长,整出了颇具自身色彩的返回值和错误处理体系,本章我们就从高屋建瓴的角度来学习,更加深入的讲解见[此章](../../errors/intro.md). ## Rust的错误哲学 错误对于软件来说是不可避免的,因此一门优秀的编程语言必须有其完整的错误处理哲学。在很多情况下,Rust需要你承认自己的代码可能会出错,并提前采取行动,来处理这些错误。 @@ -13,4 +13,4 @@ Rust中的错误主要分为两类: - **可恢复错误**, 通常用于从系统全局角度来看可以接受的错误,例如处理用户的访问、操作等错误,这些错误只会影响某个用户自身的操作进程,而不会对系统的全局稳定性产生影响 - **不可恢复错误**,刚好相反,该错误通常是全局性或者系统性的错误,例如数组越界访问,系统启动时发生了影响启动流程的错误等等,这些错误的影响往往对于系统来说是致命的 -很多编程语言,并不会区分这些错误,而是直接采用异常的方式去处理。Rust没有异常,但是Rust也有自己的卧龙凤雏:`Result`用于可恢复错误,`panic!`用于不可恢复错误。 \ No newline at end of file +很多编程语言,并不会区分这些错误,而是直接采用异常的方式去处理。Rust没有异常,但是Rust也有自己的卧龙凤雏:`Result`用于可恢复错误,`panic!`用于不可恢复错误。 \ No newline at end of file diff --git a/course-book/contents/basic/result-error/panic.md b/course-book/contents/basic/result-error/panic.md index 4b6acca6..0fa076b1 100644 --- a/course-book/contents/basic/result-error/panic.md +++ b/course-book/contents/basic/result-error/panic.md @@ -115,7 +115,7 @@ let home: IpAddr = "127.0.0.1".parse().unwrap(); 因此`unwrap`简而言之:成功则返回值,失败则`panic`, 总之不进行任何错误处理。 #### 示例、原型、测试 -这些场景,需要快速的搭建代码,错误处理反而会拖慢实现速度,也不是特别有必要,因此通过`unrap`、`expect`等方法来处理是最快的。 +这些场景,需要快速的搭建代码,错误处理反而会拖慢实现速度,也不是特别有必要,因此通过`unwrap`、`expect`等方法来处理是最快的。 同时,当我们准备做错误处理时,全局搜索这些方法,也可以不遗漏的进行替换。 @@ -138,7 +138,7 @@ let home: IpAddr = "127.0.0.1".parse().unwrap(); 当错误预期会出现时,返回一个错误较为合适,例如解析器接收到格式错误的数据,HTTP请求接收到错误的参数甚至该请求内的任何错误(不会导致整个程序有问题,只影响该此请求)。 **因为错误是可预期的,因此也是可以处理的**。 -当启动时某个流程发生了错误,导致了后续代码的允许造成影响,那么就应该使用`panic`,而不是处理错误后,继续运行,当然你可以通过重试的方式来继续。 +当启动时某个流程发生了错误,导致了后续代码的运行造成影响,那么就应该使用`panic`,而不是处理错误后,继续运行,当然你可以通过重试的方式来继续。 上面提到过,数组访问越界,就要`panic`的原因,这个就是属于内存安全的范畴,一旦内存访问不安全,那么我们无法保证自己的程序会怎么运行下去,也无法保证逻辑和数据的正确性。 diff --git a/course-book/contents/basic/result-error/result.md b/course-book/contents/basic/result-error/result.md index acb6197c..98e4a894 100644 --- a/course-book/contents/basic/result-error/result.md +++ b/course-book/contents/basic/result-error/result.md @@ -24,7 +24,7 @@ fn main() { > > 有几种常用的方式: > - 第一种是查询标准库或者三方库文档,搜索`File`,然后找到它的`open`方法,但是此处更推荐第二种方法: -> - 在[Rust IDE](../../first-try/editor.md)章节,我们推荐了`VSCode` IED和`rust-analyze`插件,如果你成功安装的话,那么就可以在`VScode`中很方便的通过代码跳转的方式查看代码,同时`rust-analyze`插件还会对代码中的类型进行标注,非常方便好用! +> - 在[Rust IDE](../../first-try/editor.md)章节,我们推荐了`VSCode` IED和`rust-analyzer`插件,如果你成功安装的话,那么就可以在`VScode`中很方便的通过代码跳转的方式查看代码,同时`rust-analyzer`插件还会对代码中的类型进行标注,非常方便好用! > - 你还可以尝试故意标记一个错误的类型,然后让编译器告诉你: ```rust let f: u32 = File::open("hello.txt"); @@ -314,7 +314,7 @@ fn main() -> Result<(), Box> { 这样就能使用`?`提前返回了,同时我们又一次看到了`Box`特征对象,因为`std::error:Error`是Rust中抽象层次最高的错误,其它标准库中的错误都实现了该特征,因此我们可以用该特征对象代表一切错误,就算`main`函数中调用任何标准库函数发生错误,都可以通过`Box`这个特征对象进行返回. -至于`main`函数可以有多种返回值,那是因为实现了[std::process::Termination]特征,目前为止该特征还没进入稳定版Rust中,也许未来你可以为自己的类型实现该特征! +至于`main`函数可以有多种返回值,那是因为实现了[std::process::Termination](https://doc.rust-lang.org/std/process/trait.Termination.html)特征,目前为止该特征还没进入稳定版Rust中,也许未来你可以为自己的类型实现该特征! 至此,Rust的基础内容学习已经全部完成,下面我们将学习Rust的高级进阶内容,正式开启你的高手之路。