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

2.4 容器類型

Rust標(biāo)準(zhǔn)庫std::collections提供了4種通用的容器類型,包含以下8種數(shù)據(jù)結(jié)構(gòu),如表2-2所示。

表2-2 Rust容器類型

028-01

下面對Rust編程中最常使用、后續(xù)實(shí)戰(zhàn)中會反復(fù)用到的Vec、VecDeque和HashMap進(jìn)行介紹。

2.4.1 Vec

Vec是一種動態(tài)可變長數(shù)組(簡稱動態(tài)數(shù)組),即在運(yùn)行時(shí)可增長或者縮短數(shù)組的長度。動態(tài)數(shù)組在內(nèi)存中開辟了一段連續(xù)內(nèi)存塊用于存儲元素,且只能存儲相同類型的元素。新加入的元素每次都會被添加到動態(tài)數(shù)組的尾部。動態(tài)數(shù)組根據(jù)添加順序?qū)?shù)據(jù)存儲為元素序列。序列中每個(gè)元素都分配有唯一的索引,元素的索引從0開始計(jì)數(shù)。

1. 動態(tài)數(shù)組的創(chuàng)建

創(chuàng)建動態(tài)數(shù)組有以下3種方式。

1)使用Vec::new函數(shù)創(chuàng)建空的動態(tài)數(shù)組,代碼如下:

let mut v: Vec<i32> = Vec::new();

2)使用Vec::with_capacity函數(shù)創(chuàng)建指定容量的動態(tài)數(shù)組,代碼如下:

let mut v: Vec<i32> = Vec::with_capacity(10);

動態(tài)數(shù)組的容量是指為存儲元素所分配的內(nèi)存空間量,而長度是動態(tài)數(shù)組中實(shí)際存儲的元素個(gè)數(shù)。動態(tài)數(shù)組的長度小于其容量時(shí),動態(tài)數(shù)組可以隨意增長,但動態(tài)數(shù)組的長度超過其容量時(shí)就需要重新分配更大的容量。比如,容量為10的動態(tài)數(shù)組在存儲10個(gè)以內(nèi)的元素時(shí),不會改變?nèi)萘俊.?dāng)動態(tài)數(shù)組的長度增加到11時(shí),就需要重新分配容量。這個(gè)分配過程會消耗一定的系統(tǒng)資源,因此應(yīng)該盡可能根據(jù)初始元素個(gè)數(shù)以及增長情況來指定合理的容量。

3)使用vec!宏在創(chuàng)建動態(tài)數(shù)組的同時(shí)進(jìn)行初始化,并且根據(jù)初始值自動推斷動態(tài)數(shù)組的元素類型。代碼如下所示,第1行代碼創(chuàng)建一個(gè)空的動態(tài)數(shù)組,由于沒有初始值,程序并不知道動態(tài)數(shù)組要存儲什么類型的元素,因此需要聲明元素類型。第2行代碼創(chuàng)建的動態(tài)數(shù)組的初始值是1、2、3,程序會根據(jù)初始值自動推斷元素類型是i32。第3行代碼創(chuàng)建的動態(tài)數(shù)組包含10個(gè)元素,元素的初始值全部是0。

1  let mut v: Vec<i32> = vec![];
2  let mut v = vec![1, 2, 3];
3  let mut v = vec![0; 10];

2. 動態(tài)數(shù)組的修改

修改動態(tài)數(shù)組的常見操作有添加元素、修改指定索引的元素值和刪除元素等。

1)使用push方法在動態(tài)數(shù)組的尾部添加新元素。代碼清單2-10中,第2行代碼中Vec::new函數(shù)創(chuàng)建了一個(gè)存儲i32類型元素的動態(tài)數(shù)組,第3~5行代碼中push方法將值添加到動態(tài)數(shù)組的尾部。

代碼清單2-10 添加元素

 1  fn main() {
 2      let mut v: Vec<i32> = Vec::new();
 3      v.push(1);
 4      v.push(2);
 5      v.push(3);
 6
 7      println!("v: {:?}", v);
 8  }
 9
10  // v: [1, 2, 3]

