From 3a7c94309d1b8ef493029e2c8be3f64c3427e041 Mon Sep 17 00:00:00 2001 From: tomoat Date: Fri, 18 Feb 2022 15:56:40 +0800 Subject: [PATCH 1/2] Update string-slice.md --- contents/basic/compound-type/string-slice.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/contents/basic/compound-type/string-slice.md b/contents/basic/compound-type/string-slice.md index ed350dc7..60b25976 100644 --- a/contents/basic/compound-type/string-slice.md +++ b/contents/basic/compound-type/string-slice.md @@ -1,6 +1,6 @@ # 字符串 -在其他语言,字符串往往是送分题,因为实在是太简单了,例如 `"hello, world"` 就是字符串章节的几乎全部内容了,对吧?如果你带着这样的想法来学 Rust,我保证,绝对会栽跟头,**因此这一章大家一定要重视,仔细阅读,这里有很多其它 Rust 书籍中没有的内容**。 +在其他语言,字符串往往是送分题,因为实在是太简单了,例如 `"hello, world"` 就是字符串章节的几乎全部内容了,但是如果你带着同样的想法来学 Rust,我保证,绝对会栽跟头,**因此这一章大家一定要重视,仔细阅读,这里有很多其它 Rust 书籍中没有的内容**。 首先来看段很简单的代码: ```rust @@ -83,7 +83,7 @@ let slice = &s[..]; >在对字符串使用切片语法时需要格外小心,切片的索引必须落在字符之间的边界位置,也就是UTF8字符的边界,例如中文在UTF8中占用三个字节,下面的代码就会崩溃: >```rust -> let s = "中国人"; +> let s = String::from("中国人"); > let a = &s[0..2]; > println!("{}",a); >``` @@ -103,6 +103,9 @@ fn main() { println!("the first word is: {}", word); } +fn first_word(s: &String) -> &str { + &s[..1] +} ``` 编译器报错如下: ```console @@ -152,7 +155,7 @@ let s: &str = "Hello, world!"; ## 什么是字符串? -顾名思义,字符串是由字符组成的连续集合,但是在上一节中我们提到过,**Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF8 编码,也就是字符所占的字节数是变化的(1 - 4)**,这样有助于大幅降低字符串所占用的内存空间。 +顾名思义,字符串是由字符组成的连续集合,但是在上一节中我们提到过,**Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4)**,这样有助于大幅降低字符串所占用的内存空间。 Rust 在语言级别,只有一种字符串类型: `str`,它通常是以引用类型出现 `&str`,也就是上文提到的字符串切片。虽然语言级别只有上述的 `str` 类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 `String` 类型。 @@ -282,7 +285,7 @@ let hello = String::from("中国人"); 所以,可以看出来 Rust 提供了不同的字符串展现方式,这样程序可以挑选自己想要的方式去使用,而无需去管字符串从人类语言角度看长什么样。 -还有一个原因导致了 Rust 不允许去索引字符:因为索引操作,我们总是期望它的性能表现是 O(1),然而对于 `String` 类型来说,无法保证这一点,因为 Rust 可能需要从 0 开始去遍历字符串来定位合法的字符。 +还有一个原因导致了 Rust 不允许去索引字符串:因为索引操作,我们总是期望它的性能表现是 O(1),然而对于 `String` 类型来说,无法保证这一点,因为 Rust 可能需要从 0 开始去遍历字符串来定位合法的字符。 ## 字符串切片 前文提到过,字符串切片是非常危险的操作,因为切片的索引是通过字节来进行,但是字符串又是 UTF8 编码,因此你无法保证索引的字节刚好落在字符的边界上,例如: @@ -349,13 +352,13 @@ for b in "中国人".bytes() { ## 字符串深度剖析 那么问题来了,为啥 `String` 可变,而字符串字面值 `str` 却不可以? -就字符串字面值来说,我们在编译时就知道其内容,最终字面值文本被直接硬编码进可执行文件中,这使得字符串字面值快速且高效,这主要得益于字符串的不可变性。不幸的是,我们不能为了获得这种性能,而把每一个在编译时大小未知的文本都放进内存中(你也做不到!),因为有的字符串是在程序运行得过程中动态生成的。 +就字符串字面值来说,我们在编译时就知道其内容,最终字面值文本被直接硬编码进可执行文件中,这使得字符串字面值快速且高效,这主要得益于字符串字面值的不可变性。不幸的是,我们不能为了获得这种性能,而把每一个在编译时大小未知的文本都放进内存中(你也做不到!),因为有的字符串是在程序运行得过程中动态生成的。 对于 `String` 类型,为了支持一个可变、可增长的文本片段,需要在堆上分配一块在编译时未知大小的内存来存放内容,这些都是在程序运行时完成的: - 首先向操作系统请求内存来存放 `String` 对象 - 在使用完成后,将内存释放,归还给操作系统 -其中第一个由 `String::from` 完成,它创建了一个全新的 `String`。 +其中第一部分由 `String::from` 完成,它创建了一个全新的 `String`。 重点来了,到了第二部分,就是百家齐放的环节,在有**垃圾回收 GC** 的语言中,GC 来负责标记并清除这些不再使用的内存对象,这个过程都是自动完成,无需开发者关心,非常简单好用;但是在无 GC 的语言中,需要开发者手动去释放这些内存对象,就像创建对象需要通过编写代码来完成一样,未能正确释放对象造成的后果简直不可估量。 From 9f366fc5e3c0121af1534fd927e3f63bdfc9987b Mon Sep 17 00:00:00 2001 From: tomoat Date: Fri, 18 Feb 2022 16:01:16 +0800 Subject: [PATCH 2/2] Update string-slice.md --- contents/basic/compound-type/string-slice.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contents/basic/compound-type/string-slice.md b/contents/basic/compound-type/string-slice.md index 60b25976..698feaf6 100644 --- a/contents/basic/compound-type/string-slice.md +++ b/contents/basic/compound-type/string-slice.md @@ -83,7 +83,7 @@ let slice = &s[..]; >在对字符串使用切片语法时需要格外小心,切片的索引必须落在字符之间的边界位置,也就是UTF8字符的边界,例如中文在UTF8中占用三个字节,下面的代码就会崩溃: >```rust -> let s = String::from("中国人"); +> let s = "中国人"; > let a = &s[0..2]; > println!("{}",a); >```