1. 程式人生 > >C++中常用的std標準容器

C++中常用的std標準容器

    從c++11標準以來,c++中std定義的幾種容器的效率非常高,優化的非常好,完全沒有必要自己去定義類似的資料結構。瞭解使用它們,可以滿足90%的日常程式設計需要。該篇文章基於c++11標準,從使用者角度來介紹常用的順序容器與並聯容器(如果想從內部瞭解它們是怎麼實現的,推薦看看《std原始碼剖析》這本書)。它們包括:

順序容器:

  • vector
  • string (它不是類模板)
  • list
  • forward_list
  • deque
  • queue
  • priority_queue
  • stack

有序關聯容器:

  • map
  • multimap
  • set
  • multiset

 無序關聯容器:

  unordered_map

  unordered_multimap

  unordered_set

  unordered_multiset

順序容器

1. vector容器

a. vector的定義與初始化

// T 表示例項化類模板時使用的型別

vector<T> v1                 // 預設初始化, 此時v1為空。
vector<T> v1(v2)              // 執行的copy初始化,此時v1與v2的內容相同
vector<T> v1 = v2           // 與上面相同,都會執行copy建構函式
vector<T> v1(n)              //
此時v1的size大小為n ,它裡面的值是根據T的型別進行預設初始化的 vector<T> v1(n, a)   // v1的初始化為n個值為a的元素 vector<T> v1{a, b, c}    // 列表初始化,v1內現在的元素就是a, b, c (這是c++11標準新入的) vector<T> v1 = {a, b, c}    // 與上面相同

列表初始化是什麼?

對於上面的幾種初始化方法,最常用的有三種, 1. 預設初始化,這裡vector為空;2.copy初始化,這時用另一個vector初始化該vector 3. 列表初始化,為vector 初始化一些初始值。   幾乎或很少在初始化vector的時候去設定它的size大小,因為vector的push_bask是非常高效的,甚至比提前設定它的大小更高效(見c++primer 頁)

b. vecotr常使用的操作 

1. 屬性操作

v1.size()      //v1內已經存放的元素的數目
v1.capacity()    // v1現有的在儲存容量(不再一次進行擴張記憶體空間的前提下)
v1.empty()     // 判斷v1是否為空
v1.max_size()    // 返回vector可以存放的最大元素個數,一般這個數很大,因為vector可以不斷調整容量大小。
v1.shrink_to_fit()  // 該函式會把v1的capacity()的大小壓縮到size()大小,即釋放多餘的記憶體空間。

2. 訪問操作:訪問操作都會返回引用,通過它,我們可以修改vector中的值。

v1[n]        // 通過下標進行訪問vector中的元素的引用 (下標一定要存在 ,否則未定義,軟體直接崩了)
v1.at(n)       // 與上面類似,返回下標為n的元素的引用,不同的是,如果下標不存在,它會丟擲out_of_range的異常。它是安全的,建議使用它。
v1.front()      // 返回vector中頭部的元素的引用(使用時,一定要進行非空判斷)
v1.back()      // 返回vector中尾部的元素 引用(使用時,一定要進行非空判斷)

3. 新增操作:

v1.push_back(a)        //在迭代器的尾部新增一個元素
v1.push_front(a)        // vector不支援這個操作
v1.insert(iter,  a)        // 將元素a 插入到迭代器指定的位置的前面,返回新插入元素的迭代器(在c++11標準之前的版本,返回void)
v1.insert(iter, iter1, iter2)       //把迭代器[iterator1, iterator2]對應的元素插入到迭代器iterator之前的位置,返回新插入的第一個元素的迭代器(在c++11標準之前的版本, 返回空)。

    在c++11標準中,引入了emplac_front()、 emplace()、emplace_back(), 它們分別與push_front()、insert()、 push_back()相對應,用法與完成的動作作完全相同,但是實現不一樣。 push_front()、insert()各push_back()是對元素使用copy操作來完成的,而emplac_front()、 emplace()和emplace_back()是對元素使用構造來完成的,後者的效率更高,避免了不必要的操作。因此,在以後更後推薦使用它們。