2)使用“實(shí)例名[索引]”語法為指定索引的元素重新賦值。代碼清單2-11中,第7行代碼將索引為1的元素值改為5,第8行代碼打印當(dāng)前動態(tài)數(shù)組的元素,此時(shí)對應(yīng)索引的元素值已被修改。

代碼清單2-11 修改指定索引的元素值

 1  fn main() {
 2      let mut v: Vec<i32> = Vec::new();
 3      v.push(1);
 4      v.push(2);
 5      v.push(3);
 6
 7      v[1] = 5;
 8      println!("v: {:?}", v);
 9  }
10
11  // v: [1, 5, 3]

3)刪除動態(tài)數(shù)組的元素有以下兩種方式。

① 使用pop方法刪除并返回動態(tài)數(shù)組的最后一個(gè)元素,如果數(shù)組為空則返回None。代碼清單2-12中,第2行代碼中Vec::with_capacity函數(shù)創(chuàng)建了一個(gè)指定容量為10、存儲i32類型元素的動態(tài)數(shù)組。第7行代碼中pop方法刪除最后一個(gè)元素,并返回打印的Option類型的值Some(3)。

代碼清單2-12 使用pop方法刪除元素

 1  fn main() {
 2      let mut v: Vec<i32> = Vec::with_capacity(10);
 3      v.push(1);
 4      v.push(2);
 5      v.push(3);
 6
 7      println!("e: {:?}", v.pop());
 8      println!("v: {:?}", v);
 9  }
10
11  // e: Some(3)
12  // v: [1, 2]

② 使用remove方法刪除并返回動態(tài)數(shù)組指定索引的元素,同時(shí)將其后面的所有元素向左移動一位。如果索引越界,將會導(dǎo)致程序錯(cuò)誤。代碼清單2-13中,第2行代碼vec!宏創(chuàng)建一個(gè)包含初始值為1、2、3的動態(tài)數(shù)組。第4行代碼中remove方法刪除索引為1的元素,并返回打印的i32類型的值2。

代碼清單2-13 使用remove方法刪除元素

1  fn main() {
2      let mut v = vec![1, 2, 3];
3
4      println!("e: {}", v.remove(1));
5      println!("v: {:?}", v);
6  }
7
8  // e: 2
9  // v: [1, 3]

3. 動態(tài)數(shù)組的訪問

訪問動態(tài)數(shù)組的元素有以下兩種方式。

1)使用“實(shí)例名[索引]”語法訪問指定索引的元素。代碼清單2-14中,第3行代碼訪問索引為2的元素,即動態(tài)數(shù)組的第3個(gè)元素。如果取消第4行的注釋,由于索引已越界,將會導(dǎo)致程序錯(cuò)誤。

代碼清單2-14 使用索引語法訪問元素

1  fn main() {
2      let v = vec![1, 2, 3];
3      println!("e: {}", v[2]);
4      // println!("e: {}", v[10]);
5  }
6
7  // e: 3

2)使用get方法以索引作為參數(shù)訪問元素。代碼清單2-15中,第3行代碼訪問索引為2的元素,返回值類型是Option<&i32>。第4行代碼訪問索引為10的元素,由于索引已越界,將會返回None。關(guān)于Option<T>類型的知識點(diǎn)將會在第5章詳細(xì)介紹。

代碼清單2-15 使用get方法訪問元素

1  fn main() {
2      let v = vec![1, 2, 3];
3      println!("e: {:?}", v.get(2));
4      println!("e: {:?}", v.get(10));
5  }
6
7  // e: Some(3)
8  // e: None

除了訪問動態(tài)數(shù)組指定索引的元素,我們還可以通過for循環(huán)遍歷動態(tài)數(shù)組的所有元素。代碼清單2-16中,第3~5行代碼使用for循環(huán)遍歷訪問動態(tài)數(shù)組的所有元素。第8~11行代碼使用for循環(huán)遍歷動態(tài)數(shù)組中每一個(gè)元素的可變引用,并使用解引用操作符“*”來追蹤和修改元素值。關(guān)于for循環(huán)、可變引用以及解引用操作符的知識點(diǎn),我們將會在后續(xù)章節(jié)詳細(xì)介紹。

