RUST 學(xué)習(xí)日記 第13課 ——字符串(一)

0x00 回顧與開篇
上節(jié)課講解了切片(Slice)、數(shù)組(Array)、向量(Vector)的區(qū)別??吹胶枚嗤瑢W(xué)給我反饋,說(shuō)可能有點(diǎn)兒晦澀難懂。那我在這里給你們吃一顆定心丸,你們可以先只了解有切片這個(gè)定義就可以了。這節(jié)課繼續(xù)講解一種重要數(shù)據(jù)類型——字符串(String)??梢赃@么說(shuō),在所有的程序中,大約有80%的代碼都是與字符串有關(guān)系,所以它很重要。
也許,了解Rust的字符串,你只看這篇文章就夠了。
0x01 字符串的定義
在Rust中有兩種字符串類型:
-
&str:原始的字符串類型str(發(fā)音:/st??r/),常被叫做字符串切片,它通常以不可變借用的形式出現(xiàn),即&str。它是一種固定長(zhǎng)度的字符串,不能隨意更改其長(zhǎng)度。咱們經(jīng)常寫的字符串字面量就是&str類型。 -
String:這種字符串類型是一種可變長(zhǎng)度的字符串,可以隨意更改它的長(zhǎng)度。String其實(shí)就是一個(gè)結(jié)構(gòu)體,它里面封裝了向量類型。這也很好理解它為什么是可變的了吧。下面是String在源碼中的定義:

