1. 程式人生 > >順序容器----順序容器概述,容器庫概覽

順序容器----順序容器概述,容器庫概覽

一、順序容器概述

  一個容器就是一些特定型別物件的集合。順序容器為程式設計師提供了控制元素儲存和訪問順序的能力。這種順序不依賴於元素的值,而是與元素加入容器時的位置相對應。

  順序容器型別:

容器型別 說明
vector 可變大小陣列。支援快速隨機訪問。在尾部之外的位置插入或刪除元素可能會很慢。
deque 雙端列表。支援快速隨機訪問。在頭尾位置插入/刪除速度很快。
list 雙向連結串列。只支援雙向順序訪問。在list中任何位置進行插入/刪除操作速度都很快。
forward_list 單向連結串列。只支援單向順序訪問。在連結串列任何位置進行插入/刪除操作速度都很快。
array 固定大小陣列。支援快速隨機訪問。不能新增或刪除元素。
string 與vector相似的容器,但專門用於儲存字元。支援快速隨機訪問。在尾部插入/刪除速度快。

  string和vector將元素儲存在連續的記憶體空間中。由於元素是連續儲存的,由元素的下標來計算其地址是非常快速的。但是,在這兩種容器的中間位置新增或刪除元素就會非常耗時。

  list和forward_list這兩個容器的設計目的是令容器任何位置的新增和刪除元素都很快速。作為代價,這兩個容器不支援元素的隨機訪問:為了訪問一個元素,我們只能遍歷整個容器。而且,與vector、deque和array相比,這兩個容器的額外記憶體開銷也很大。

  deque是一個更為複雜的資料結構。與string和vector類似,在deque的中間位置新增或刪除元素的代價(可能)很高。但是,在deque的兩端新增或刪除元素都是很快的,與list和forward_list新增刪除元素的速度相當。

  forward_list和array是新C++標準增加的型別。與內建陣列相比,array是一種更安全、更容易使用的陣列型別。forward_list的設計目標是達到與最好的手寫的單向連結串列資料結構相當的效能。因此,forward_list沒有size操作,因為儲存或計算其大小就會比手寫連結串列多出額外的開銷。

 

二、容器庫概述

  一般來說,每個容器都定義在一個頭檔案中,檔名與型別名相同。

  順序容器幾乎可以儲存任意型別的元素。特別是,我們可以定義一個容器,其元素的型別是令一個容器。這種容器的定義與任何其他容器型別完全一樣。但某些容器操作對元素型別有其自己的特殊要求。我們可以為不支援特定操作需求的型別定義容器,但這種情況下就只能使用那些沒有特殊要求的容器操作了。

  所有容器都支援的操作:

類型別名 說明
iterator 此容器型別的迭代器型別
const_iterator 可以讀取元素,但不能修改元素的迭代器型別
size_type 無符號整數型別,足夠儲存此種容器型別最大可能容器的大小
difference_type 帶符號整數型別,足夠儲存兩個迭代器之間的距離
value_type 元素型別
reference 元素的左值型別;與value_type&含義相同
const_reference 元素的const左值型別(即,const value_type&)
建構函式  
C c; 預設建構函式,構造空容器(array不支援)
C c1(c2); 構造c2的拷貝c1
C c(b, e); 構造c,將迭代器b和e指定的範圍內的元素拷貝到c(array不支援)
C c{a, b, c, ...}; 列表初始化c
賦值與swap  
c1 = c2 將c1中的元素替換為c2中元素
c1 = {a, b, c, ...} 將c1中的元素替換為列表中元素(array不支援)
a.swap(b) 交換a和b的元素
swap(a,b) 與a.swap(b)等價
大小  
c.size() c中元素的數目(不支援forward_list)
c.max_size() c可儲存的最大元素數目
c.empty() 若c中儲存了元素,返回false,否則返回true

新增/刪除元素(array不支援)

注:在不同容器中,這些操作的介面都不同

 
c.insert(args) 將args中的元素拷貝進c
c.emplace(inits) 使用inits構造c中的一個元素
c.erase(args) 刪除args指定的元素
c.clear() 刪除c中的所有元素,返回void
關係運算符  
==, != 所有容器都支援相等(不相等)運算子
<, <=, >, >= 關係運算符(無序關聯容器不支援)
獲取迭代器  
c.begin(), c.end() 返回指向c的首元素和尾元素之後位置的迭代器
c.cbegin(), c.cend() 返回const_iterator
反向容器的額外成員(不支援forward_list)  
reverse_iterator 按逆序定址元素的迭代器
const_reverse_iterator 不能修改元素的逆序迭代器
c.rbegin(), c.rend() 返回指向c的尾元素和首元素之前位置的迭代器
c.crbegin(), c.crend() 返回const_reverse_iterator
   

