官术网_书友最值得收藏!

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 |   | 老 | 虎 | 
主站蜘蛛池模板: 福建省| 红桥区| 西和县| 宣威市| 沿河| 华亭县| 南开区| 那曲县| 彩票| 察哈| 巴南区| 高安市| 石城县| 藁城市| 两当县| 金华市| 屯门区| 思南县| 临清市| 兰坪| 淮阳县| 高平市| 商河县| 西盟| 平谷区| 淮北市| 井研县| 汉源县| 公主岭市| 甘德县| 江阴市| 三门峡市| 沙雅县| 区。| 阿图什市| 曲沃县| 富裕县| 张北县| 西乌珠穆沁旗| 龙州县| 芒康县|