4. 刪除操作:

v1.erase(iterator)     // 刪除人人迭代器指定的元素,返回被刪除元素之後的元素的迭代器。(效率很低,最好別用)
v1.pop_front()       //vector不支援這個操作
v1.pop_back()      //刪除vector尾部的元素 , 返回void型別 (使用前,一定要記得非空判斷)
v1.clear()         //清空所有元素

 5. 替換操作:

v1.assign({初始化列表})    // 它相當於賦值操作,
v1.assign(n, T)        // 此操作與初始化時的操作類似,用個n T型別的元素對v1進行賦值
v1.assign(iter1, iter2)     // 使用迭代器[iter1, iter2]區間內的元素進行賦值(該迭代器別指向自身就可以),另外,只要迭代器指的元素型別相同即可(存放元素的容器不同,例如:可以用list容器內的值對vector容器進行assign操作,而用 "=" 絕對做不到的。
v1.swap(v2)      // 交換v1與v2中的元素。  swap操作速度很快,因為它是通過改變v1與v2兩個容器內的資料結構(可能是類似指標之類的與v1和v2的繫結)完成的,不會對容器內的每一個元素進行交換。 這樣做,不僅速度快,並且指向原容器的迭代器、引用以及指標等仍然有效,因為原始的資料沒有變。在c++ primer 中建議大家使用非成員版本的swap()函式,它在範型程式設計中很重要。

c. 小結:

    1. vector容器最重要的特性是: 它在一段連續的記憶體空間中儲存元素, 可以在常量時間內對vector容器進行隨機訪問,並且可以很高效的在vector的尾部進行新增與刪除操作,在vector中間或頭部新增與刪除元素的效率很低。
    2. 只要對vector進行增加與刪除元素的操作,都會使迭代器、指標、引用失效(可能有時候它們仍然有效,不過是隨機的,絕對不能作這樣假設)。所以當使用vector的迭代器、引用和指標時,一定要杜絕對他們進行增加與刪除元素的操作
    3.  對於vector的迭代器,它除了可以進行  ++iter   與   --iter   的操作之外 ,還可以進行算術運算,例如: iter + n 、 ::difference_type a = iter1 - iter2 //它的返回型別為 ::difference_type,例如vector<int>::difference_type (另一個也支援迭代器算術運算的容器為string)
    4. 待補充!

 2. string容器

     string與vector類似,但是string不是一種類模板,而就是一種型別,因為它專門用於存放字元的(存放的元素型別已經明確),所以沒有設計為類模板。它的所有特性與vector相同,包括儲存在連續的空間/快速隨機訪問/高效在尾部插入與刪除/低效在中間插入與刪除等, string的迭代器也支援算術運算。  實際上,就可以把string型別看作為vector<char>型別, vector的所有特性都適合與string型別。當然,因為string型別比vector模板更特例化一些,因此它肯定具有一些自己特有而vector沒有的特性,下面總結一下。

在陳述之前,首先說明:

1. 在string中(有一些也適用於C風格的字串),我們可以使用一組迭代器/單個迭代器(從此迭代器開始到字串末)/位置+長度表示範圍/單個位置(從此位置到字串末)來表示字串中的範圍, 這樣的引數記作range.

2.  可以使用列表初始化的字串/使用字串+range的組合形式表示的子字串 / 字面值常量(如“china”)來表示字串。  這裡的字串包括string型別的字串和C風格的char* 字串。  字串使用字元args 表示。

正因為pos和args的樣式可以隨意組合,所以string的操作函式的引數是多種的,因此它的過載函式數目很多,由於對於insert(pos, args)/append(args)/erase(pos,args)/replace(pos, args)等操作。

a.   string的初始化

相對於vector型別來說, string 增加一個使用字面值型別進行初始化,即:

1 string a("xiaoming")
2 string a = "xiaoming"

b. string中包含的專有的操作(相對於vector來說)

1.  string的新增與替換

在string中,增加了append()與 replace()函式

str.append(args)    // 在尾部新增一個字元或一個字元

str.replace(pos, args)    // 在尾部新增一個字元或一個字元 ,它的過載函式很多,共16個。