代碼清單2-16 遍歷所有元素

 1  fn main() {
 2      let v = vec![10, 20, 30];
 3      for i in v {
 4          print!("{} ", i);
 5      }
 6    
 7      let mut v = vec![10, 20, 30];
 8      for i in &mut v {
 9          *i += 50;
10          print!("{} ", i);
11      }
12  }
13
14  // 10 20 30 60 70 80

2.4.2 VecDeque

雙端隊(duì)列是一種同時(shí)具有棧(先進(jìn)后出)和隊(duì)列(先進(jìn)先出)特征的數(shù)據(jù)結(jié)構(gòu),適用于只能在隊(duì)列兩端進(jìn)行添加或刪除元素操作的應(yīng)用場景。Rust使用VecDeque結(jié)構(gòu)體表示雙端隊(duì)列,它定義在標(biāo)準(zhǔn)庫的std::collections模塊中。使用VecDeque結(jié)構(gòu)體之前需要顯式導(dǎo)入std::collections::VecDeque。

1. VecDeque的創(chuàng)建

創(chuàng)建VecDeque有以下兩種方式。

1)使用VecDeque::new函數(shù)創(chuàng)建空的VecDeque,代碼如下:

let mut v: VecDeque<u32> = VecDeque::new();

2)使用VecDeque::with_capacity函數(shù)創(chuàng)建指定容量的VecDeque,代碼如下:

let mut v: VecDeque<u32> = VecDeque::with_capacity(10);

2. VecDeque的修改

修改VecDeque的常見操作有添加元素、修改指定索引的元素值和刪除元素等。

1)使用push_front方法在隊(duì)列的頭部添加新元素,使用push_back方法在隊(duì)列的尾部添加新元素。代碼清單2-17中,第4行代碼中VecDeque::new函數(shù)創(chuàng)建了一個(gè)存儲u32類型元素的VecDeque。第5~7行代碼中push_back方法將值添加到VecDeque的尾部。此時(shí),v中的元素是[1, 2, 3]。第8~9行代碼中push_front方法將值添加到VecDeque的頭部。此時(shí),v中的元素是[2, 1, 1, 2, 3]。

代碼清單2-17 添加元素

 1  use std::collections::VecDeque;
 2
 3  fn main() {
 4      let mut v: VecDeque<u32> = VecDeque::new();
 5      v.push_back(1);
 6      v.push_back(2);
 7      v.push_back(3);
 8      v.push_front(1);
 9      v.push_front(2);
10
11      println!("v: {:?}", v);
12  }
13
14  // v: [2, 1, 1, 2, 3]

2)使用“實(shí)例名[索引]”語法為指定索引的元素重新賦值。代碼清單2-18中,第9行代碼將索引為1的元素值改為5,第10行代碼打印當(dāng)前VecDeque的元素,此時(shí)對應(yīng)索引的元素值已被修改。

代碼清單2-18 修改指定索引的元素值

 1  use std::collections::VecDeque;
 2
 3  fn main() {
 4      let mut v: VecDeque<u32> = VecDeque::new();
 5      v.push_back(1);
 6      v.push_back(2);
 7      v.push_back(3);
 8    
 9      v[1] = 5;
10      println!("v: {:?}", v);
11  }
12
13  // v: [1, 5, 3]

3)刪除VecDeque的元素有以下兩種方式。

① 使用pop_front方法刪除并返回隊(duì)列的頭部元素,使用pop_back方法刪除并返回隊(duì)列的尾部元素。代碼清單2-19中,使用第11行代碼中的pop_back方法刪除隊(duì)列尾部元素,并返回打印的Option類型的值Some(3)。此時(shí),v中的元素是[2, 1, 1, 2]。第12行代碼中的pop_front方法刪除隊(duì)列頭部元素,并返回打印的Option類型的值Some(2)。此時(shí),v中的元素是[1, 1, 2]。