1、迭代器

  標準容器迭代器的運算子:

運算子 說明
*iter 返回迭代器iter所指元素的引用
iter->mem 解引用iter並獲取該元素的名為mem的成員,等價於(*iter).mem
++iter 令iter指向容器的下一個元素
--iter 令iter指向容器中的上一個元素
iter1 == iter2 判斷兩個迭代器是否相等(不相等),如果兩個迭代器指示的是同一個元素或者它們是同一個容器的尾喉迭代器,則相等;反之,不相等
iter1 != iter2  

其中,forward_list迭代器不支援遞減運算子(--)。

 

  string、vector、deque、array的迭代器支援的算術運算:

算術運算 說明
iter + n 迭代器加上一個整數值仍得一個迭代器,迭代器指示的新位置與原來相比向前移動了若干個元素。結果迭代器或者指示容器內的一個元素,或者等於容器尾後迭代器
iter - n 迭代器減去一個整數值仍得一個迭代器,迭代器指示的新位置與原來相比向後移動了若干個元素
iter += n 迭代器加法的複合賦值語句,將iter加n的結果賦給iter
iter -= n 迭代器減法的複合賦值語句,將iter減n的結果賦給iter
iter1 - iter2 兩個迭代器相減的結果是它們之間的距離。參與運算的必須是同一個容器的迭代器
>、>=、<、<= 迭代器的關係運算符,如果某迭代器指向的容器位置在另一個迭代器所指位置之前,則說明前者小於後者。參與運算的必須是同一個容器的迭代器

注意:上面的迭代器的範圍區間是[begin(),end()],如果運算結果不在這個區間內,會報錯。

  

  一個迭代器範圍是由一對迭代器表示,兩個迭代器分別指向同一個容器中的元素或者尾元素之後的位置。這兩個迭代器通常被稱為begin和end。這種元素範圍被稱為左閉右合區間,其標準數學描述為:[begin, end)。

  當auto與begin或end結合使用時,獲得的迭代器型別依賴於容器型別,如果物件是常量,返回const_iterator;如果物件不是常量,返回iterator。cbegin和cend不論物件是否是常量,返回值都是const_iterator。

 

2、容器定義和初始化

操作 說明
C c 預設建構函式。如果c是一個array,則c中元素按預設方式初始化;否則c為空
C c1(c2) c1初始化為c2的拷貝。c1和c2必須是相同型別(即,它們必須是相同的容器型別,且儲存的是相同的元素型別;對於array來說,兩者還必須具有相同大小
C c1=c2 同上
C c{a, b, c, ...} c初始化為初始化列表中元素的拷貝。列表中元素的型別必須與c的元素型別相容。對於array型別,列表中元素數目必須等於或小於array的大小,任何遺漏的元素都進行值初始化。初始化列表隱含地指定了容器的大小:容器將包含與初始值一樣多的元素。
C c={a, b, c, ...} 同上
C c(b, e) c初始化為迭代器b和e指定範圍中的元素的拷貝。範圍中元素的型別必須與c的元素型別相容(array不適用)
只有順序容器(不包括array)的建構函式才能接受大小引數  
C seq(n) seq包含n個元素,這些元素進行了值初始化;此建構函式是explicit的
C seq(n, t) seq包含n個初始化為值t的元素

  將一個容器初始化為另一個容器的拷貝的例子:

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 #include <deque>
 5 #include <list>
 6 #include <forward_list>
 7 #include <array>
 8 
 9 int main()
10 {
11     std::list<std::string> authors = { "abc", "QAQ", "hello" };
12     std::vector<const char*> articles = { "a", "ab", "the" };
13     
14     std::list<std::string> list2(authors); // 型別匹配
15     //std::deque<std::string> auth(authors); // 錯誤:容器型別不匹配
16     std::forward_list<std::string> words(articles.begin(), articles.end()); // 元素型別可以相互轉換
17     return 0;
18 }
View Code

1)標準庫array具有固定大小

  與內建陣列一樣,標準庫array的大小也是型別的一部分。當定義一個array時,除了指定元素型別,還要指定容器大小。使用array時也要指定容器大小。

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 #include <deque>
 5 #include <list>
 6 #include <forward_list>
 7 #include <array>
 8 
 9 int main()