2. string的訪問子字串:

str.substr(_pos, n)  //該函式可以獲得原字串中的部分字元, 從pos開始的n個字元,當_pos超過範圍時,會丟擲out_of_range的異常。

3. str的搜尋操作:

str.find(args)  //查詢args 第一次出現的位置

str.rfind(args)  //查詢args最後一次出現的位置

str.find_first_of(args)   //搜尋的是字元, 第一個是args裡的字元的位置

str.find_last_of(args)   // 搜尋的是字元, 最後一個是args裡的字元的位置

str.find_first_not_of()  // 搜尋的是字元,第一個不是args裡的字元的位置

str.find_last_not_of()  // 搜尋的是字元, 最後一個不是args裡的字元的位置

4. str的大小操作:

str.length()   // 該函式與str.size()函式完成一樣,只是名字不同而已罷了。只所以這樣搞的原因,可能開發人員感覺length更適合字串符,size更適合容器吧。

c字串的轉換函式

1. 由數值轉換為字串:

to_string(val): 

2. 由字串轉換為數值:(要轉換的string的第一個非空白符必須是數值中可能出現的字元,處理直到不可能轉換為數值的字元為止,以下內容來自:c++primer)

stoi(str, pos, base)    // 字串轉換為整型,其中str表示字串,  pos用於表示第一個非數值字元的下標(意思就是我給函式傳入一個地址,它會對它進行賦第一個非數值字元的位置), base表數值的基數,預設為10,即10進位制數。
stol(str, pos, base)    // 轉換為long
stoul(str, pos, base)    // 轉換為 unsigned long
stoll(str, pos, base)    // 轉換為 long long
stoull(str, pos, base)   // 轉換為unsigned long long
stof(str, pos)      // 轉換為float
stod(str, pos,)     // 轉換為double
stold(str, pos,)     // 轉換為long double

d 對字元的操作(在cctype標頭檔案中,並不屬於string標頭檔案的範圍,但是關係很緊密的)

以下內容來自:c++ primer 第五版p82, 只寫出部分常用來的(字母:alpha, 數字:number或digit)

isalnum(c)  // 當為字母或數字時為真

isalpha(c)  // 當為字母時為真

isdigit(c)  // 當為數字時真

islower(c)  // 當為小寫字母時為真

issupper(c)  // 當為大寫字母時為真

isspace(c)  // 當為空格時為真

tolower(c)  // 轉換為小寫字母, 當本身為小寫字母時,原樣輸出

toupper(c)  // 轉換為大寫字母, 當本身為大寫字母時,原樣輸出

3. list 容器

    與vector和string相比,list內部的實現為一個雙向連結串列,它的元素不是儲存在連續的記憶體空間中,而是非連續的,這就決定了它不能在常量時間內完成對元素的隨機訪問,只能從頭到尾的遍歷一遍。  因為它是用雙向連結串列實現的,所以,它的一大特性就是它的迭代器永遠不會變為無效(除非這段空間不存在了),即無論增加、刪除操作,都不會破壞迭代器。

大多數對vector的操作也適合於list,由於底層實現不同,有也差異:

list與vector的差別:

1. list支援push_front()、pop_front()操作

2. list不支援vector中的隨機訪問操作,即使用v1.at( )和v1[ ] 操作。

3. list的刪除與增加元素的操作不會破壞迭代器,而 vector與string 會使迭代器失效。

4. list 內部增加了一個sort()的方法,用於實現排序,不過呢,反正我感覺基本不用它,直接用<algorithm>裡的範型sort()更好啊啊。

 5. list增加了一個類似insert()的函式,為splice( ) :該函式可以實現在常數時間內把一個list  插入到另一個list內,與insert()的區別在於insert是進行copy, 而splice()直接操作的連結串列的指標指向。它有好幾個過載函式。

 6. list的去重複函式: unique(); 該函式的作用是去除連續重複的元素,引數即可以為空,也可以傳入一個二元謂詞,用於確定相等的比較演算法。  因為unique()函式可能去除連續重複的元素,因此,很依賴配合上sort()函式使用啊。

