- Rust編程:入門、實戰與進階
- 朱春雷
- 3028字
- 2021-04-30 12:37:23
2.5 字符串
字符串的本質是一種特殊的容器類型,是由零個或多個字符組成的有限序列。字符串是程序開發中最常用的數據結構。不同于其他容器類型較多關注于容器中的元素,字符串常被作為一個整體來關注和使用。下面介紹字符串最常用的創建、修改、訪問等操作。不過,由于字符串的處理涉及迭代器、引用、解引用、所有權和生命周期等概念,對這些概念不了解的讀者可先閱讀相關內容后再回到本節學習。
2.5.1 字符串的創建
Rust常用的字符串有兩種,一種是固定長度的字符串字面量str,另一種是可變長度的字符串對象String。
1. &str的創建
Rust內置的字符串類型是str,它通常以引用的形式&str出現。字符串字面量&str是字符的集合,代表的是不可變的UTF-8編碼的字符串的引用,創建后無法再為其追加內容或更改內容。
創建字符串字面量&str有以下兩種方式。
1)使用雙引號創建字符串字面量,代碼如下:
let s1 = "Hello, Rust!";
2)使用as_str方法將字符串對象轉換為字符串字面量,代碼如下:
1 let str = String::from("Hello, Rust!"); 2 let s2 = str.as_str();
2. String的創建
字符串對象String是由Rust標準庫提供的、擁有所有權的UTF-8編碼的字符串類型,創建后可以為其追加內容或更改內容。String類型的本質是一個字段為Vec<u8>類型的結構體,它把字符內容存放在堆上,由指向堆上字節序列的指針(as_ptr方法)、記錄堆上字節序列的長度(len方法)和堆分配的容量(capacity方法)3部分組成。
創建字符串對象String有以下3種方式。
1)使用String::new函數創建空的字符串對象,代碼如下:
let mut s = String::new();
2)使用String::from函數根據指定的字符串字面量創建字符串對象,代碼如下
let s = String::from("Hello, Rust!");
3)使用to_string方法將字符串字面值轉換為字符串對象,代碼如下:
1 let str = "Hello, Rust!"; 2 let s = str.to_string();
2.5.2 字符串的修改
String類型字符串常見的修改操作有追加、插入、連接、替換和刪除等。
1)使用push方法在字符串后追加字符,使用push_str方法在字符串后追加字符串字面量。這兩個方法都是在原字符串上追加,并不會返回新的字符串。
代碼清單2-29的第2行代碼中變量s的聲明使用了mut關鍵字,是因為要在字符串后追加字符,該字符串必須是可變的。第3行代碼中push方法將字符R追加到s的尾部,s變為“Hello, R”。第4行代碼中push_str方法將字符串字面量“ust!”追加到s尾部,s變為“Hello, Rust!”。
代碼清單2-29 追加字符串
1 fn main() { 2 let mut s = String::from("Hello, "); 3 s.push('R'); 4 s.push_str("ust!"); 5 6 println!("{}", s); 7 } 8 9 // Hello, Rust!
2)使用insert方法在字符串中插入字符,使用insert_str方法在字符串中插入字符串字面量。這兩個方法都接收兩個參數,第1個參數是插入位置的索引,第2個參數是插入字符或字符串字面量。同樣地,這兩個方法都是在原字符串上插入,并不會返回新的字符串。
代碼清單2-30中,第3行代碼的insert方法在索引為5的位置插入字符“,”,s變為“Hello, World!”。第4行代碼的insert_str方法在索引為7的位置插入字符串字面量“Rust”,s變為“Hello, Rust World!”。需要注意的是,insert和insert_str方法是基于字節序列的索引進行操作的,其內部會通過is_char_boundary方法判斷插入位置的索引是否在合法邊界內,如果索引非法將會導致程序錯誤。
代碼清單2-30 插入字符串
1 fn main() { 2 let mut s = String::from("Hello World!"); 3 s.insert(5, ','); 4 s.insert_str(7, "Rust "); 5 6 println!("{}", s); 7 } 8 9 // Hello, Rust World!
3)使用“+”或“+=”運算符將兩個字符串連接成一個新的字符串,要求運算符的右邊必須是字符串字面量,但不能對兩個String類型字符串使用“+”或“+=”運算符。連接與追加的區別在于,連接會返回新的字符串,而不是在原字符串上的追加。
代碼清單2-31中,第6行代碼中“+”運算符對4個字符串進行連接,由于s2、s3是String類型,不能出現在“+”運算符的右邊,因此需要將s2、s3轉換為字符串字面量。&s2為&String類型,但String類型實現了Deref trait,執行連接操作時會自動解引用為&str類型。s3使用as_str方法將String類型轉換為&str類型。s4和第7行代碼中的字符“!”已是&str類型,可以直接使用“+”或“+=”運算符連接。
代碼清單2-31 使用“+”或“+=”運算符連接字符串
1 fn main() { 2 let s1 = String::from("Hello"); 3 let s2 = String::from(", "); 4 let s3 = String::from("Rust "); 5 let s4 = "World"; 6 let mut s = s1 + &s2 + s3.as_str() + s4; 7 s += "!"; 8 9 println!("{}", s); 10 } 11 12 // Hello, Rust World!
對于較為復雜或帶有格式的字符串連接,我們可以使用格式化宏format!,它對于String類型和&str類型的字符串都適用,如代碼清單2-32所示。
代碼清單2-32 使用format!連接字符串
1 fn main() { 2 let s1 = String::from("Hello"); 3 let s2 = String::from("Rust"); 4 let s3 = "World"; 5 let s = format!("{}-{}-{}", s1, s2, s3); 6 7 println!("{}", s); 8 } 9 10 // Hello-Rust-World
4)使用replace和replacen方法將字符串中指定的子串替換為另一個字符串。replace方法接收兩個參數,第1個參數是要被替換的字符串子串,第2個參數是新字符串,它會搜索和替換所有匹配到的要被替換的子串。replacen方法除replace方法接收的兩個參數外,還接收第3個參數來指定替換的個數。
代碼清單2-33中,第3行代碼的replace方法將匹配到的兩個子串都進行替換,第4行代碼的replacen方法指定只替換一個匹配到的子串。
代碼清單2-33 替換字符串
1 fn main() { 2 let s = String::from("aaabbbbccaadd"); 3 let s1 = s.replace("aa", "77"); 4 let s2 = s.replacen("aa", "77", 1); 5 6 println!("{}", s1); 7 println!("{}", s2); 8 } 9 10 // 77abbbbcc77dd 11 // 77abbbbccaadd
5)使用pop、remove、truncate和clear方法刪除字符串中的字符。
- pop:刪除并返回字符串的最后一個字符,返回值類型是Option<char>。如果字符串為空,則返回None。
- remove:刪除并返回字符串中指定位置的字符,其參數是該字符的起始索引位置。remove方法是按字節處理字符串的,如果給定的索引位置不是合法的字符邊界,將會導致程序錯誤。
- truncate:刪除字符串中從指定位置開始到結尾的全部字符,其參數是起始索引位置。truncate方法也是按字節處理字符串的,如果給定的索引位置不是合法的字符邊界,將會導致程序錯誤。
- clear:等價于將truncate方法的參數指定為0,刪除字符串中所有字符。
代碼清單2-34中,第4行代碼的pop方法刪除字符串變量s中的最后一個字符“d”,返回值是Some('d')。第7行代碼的remove方法刪除s中的字符“虎”,如果把參數改為7就會導致程序錯誤,原因將在2.5.3節解釋。第10行代碼的truncate方法刪除s中的子串“Léopar”,同樣如果把參數改為7就會導致程序錯誤。第13行代碼的clear方法刪除s中的所有字符。
代碼清單2-34 刪除字符串
1 fn main() { 2 let mut s = String::from("L?we 老虎 Léopard"); 3 4 println!("{:?}", s.pop()); 5 println!("{}", s); 6 7 println!("{:?}", s.remove(9)); 8 println!("{}", s); 9 10 s.truncate(9); 11 println!("{}", s); 12 13 s.clear(); 14 println!("{}", s); 15 } 16 17 // Some('d') 18 // L?we 老虎 Léopar 19 // '虎' 20 // L?we 老 Léopar 21 // L?we 老 22 //
2.5.3 字符串的訪問
這里不準備詳細介紹字符編碼的細節,對Unicode字符集、UTF-8編碼感興趣的讀者可以自行搜索相關資料學習。讀者只需要了解以下兩點,就基本能處理常見的字符串操作。
1)字符串是UTF-8編碼的字節序列,不能直接使用索引來訪問字符。
2)字符串操作可以分為按字節處理和按字符處理兩種方式,按字節處理使用bytes方法返回按字節迭代的迭代器,按字符處理使用chars方法返回按字符迭代的迭代器。
代碼清單2-35中,使用len方法獲取以字節為單位的字符串長度,即字符串中所有字符的總字節數,而不是直觀上看到的字符長度。字符串“L?we 老虎”的長度是12,其中字母“L”的長度是1,特殊字符“?”的長度是2,中文“老”的長度是3。由此可知,不同字符的長度是不一樣的,如果給定的索引位置不是合法的字符邊界就會導致程序錯誤。
代碼清單2-35 使用len方法獲取字符串長度
1 fn main() { 2 let s = String::from("L?we 老虎"); 3 println!("L?we 老虎: {}", s.len()); 4 5 let s = String::from("L"); 6 println!("L: {}", s.len()); 7 8 let s = String::from("?"); 9 println!("?: {}", s.len()); 10 11 let s = String::from("老"); 12 println!("老: {}", s.len()); 13 } 14 15 // L?we 老虎: 12 16 // L: 1 17 // ?: 2 18 // 老: 3
代碼清單2-36中,第3行代碼的bytes方法返回Bytes迭代器,第4~6行代碼的for循環對Bytes迭代器進行迭代處理,可以看到它是按字節進行迭代的。第9行代碼的chars方法返回Chars迭代器,第10~12行代碼的for循環對Chars迭代器進行迭代處理,可以看到它是按字符進行迭代的。
代碼清單2-36 通過迭代器訪問字符串的字符
1 fn main() { 2 let s = String::from("L?we 老虎"); 3 let bytes = s.bytes(); 4 for b in bytes { 5 print!("{} | ", b); 6 } 7 println!(); 8 9 let chars = s.chars(); 10 for c in chars { 11 print!("{} | ", c); 12 } 13 } 14 15 // 76 | 195 | 182 | 119 | 101 | 32 | 232 | 128 | 129 | 232 | 153 | 142 | 16 // L | ? | w | e | | 老 | 虎 |
- DevOps:軟件架構師行動指南
- 玩轉Scratch少兒趣味編程
- Microsoft Application Virtualization Cookbook
- Rake Task Management Essentials
- Django開發從入門到實踐
- Rust Cookbook
- 網絡爬蟲原理與實踐:基于C#語言
- Clojure Reactive Programming
- Java面向對象程序設計
- Oracle GoldenGate 12c Implementer's Guide
- 詳解MATLAB圖形繪制技術
- 機器學習微積分一本通(Python版)
- Oracle數據庫編程經典300例
- Xamarin Blueprints
- Android系統下Java編程詳解