代碼清單2-19 使用pop_front、pop_back方法刪除元素

 1  use std::collections::VecDeque;
 2
 3  fn main() {
 4      let mut v: VecDeque<u32> = VecDeque::new();
 5      v.push_back(1);
 6      v.push_back(2);
 7      v.push_back(3);
 8      v.push_front(1);
 9      v.push_front(2);
10    
11      println!("e: {:?}", v.pop_back());
12      println!("e: {:?}", v.pop_front());
13      println!("v: {:?}", v);
14  }
15
16  // e: Some(3)
17  // e: Some(2)
18  // v: [1, 1, 2]

② 使用remove方法刪除并返回隊(duì)列指定索引的元素,同時(shí)將其后面的所有元素向左移動一位。如果索引越界,則返回None。代碼清單2-20中,第4行代碼中VecDeque::with_capacity函數(shù)創(chuàng)建一個(gè)指定容量為10、存儲u32類型元素的VecDeque。第9行代碼中remove方法刪除索引為1的元素,并返回打印的Option類型的值Some(2)。第10行代碼刪除索引為5的元素,由于索引已越界,返回None。

代碼清單2-20 使用remove方法刪除元素

 1  use std::collections::VecDeque;
 2
 3  fn main() {
 4      let mut v: VecDeque<u32> = VecDeque::with_capacity(10);
 5      v.push_back(1);
 6      v.push_back(2);
 7      v.push_back(3);
 8    
 9      println!("e: {:?}", v.remove(1));
10      println!("e: {:?}", v.remove(5));
11      println!("v: {:?}", v);
12  }
13
14  // e: Some(2)
15  // e: None
16  // v: [1, 3]

3. VecDeque的訪問

訪問VecDeque的元素有以下兩種方式。

1)使用“實(shí)例名[索引]”語法訪問指定索引的元素。代碼清單2-21中,第9行代碼訪問索引為0的元素,即VecDeque的第1個(gè)元素。如果取消第10行的注釋,由于索引越界,將會導(dǎo)致程序錯(cuò)誤。

代碼清單2-21 使用索引語法訪問元素

 1  use std::collections::VecDeque;
 2
 3  fn main() {
 4      let mut v: VecDeque<u32> = VecDeque::new();
 5      v.push_back(1);
 6      v.push_back(2);
 7      v.push_back(3);
 8    
 9      println!("e: {}", v[0]);
10      // println!("e: {}", v[10]);
11  }
12
13  // e: 1

2)使用get方法以索引作為參數(shù)訪問元素。代碼清單2-22中,第9行代碼訪問索引為0的元素,返回值類型是Option<&i32>。第10行代碼訪問索引為10的元素,由于索引越界,將會返回None。

代碼清單2-22 使用get方法訪問元素

 1  use std::collections::VecDeque;
 2
 3  fn main() {
 4      let mut v: VecDeque<u32> = VecDeque::new();
 5      v.push_back(1);
 6      v.push_back(2);
 7      v.push_back(3);
 8    
 9      println!("e: {:?}", v.get(0));
10      println!("e: {:?}", v.get(10));
11  }
12
13  // e: Some(1)
14  // e: None

2.4.3 HashMap

哈希表(HashMap)是基于哈希算法來存儲鍵-值對的集合,其中所有的鍵必須是同一類型,所有的值也必須是同一類型,不允許有重復(fù)的鍵,但允許不同的鍵有相同的值。Rust使用HashMap結(jié)構(gòu)體表示哈希表,它定義在標(biāo)準(zhǔn)庫的std::collections模塊中。使用HashMap結(jié)構(gòu)體之前需要顯式導(dǎo)入std::collections::HashMap。

1. HashMap的創(chuàng)建

創(chuàng)建HashMap有以下兩種方式。

1)使用HashMap::new函數(shù)創(chuàng)建空的HashMap,代碼如下:

let mut map: HashMap<&str, i32> = HashMap::new();

2)使用HashMap::with_capacity函數(shù)創(chuàng)建指定容量的HashMap,代碼如下:

let mut map: HashMap<&str, i32> = HashMap::with_capacity(10);

2. HashMap的修改

由于HashMap中元素是鍵-值對的特殊性,要修改HashMap就必須考慮鍵已經(jīng)有值的情況。修改HashMap的常見操作有插入/更新鍵-值對、只在鍵沒有對應(yīng)值時(shí)插入鍵-值對、以新舊兩值的計(jì)算結(jié)果來更新鍵-值對和刪除鍵-值對。