7. list的合併函式merge():  該函式就是合併兩個list, 它在合併過程中會在兩個連結串列之間進行來回的比較,如果原來的兩個list是有順序的,合併之後的結果也是有序的,如果合併之前是無序的,合併之後也是無序的。反正吧,這個比較就這樣。

4. forward_list容器

 forward_list的實現是使用單向連結串列(list為雙向連結串列),  在操作單向連結串列的時候,為了對一個元素進行刪除與新增,都需要訪問到該元素的前趨節點,因此呢,forward_list的會有insert.after()emplase.after()/erase.after()等操作, 另外forward_list也沒有size()操作,原因在於為了儘可能讓forward_list與手寫的單向連結串列的效率相同。說實話呢,forward_list操作起來有點反人類,用起來有點不方便,我個人比較買習慣使用list,但是list相對forward_list的記憶體空間花費更多。

以後什麼時候用它的時候,再來介紹。

5. deque容器(double-end queue, 雙端佇列)

6.

有序關聯容器

    關聯容器與順序容器最大的區別在於關聯容器沒有下標,都過鍵值或 值本身進行索引。有序關聯容器內部通過紅黑樹實現的,當搜尋一個元素時,具有O(logn)的平均複雜度,而無序的關聯容器在底層是通過散列表(雜湊函式對映)實現的,當搜尋一個元素時,通常O(1)的平均複雜度,最壞為O(logn), 下面介紹它們。

1. map 容器

在介紹map之前,必須先介紹pair 型別。

 pair型別:

1. pair型別定義在標頭檔案utility中。
2. pair型別為一個結構體型別的模板,(在c++中結構體與類,除了預設的訪問符不同,沒有其它任何區別)
3. pair 有兩個public的資料成員,分別為first與second.
4. pair的初始化與大多數結構體或類的初始化相同:

  • pair<int, string> sb //初始化一個預設值的pair物件sb, 它的first是預設初始化的(0,內建型別預設初始化大多數應該是未定義的啊,它這是為0), second也是採用預設初始化(空字串)
  • pair<int, string> sb(1, "japan"); //很常見的初始化方法
  • pair<int, string> sb = (1, "japan");
  • pair<int, string> sb{1,"japan"} //c++11中的列表初始化方法
  • pair<int, string> sb = {1, "japan"}
  • 可以呼叫make_pair()模板函式,返回一個pair物件:


    1. map是用於存放鍵-值對的容器,它使用pair的first資料成員表示鍵(key),second資料成員表示對應的值(value),所以呢,map是存放pair型別物件的容器。在map中,key都是固定的,一旦使用就不可以改變,而value是可以改變的, 因此會把pair型別的first資料成員的型別宣告為const。

    2. map的特性之一是:按value的大小進行有序存放(unordered_map是無序的), 因此,構造mqp容器時,要求它的key型別必須能夠比較大小,當使用自定義的類型別時,
應該把過載的 operator< 運算子傳遞給map, 例如:

1 // 新增相關程式碼
2 
3 
4 
5 ..

    3.在map中:

  • ::value_type表示"鍵-值 對"型別
  • ::key_type表示鍵型別,vlue型別
  • ::mapped_type 表示值的型別