0x02 字符串字面量(&str)
在第7課已經(jīng)提到過(guò)字面量了,這里單獨(dú)講下字符串字面量。字符串字面量它就是&str類型。
示例代碼如下:
// 未聲明類型
let hello1 = "hello study rust!";
// 聲明類型
let hello2: &str = "hello study rust!";
println!("hello1 = {}", hello1);
println!("hello2 = {}", hello2);
轉(zhuǎn)義
在字符串字面量中,英文雙引號(hào)需要通過(guò)\轉(zhuǎn)義。
示例代碼如下:
let hello3 = "I bought a book called \"Rust\"";
println!("hello3 = {}", hello3);
代碼運(yùn)行結(jié)果:
I bought a book called "Rust"
換行符
如果定義的字符串比較長(zhǎng),通常在代碼中習(xí)慣性的加上換行符,那么也會(huì)將換行符輸出。但是通常,咱們換行只是為了代碼美觀,這時(shí)如果在需要換行的地方以\結(jié)尾,則會(huì)忽略當(dāng)前行的換行符和下一行的開頭空白符。
示例代碼如下:
// 換行
let hello4 = "My name is Betty, 18 years old. I like play piano very much and was awarded
of a numbers of prizes for that.";
println!("hello4 = {}", hello4);
// 忽略換行符
let hello5 = "My name is Betty, 18 years old. I like play piano very much and was awarded \
of a numbers of prizes for that.";
println!("hello5 = {}", hello5);
代碼運(yùn)行結(jié)果:
hello4 = My name is Betty, 18 years old. I like play piano very much and was awarded
of a numbers of prizes for that.
hello5 = My name is Betty, 18 years old. I like play piano very much and was awarded of a numbers of prizes for that.
原始字符串(Raw String)
有的時(shí)候是不是感覺(jué)轉(zhuǎn)義其實(shí)是一個(gè)很煩的事情,尤其是在輸入windows文件路徑的時(shí)候。別著急,Rust提供了一種Raw String類型,字面翻譯是未經(jīng)處理的字符串,原始字符串。
Raw String定義:Raw String以小寫字面r為標(biāo)記,其中的所有反斜杠和空白符都會(huì)原樣的包含在字符串中,轉(zhuǎn)義符在其中無(wú)效。如果原始字符串中包含英文引號(hào),則需要在字符串的開頭和結(jié)尾添加#號(hào)標(biāo)記。(#號(hào)數(shù)量可自己定義,但是開頭和結(jié)尾的數(shù)量一定要相等。)
示例代碼如下:
// 測(cè)試轉(zhuǎn)義
let raw_str = r"D:\study_rust\013\string";
println!("raw_str = {}", raw_str);
// 測(cè)試引號(hào)
let raw_str_ref = r##"測(cè)試引號(hào)"英文引號(hào)",英文引號(hào)會(huì)原樣輸出!!"##;
println!("raw_str_ref = {}", raw_str_ref);
代碼運(yùn)行結(jié)果:
raw_str = D:\study_rust\013\string
raw_str_ref = 測(cè)試引號(hào)"英文引號(hào)",會(huì)原樣輸出!!
字節(jié)字符串 (Byte String)
字節(jié)字符串就是前綴帶有b的字符串字面量,類似于在第7課中字節(jié)字符。字節(jié)字符串的是u8值(字節(jié))的切片,只能幫韓ASCII字符和\xHH轉(zhuǎn)義序列,其不能包含任何Unicode字符。
PS:它不支持在后面將要介紹的所有字符串的方法,其最像字符串的地方就是它的語(yǔ)法了。但是它支持上面介紹的跨行,轉(zhuǎn)義,原始字節(jié)字符串。其中,原始字節(jié)字符串以br開頭。
示例代碼如下:
// 字節(jié)字符串
let byte_str = b"a byte string!";
println!("byte_str = {:#?}", byte_str);
// 原始字節(jié)字符串
let raw_byte_str = br#"it is a "raw byte string"."#;
println!("raw_str_ref = {:#?}", raw_byte_str);
代碼運(yùn)行結(jié)果:
byte_str = [97, 32, 98, 121, 116, 101, 32, 115, 116, 114, 105, 110, 103, 33]
raw_str_ref = [105, 116, 32, 105, 115, 32, 97, 32, 34, 114, 97, 119, 32, 98, 121, 116, 101, 32, 115, 116, 114, 105, 110, 103, 34, 46]
看到上面的運(yùn)行結(jié)果了嗎?沒(méi)錯(cuò)不要驚訝,其實(shí)它實(shí)際上就是&[u8;N]——哈哈,這不就是上節(jié)課介紹的切片引用嗎。byte_str就是包含14個(gè)字節(jié)的數(shù)組的切片引用。
0x03 可變長(zhǎng)度字符串(String)
前面提到過(guò),String類似于Vec<T>,其本質(zhì)就是一個(gè)字段為Vec<u8>類型的結(jié)構(gòu)體。每個(gè)String都有在堆上分配的緩沖區(qū),不跟其它的String共享。當(dāng)String變量超出作用域后其緩沖區(qū)會(huì)自動(dòng)釋放,除非String的所有權(quán)發(fā)生轉(zhuǎn)移(有關(guān)所有權(quán)的知識(shí)點(diǎn)在后續(xù)章節(jié)介紹)。當(dāng)然String它在棧上也是由3部分組成,分別是指向堆上的字節(jié)序列的指針、記錄堆上字節(jié)序列的長(zhǎng)度、在堆上分配的容量。
創(chuàng)建字符串
創(chuàng)建字符串的常見的方式有三種:
1、使用String::new創(chuàng)建空的字符串。
let empty_string = String::new();
2、使用String::from通過(guò)字符串字面量創(chuàng)建字符串。實(shí)際上復(fù)制了一個(gè)新的字符串。
let rust_str = "rust";
let rust_string = String::from(rust_str);
- 為什么是說(shuō)它是復(fù)制了一個(gè)新的字符串呢?
as_ptr()方法可以打印rust_str和rust_string指向堆的內(nèi)存地址。
示例代碼如下:
println!("rust_str 字面量指向的地址 {:?}", rust_str.as_ptr());
println!("rust_string 指向的地址 {:?}", &rust_string.as_ptr());
代碼運(yùn)行結(jié)果(注:內(nèi)存地址每次運(yùn)行可能都不一致):
rust_str 字面量指向的地址 0x7ff61cac08a8
rust_string 指向的地址 0x164c4c7aa00
結(jié)果就是不一樣,他們指向堆的地址是兩個(gè)不同的地址。也就是說(shuō),在堆上有兩個(gè)地方保存了rust這個(gè)字符串。下節(jié)課會(huì)更詳細(xì)的講解String在內(nèi)存的表現(xiàn)形式。
3、使用字符串字面量的to_string將字符串字面量轉(zhuǎn)換為字符串。實(shí)際上復(fù)制了一個(gè)新的字符串。
let s1 = "rust_to_string";
let s2 = s1.to_string();
to_string()實(shí)際上是封裝了String::from(),如下圖源碼:

這也間接解釋了to_string()為什么也是在堆上復(fù)制了一個(gè)新的字符串了。
PS:to_string()最早支持的版本是1.9.0。
0x06 小結(jié)
如果你對(duì)C++比較熟悉,那么你肯定知道在C++中存在兩種字符串類型,那么在Rust中也有類似的兩種字符串類型,本節(jié)簡(jiǎn)單介紹了String和&str的這兩種類型的概念。簡(jiǎn)單介紹了String的三種創(chuàng)建方式和String字符串在內(nèi)存的形式如同向量一樣。是不是感覺(jué)還不是很懂,還是有點(diǎn)兒“懵”。它們到底是怎么保存的呢?String既然封裝了向量類型,那么它跟向量類型又存在什么區(qū)別呢?下節(jié)課詳細(xì)講解字符串在內(nèi)存中的表現(xiàn)形式。帶你徹底搞懂String和&str。
0x7 本節(jié)源碼
013 · StudyRust - 碼云 - 開源中國(guó) (gitee.com)
下節(jié)預(yù)告——字符串(二)。