1)使用insert方法在HashMap中插入或更新鍵-值對。如果鍵不存在,執(zhí)行插入操作并返回None。如果鍵已存在,執(zhí)行更新操作,將對應(yīng)鍵的值更新并返回舊值。

代碼清單2-23中,第4行代碼中HashMap::new函數(shù)創(chuàng)建一個(gè)空的HashMap,可存儲由&str類型的鍵與i32類型的值組成的鍵-值對。第6行代碼由于鍵zhangsan不存在,insert方法執(zhí)行插入操作,返回None。第12行代碼由于鍵zhangsan已經(jīng)存在,insert方法執(zhí)行更新操作,鍵值由97變成79,返回Some(97)。

代碼清單2-23 使用insert方法插入/更新鍵-值對

 1  use std::collections::HashMap;
 2
 3  fn main() {
 4      let mut map: HashMap<&str, i32> = HashMap::new();
 5
 6      let zhangsan1 = map.insert("zhangsan", 97);
 7      map.insert("lisi", 86);
 8    
 9      println!("{:?}", zhangsan1);
10      println!("{:?}", map);
11
12      let zhangsan2 = map.insert("zhangsan", 79);
13      println!("{:?}", zhangsan2);
14      println!("{:?}", map);
15  }
16
17  // None
18  // {"lisi": 86, "zhangsan": 97}
19  // Some(97)
20  // {"lisi": 86, "zhangsan": 79}

2)使用entry和or_insert方法檢查鍵是否有對應(yīng)值,沒有對應(yīng)值就插入鍵-值對,已有對應(yīng)值則不執(zhí)行任何操作。entry方法以鍵為參數(shù),返回值是一個(gè)枚舉類型Entry。Entry類型的or_insert方法以值為參數(shù),在鍵有對應(yīng)值時(shí)不執(zhí)行任何操作。在鍵沒有對應(yīng)值時(shí),將鍵與值組成鍵-值對插入HashMap。

代碼清單2-24中,第6行代碼中entry方法會檢查鍵zhangsan是否有對應(yīng)值,沒有對應(yīng)值就插入該鍵-值對。第10行代碼中entry方法會再次檢查鍵zhangsan是否有對應(yīng)值,發(fā)現(xiàn)鍵zhangsan已有對應(yīng)值97,那就不執(zhí)行任何操作直接返回這個(gè)值的類型Entry,因此鍵zhangsan的對應(yīng)值不變。

代碼清單2-24 使用entry方法插入鍵-值對

 1  use std::collections::HashMap;
 2
 3  fn main() {
 4      let mut map: HashMap<&str, i32> = HashMap::new();
 5    
 6      map.entry("zhangsan").or_insert(97);
 7      map.entry("lisi").or_insert(86);
 8      println!("{:?}", map);
 9
10      map.entry("zhangsan").or_insert(79);
11      println!("{:?}", map);
12  }
13
14  // {"lisi": 86, "zhangsan": 97}
15  // {"lisi": 86, "zhangsan": 97}

3)以新舊兩值的計(jì)算結(jié)果來更新鍵-值對是指找到一個(gè)鍵對應(yīng)值,結(jié)合新舊兩值進(jìn)行某些計(jì)算處理,以計(jì)算結(jié)果來更新鍵對應(yīng)值。比如,老師發(fā)現(xiàn)本次考試試卷上出現(xiàn)了一道錯(cuò)題,決定為所有學(xué)生的分?jǐn)?shù)都加上2分,那么就可以將每個(gè)學(xué)生的名字作為鍵,將對應(yīng)分?jǐn)?shù)加上2。

代碼清單2-25中,第11行代碼中iter_mut方法會返回由HashMap中所有鍵-值對的可變引用組成的迭代器,通過for循環(huán)遍歷迭代器。由于只針對值做處理,因此只取&mut i32類型的val,通過解引用操作符“*”對val進(jìn)行賦值操作。關(guān)于迭代器、for循環(huán)、可變引用以及解引用操作符的知識點(diǎn),我們將會在后續(xù)章節(jié)詳細(xì)介紹。