例如: map<int, string>, 則 map<int, string>::value_type 與pair<int, string>等價, map<int, string>::key_type與int等價, map<int, string>::mapped_type與string等價;

    4. map的訪問操作:

  • map同樣支援使用迭代器,它會返回指向 pair型別的物件 的迭代器
  • map 使用[]運算子 通過key來訪問對應的 value ,如果訪問的key不存在,則會自動新增一個對應的pair 物件,其中它的value採用預設值。因此,當通過key來訪問map時,
  • map不能是const型別。
  • map 使用at()成員函式 通過key來訪問對應的value, 如果訪問的key不存在,則會丟擲一個out_of_range的異常;

    5. map的新增與刪除操作:

  • insert()或emplace()操作: 當向map中插入不存在的元素(指key值不同)時,可以插入成功,當插入一個已經存在key值的pair物件時,ma不會作任何改變。因此,當對map進行插入操作時,需要知道有沒有插入成功。insert()與emplace()函式的 返回值也是一個pair型別,first為一個迭代器,指向插入時的鍵值對應的pair物件(可能是新插入的,也可能是已經存在的), second是一個bool型別,它表示是否插入成功(例如:當map中已經存在待插入的值時,為false)
  • erase()操作:它有三個版本,前兩個版本與順序容器相同,使用迭代器指定一個位置或一對迭代器指定一個範圍,這時返回值為一個迭代器,指向刪除之後的下一個元素;第三個版本的erase()很不錯,我很喜歡,它的引數為key值,刪除對應key值的pair()物件, 返回值為成功刪除的個數(可能為0或1,在multimap中可能為n)

    6.查詢操作

  • find(key): 查詢一個特定key值的pair物件,如果找到就返回對應的迭代器,如果找不到,就返回.end()迭代器。
  • count(key):統計在map容器中特徵key值的pair物件的個數.(在multimap與multiset中很有用的)
  • equal_range(key) // 返回一個pair型別,first表示low_bound, second表示upper_bound;
  • lower_bound(key) //返回迭代器,對應第一個大於等於key的元素
  • upper_bound(key) //返回迭代器,對應第一個大於key的元素 (說明:其實,最後這四個函式,在multimap與multiset中是非常有用的) 

2. multimap容器:

    與map容器相比,區別在於multimap允許鍵值重複,即一個鍵值可能對應多個value。所以呢,相應的操作會有一些變化,例如:multimap不可以像map中使用key 作為索引(使用operator[]和at()成員函式)進行訪問元素(因為對應的value可能是多個),multimap的插入操作一定會成功等,除此之外,它們的性相同, 不多介紹。

3. set容器:

    set容器與map容器的唯一區別在於:存放的元素型別不同: map儲存的是鍵-值對,即pair型別,而set中只存放鍵值。正因為如此,所以:

  • 1. set只有::value_type與key_type型別,沒有::mapped_type型別;
  • 2. set不需要索引訪問操作(通過operator[]和at()函式)

    除此之外, set與map也沒有什麼其它區別了。

4. multiset容器:

    multiset容器相對於set容器,允許它容器內部的元素重複。沒有其它區別了。

無序關聯容器:

1. unordered_map容器:

d

2. unordered_multimap容器:

d

3. unordered_set容器:

d

4. unordered_multiset容器:

d

相關推薦