10 {
11     std::array<int, 10> arr; // 型別為10個int的陣列
12     std::array<int, 10>::size_type i; // 陣列型別包括元素型別和大小
13     return 0;
14 }
View Code

  一個預設構造的array是非空的:它包含了與其大小一樣多的元素。這些元素都被預設初始化。如果我們對array進行列表初始化,初始值的數目必須等於或小於array的大小。如果初始值數目小於array的大小,則它們被用來初始化array中靠前的元素,所有剩下的元素都會進行值初始化。

  雖然我們不能對內建陣列型別進行拷貝或物件賦值操作,但array並無此限制。

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 #include <deque>
 5 #include <list>
 6 #include <forward_list>
 7 #include <array>
 8 
 9 int main()
10 {
11     std::array<int, 10> arr = { 2, 3 };
12     std::array<int, 10> copy = arr;
13     return 0;
14 }
View Code

 

3、賦值和swap

   賦值運算子將其左邊容器中的全部元素替換為右邊容器中元素的拷貝。

  所有容器都支援的賦值運算:

操作 說明
c1=c2 將c1中的元素替換為c2中元素的拷貝。c1和c2必須具有相同的容器型別,且儲存的是相同的元素型別
c={a, b, c, ...} 將c1中的元素替換為初始化列表中元素的拷貝(array不適用)。儲存的必須是相同的元素型別
swap(c1, c2) 交換c1和c2中的元素。c1和c2必須具有相同的型別。swap通常比從c2向c1拷貝元素快得多
c1.swap(c2) 同上
assign操作不適用於關聯容器和array  
seq.assign(b, e) 將seq中的元素替換為迭代器b和e所表示的範圍中的元素。迭代器b和e不能指向seq中的元素。儲存的元素型別必須相容。
seq.assign(items) 將seq中的元素替換為初始化列表items中的元素
seq.assign(n, t) 將seq中的元素替換為n個值為t的元素 

  賦值相關運算會導致指向左邊容器內部的迭代器、引用和指標失效。

  swap操作將容器內容交換不會導致指向容器的迭代器、引用和指標失效(容器型別為array何string的情況除外)。

1)使用assign(僅順序容器)

  順序容器(array除外)還定義了一個名為assign的成員,允許我們從一個不同但相容的型別賦值,或者從容器的一個子序列賦值。

 1 #include <iostream>
 2 #include <string>
 3 #include <vector>
 4 #include <deque>
 5 #include <list>
 6 #include <forward_list>
 7 #include <array>
 8 
 9 int main()
10 {
11     std::vector<int> vec = { 1, 2, 3 };
12     std::vector<double> vec2 = { 4.5, 5.5, 6.5 };
13     // vec = vec2; // 錯誤:型別不同
14     std::list<double> lst = { 7, 8, 9 };
15     vec.assign(lst.begin(), lst.end());
16     return 0;
17 }
View Code

2)使用swap

  除array外,交換兩個容器內容的操作保證會很快----元素本身並未交換,swap只是交換了兩個容器的內部資料結構。

  元素不會被移動的事實意味著,除string外,指向容器的迭代器、引用和指標在swap操作都不會失效。他們仍然指向操作之前所指向的那些元素。但是,在swap之後,這些元素已經屬於不同的容器了。

  對一個string呼叫swap會導致迭代器、引用和指標失效。

  swap兩個array會真正交換它們的元素。因此,交換兩個array所需的時間與array中元素的數目成正比。

  因此,對於array,在swap操作之後,指標、引用和迭代器所繫結的元素保持不變,但元素值已經與另一個array中對應元素的值進行了交換。

 

5、關係運算符

  每個容器型別都支援相等運算子(==和!=);除了無序關聯容器外的所有容器都支援關係運算符(>、>=、<、<=)。關係運算符左右兩邊的運算物件必須是相同型別的容器,且必須儲存相同型別的元素。

  比較兩個容器實際上是進行元素的比較。這些運算子的工作方式與string的關係運算類似:

a、如果兩個容器具有相同大小且所有元素都兩兩對應相等,則這兩個容器相等;否則兩個容器不等。

b、如果兩個容器大小不同,但較小容器中每個元素都等於較大容器中的對應元素,則較小容器小於較大容器。

c、如果兩個容器都不是另一個容器的字首子序列,則它們的比較結果取決於第一個不相等的元素的比較結果。

  只有當其元素型別也定義了相應的比較運算子時,我們才可以使用關係運算符比較兩個容器。

  容器的相等運算實際上是使用元素的==運算子實現比較的,而其他關係運算符是使用元素的<運算子。如果元素不支援所需運算子,那麼儲存這種元素的容器就不能使用相應的關係運算。