代碼清單2-25 修改HashMap元素

 1  use std::collections::HashMap;
 2
 3  fn main() {
 4      let mut map: HashMap<&str, i32> = HashMap::new();
 5    
 6      map.insert("zhangsan", 97);
 7      map.insert("lisi", 86);
 8      map.insert("wangwu", 55);    
 9      println!("{:?}", map);
10
11      for (_, val) in map.iter_mut() {
12          *val += 2;
13      }
14      println!("{:?}", map);
15  }
16
17  // {"zhangsan": 97, "lisi": 86, "wangwu": 55}
18  // {"zhangsan": 99, "lisi": 88, "wangwu": 57}

4)使用remove方法刪除并返回指定的鍵-值對,如果不存在就返回None。代碼清單2-26中,第11行代碼中的remove方法刪除鍵wangwu的對應(yīng)值,并將值返回,打印Option類型的值Some(55)。

代碼清單2-26 使用remove方法刪除鍵-值對

 1  use std::collections::HashMap;
 2
 3  fn main() {
 4      let mut map: HashMap<&str, i32> = HashMap::new();
 5    
 6      map.insert("zhangsan", 97);
 7      map.insert("lisi", 86);
 8      map.insert("wangwu", 55);
 9      println!("{:?}", map);
10    
11      let result = map.remove("wangwu");
12      println!("{:?}", map);
13      println!("{:?}", result);
14  }
15
16  // {"wangwu": 55, "lisi": 86, "zhangsan": 97}
17  // {"lisi": 86, "zhangsan": 97}
18  // Some(55)

3. HashMap的訪問

訪問HashMap中指定的鍵-值對有以下兩種方式。

1)使用“實(shí)例名[鍵]”語法訪問指定的鍵-值對。如果鍵不存在,將會導(dǎo)致程序錯(cuò)誤。代碼清單2-27中,第7行代碼訪問鍵zhangsan的對應(yīng)值,由于鍵zhangsan存在,可以正常訪問到對應(yīng)值。如果取消第8行的注釋,由于鍵wangwu不存在,將會導(dǎo)致程序錯(cuò)誤。

代碼清單2-27 使用“實(shí)例名[鍵]”語法訪問鍵-值對

 1  use std::collections::HashMap;
 2
 3  fn main() {
 4      let mut map: HashMap<&str, i32> = HashMap::new();
 5      map.insert("zhangsan", 97);
 6    
 7      println!("zhangsan: {}", map["zhangsan"]);
 8      // println!("wangwu: {}", map["wangwu"]);
 9  }
10
11  // zhangsan: 97

2)使用get方法以鍵為參數(shù)訪問指定的鍵-值對。代碼清單2-28中,第7行代碼訪問鍵zhangsan的對應(yīng)值,返回值類型是Option<&i32>。第8行代碼訪問鍵wangwu的對應(yīng)值,由于鍵wangwu不存在,將會返回None。

代碼清單2-28 使用get方法為參數(shù)訪問指定的鍵-值對

 1  use std::collections::HashMap;
 2
 3  fn main() {
 4      let mut map: HashMap<&str, i32> = HashMap::new();
 5      map.insert("zhangsan", 97);
 6    
 7      println!("zhangsan: {:?}", map.get("zhangsan"));
 8      println!("wangwu: {:?}", map.get("wangwu"));
 9  }
10
11  // zhangsan: Some(97)
12  // wangwu: None
主站蜘蛛池模板: 建瓯市| 浪卡子县| 青田县| 抚顺市| 洛隆县| 白城市| 磐石市| 明水县| 那曲县| 江永县| 汪清县| 太仆寺旗| 武穴市| 沈丘县| 云浮市| 灵丘县| 新乐市| 广南县| 新绛县| 大港区| 黎城县| 雅江县| 深圳市| 额济纳旗| 江源县| 怀柔区| 东平县| 屯门区| 河津市| 鄯善县| 西宁市| 泸西县| 金川县| 当雄县| 诸城市| 鄂尔多斯市| 克山县| 通化市| 绥芬河市| 满洲里市| 潞西市|