C++常用std標準容器

    從c++11標準以來,c++中std定義的幾種容器的效率非常高,優化的非常好,完全沒有必要自己去定義類似的資料結構。瞭解使用它們,可以滿足90%的日常程式設計需要。該篇文章基於c++11標準,從使用者角度來介紹常用的順序容器與並聯容器(如果想從內部瞭解它們是怎麼實現的,推薦看看《std原始碼剖析》這本

C++的新標準的for循環的應用

標準 gin bsp clu detail [] num eight add 轉自http://blog.csdn.net/qq_21400315/article/details/50561030 #include "stdafx.h" #include &l

C常用格式格式碼

blog .cn png ima 常用 cnblogs 格式 nbsp sca 一、常用printf格式碼 二、常用scanf格式碼 C中常用格式格式碼

c#常用集合類和集合接口之集合類系列【轉】

arr 關聯 special rect 替代 不能 一個數 lock resize 常用集合接口系列:http://www.cnblogs.com/fengxiaojiu/p/7997704.html 常用集合類系列:http://www.cnblogs.com/fengx

c++常用的泛型演算法

    std中定義了很好幾種順序容器,它們自身也提供了一些操作,但是還有很多演算法,容器本身沒有提供。 而在algorithm標頭檔案中,提供了許多演算法,適用了大多數順序容器。與c++11相比,很多函式在 c++17與c++20又改變了很多,下面內容基於c++11去簡單介紹.

C++std::lock_guard和std::unique_lock

std::lock_guard 這是最簡單的一個管理鎖的物件。只有構造和解構函式,在構造的時候加鎖,析構的時候解鎖,解鎖後就不能使用該物件再加鎖了。可以避免使用std::mutex時忘記解鎖的情況,同時可以方便處理異常。 簡單的例項: #include <iostrea

c++ 常用功能進行封裝

原始碼路徑: (1) 字元編碼 (2) 類似 golang 中的 channel 的 模擬實現 (3) 配置檔案讀寫 (4) 基於 libcurl 的 http 客戶端(支援 https) (5) 資料庫連線池 (6) excel 檔案的處理 (7) 基

C/C++常用的字串處理函式

一、            字元處理函式 1.        字元處理函式:<ctype.

stl的常用幾個容器的介紹與特點。

<1>vector容器     vector容器是一個動態陣列的結構,在記憶體中有一個指標指向一塊連續的記憶體。類似陣列結構一樣。它的特點支援隨機訪問資料,因為其在記憶體中的單元是連續。如此之外,還可以vector的大小是可以自動增長的。當向一個vector中繼續

linux C 常用的字串操作函式

<script>window._bd_share_config={"common":{"bdSnsKey":{},"bdText":"","bdMini":"2","bdMiniList":false,"bdPic":"","bdStyle":"0","bdSi

C常用巨集定義

寫好C語言,漂亮的巨集定義很重要,使用巨集定義可以防止出錯,提高可移植性,可讀性,方便性 等等。下面列舉一些成熟軟體中常用得巨集定義。。。。。。 1,防止一個頭檔案被重複包含         #ifndef COMDEF_H         #define COMDEF_H           //標頭檔

C#常用的字串處理函式

字串常用的處理函式:    string s="csdn1+csdn2-csdn3"; 1.Substring 擷取 Substring(int Startindex)  Substring(int Startindex,int length)//過載方法  string

【unity3d-C#學習筆記】C#常用的資料結構及遍歷方法

常用的集合類:ArrayList,Queue,Stack,SortedList,Hashtable 陣列: Array: 1.資料儲存在連續的記憶體上。 2.陣列的語速都是同類型的。 3.陣列

c++std::bind與std::function

最近在看《深入應用c++11》的時候遇到了std::bind的一些新用法,之前沒有遇到過,這裡記錄下。通常時候std::bind是與std::function一起結合使用的,std::bind是一個函式模板,而std::function是一個類模板,這個從其原始

C#常用的幾種讀取XML檔案的方法

 XML檔案是一種常用的檔案格式,例如WinForm裡面的app.config以及Web程式中的web.config檔案,還有許多重要的場所都有它的身影。Xml是Internet環境中跨平臺的,依賴於內容的技術,是當

C++while(std::cin >> value)的思考

例子: #include <iostream> int main() { int sum = 0, value = 0; while(std::cin >> value) { sum += value;

c++常用的計算程式執行時間的方法

方法1: 計時函式是clock(),而與其相關的資料型別是clock_t(標頭檔案是time.h)。函式定義原型為:clock_t clock(void); 這個函式返回從“開啟這個程式程序”到“程式中呼叫clock()函式”時之間的CPU時鐘計時單元(clock t

學習一下C#常用集合和陣列的區別,雖然很基礎,但感覺很實用

在C#中,當我們想要儲存一組物件的時候,就會想到用陣列,ArrayList,List這三個物件了。那麼這三者到底有什麼樣的區別呢?我們先來了解一下陣列,因為陣列在C#中是最早出現的。 陣列 陣列有很多的優點,比如說陣列在記憶體中是連續儲存的,所以它的索引速度是非常的快,而且

C++常用小函式(刷演算法題必備)

int a = 123; string str1 = to_string(a);////將整數轉化為string型別 string str = "abcdefg"; //str.substr(1

c++常用排序方法

排序方法:(由小到大) 氣泡排序:即整個過程將最值像冒泡一樣浮現。對於給定的n個記錄,從第一個記錄開始將相鄰兩個記錄進行比較,如果前者大於後者,將他們交換位置(見紅色部分),當i=0,j進行全體遍歷後,可以獲得這組記錄的最大值,並將其處於最後陣列的最後一個;i=1,j進行前