C++容器和STL, since 2020-12-08

(2020.12.08 Tues)
STL容器允許重復利用已有的實現構造自己特定類型下的數據結構,通過設置一些模板類,這些模板的參數允許用戶指定容器中元素的數據類型,從而提高編程效率。

(容器部分的內容可以對比python中的list, tuple, dict, set等數據結構。)
容器主要由頭文件<vector>, <list>, <deque>, <set>, <map>, <stack>和<queue>組成。

數據結構 描述 實現頭文件
向量(vector) 連續存儲的元素 <vector>
列表(list) 由節點組成的雙向鏈表,每個結點包含著一個元素 <list>
雙隊列(deque) 連續存儲的指向不同元素的指針所組成的數組 <deque>
集合(set) 由節點組成的紅黑樹,每個結點都包含著一個元素,節點之間以某種作用于元素對的謂詞排列,沒有兩個不同的元素能夠擁有相同的次序 <set>
多重集合(multiset) 允許存在兩個次序相等的元素的集合 <set>
棧(stack) 后進先出的值的排列 <stack>
隊列(queue) 先進先出的值的排列 <queue>
優先隊列(priority queue) 元素的次序是由作用于所存儲的值對上的某種謂詞決定的一種隊列 <queue>
映射(map) 由{鍵,值}對組成的集合,以某種作用于鍵對上的謂詞排列 <map>
多重映射(multimap) 允許鍵對有相等的次序的映射 <map>

(2020.12.09 Wed)
STL定義的通用容器分為順序容器(sequence container)關聯容器(associative container)容器適配器

順序容器

順序容器是一種各元素間有順序關系的線性表,是線性結構的可序群集,其中的每一個元素有固定的位置,除非用刪除或插入的操作改變變量的位置。順序容器具有插入速度快但查找操作相對較慢的特征。C++ STL(標準模板庫)提供3中順序容器:vector、list和deque。vector和deque類時以數組為基礎,list類是以雙向鏈表為基礎。

順序容器中各種類型的比較

vector是動態順序容器,有連續內存地址的數據結構。相比于數組,vector會消耗更多的內存以有效地動態增長。而相比于其他順序容器(deques, list),vector能更快的索引元素(如同數組一樣),而且能相對高效的在尾部插入和刪除元素。在其他位置刪除和插入元素,效率沒有這些容器高。

list是STL實現的雙向鏈表,相比vector的連續線性空間,list復雜太多,它允許快速的插入和刪除,但是隨機訪問卻比較慢。它的數據有若干個節點構成,每個節點包括一個信息塊、一個前驅指針和一個后驅指針,可以向前或向后進行訪問,但不能隨機訪問。好處是每次插入或刪除會配置或者釋放一個元素空間,對空間的運用絕對精準。

deque(double ended queues雙向隊列)和vector相似,但它允許在容器頭部快速插入和刪除,如同在尾部一樣。

向量vector

使用vector的時候,需包含頭文件: <vector>,一般還要加上'using namespace std;',如果不加,調用的時候必須加std::vector<..>這樣的形式,加了std::表示運用的是std命令空間的vector容器。
向量聲明和初始化

語句 作用
vector<元素類型>向量對象名; 創建一個沒有任何元素的空向量對象
vector<元素類型>向量對象名(size); 創建一個大小為size的向量對象
vector<元素類型>向量對象名(n,初始值); 創建一個大小為n的向量對象,并初始化
vector<元素類型>向量對象名(begin, end); 創建一個向量對象,初始化該對象(begin, end)中的元素
vector<int> a;
vector<float> a(10); //初始大小為10的向量
vector<float> a(10,1);//初始大小為10,初始值都是1的向量
vector<int> b(a); //用向量a初始化向量b
vector<int> b(a.begin(), a.begin()+3); //將a向量中第0到第2個作為向量b的初始值
//可以用數組初始化向量
int n[] = {1,2,3,4,5};
vector<int> a(n,n+5); //n的前5個元素作為a的初值
vector<in> a(&n[1], &n[4]); //n[1]到n[4]范圍內的元素作為向量a的初值

元素的輸入和訪問

#include <iostream>
#include <vector>
using namespace std;
int main() 
{
    vector<int> a(10,1);
    int i;
    cout << 'initialisaiton:' << endl;
    cout << 'insert data:' << endl;
    cin >> a[2];
    cin >> a[5];
    cin >> a[8];
    cout << '賦值后遍歷:'<<endl;
    for (i = 0; i < a.size(); i++)
    { 
        cout<<a[i]<<endl; //以a[n]的方式訪問向量的第n個元素
    }
}

基本操作

語句 作用
a.insert(position, value) 將數值的一個拷貝插入到有position指定的位置上,并返回新元素的位置
a.insert(position,n,value) 將數值的n個拷貝插入到position指定的位置上
a.insert(position,beg,end) 將從beg到end-1之間的所有元素的拷貝插入到a中由position指定的位置上
a.push_back(value) 在尾部插入
a.pop_back() 刪除最后元素
a.resize(num) 將元素個數改為num
a.resize(num,value) 將元素個數改為num,如果size()增加,默認的構造函數將這些新元素初始化(用value?)
a.clear() 刪除容器中所有元素
a.erase(position) 刪除由position指定的位置上的元素
a.erase(beg, end) 刪除beg到end-1之間的所有元素
a.empty() 判斷是否為空
a.size(), a.max_size() 向量a的大小和最大容量
a.capacity() 向量a的真實大小

以迭代器訪問向量

vector<int> a(10,5);
vector<int>::iterator iter;
for (iter = a.begin(); it != a.end(); iter++)
{
   cout<<*iter<<endl;
}

因為迭代器是一個定義在vector類中的typedef,所以必須使用容器名vector、容器元素類型和作用域符來使用iterator。

迭代器是一個指針,用來存取容器中的數據元素,因此迭代器的操作和指針的操作相同。

二維向量
vector<vector<int>> a(3,vector<int>(4)); //相當于數組a[3][4],都是空
vector<vector<int>> a(3, vector<int>(4,0)); //相當于a[3][4],都是0
for(int m = 0; m < a.size(); m++) //a.size()獲取行向量大小
{
    for (int n = 0; n < a[3].size(); n++) //a[n].size()獲取向量中具體每個向量的大小
    {
        cout<<a[m][n]<<endl;
     }
}

列表list

STL實現的雙向鏈表,相比vector的連續線性空間,list復雜太多,它允許快速的插入和刪除,但是隨機訪問卻比較慢。它的數據有若干個節點構成,每個節點包括一個信息塊、一個前驅指針和一個后驅指針,可以向前或向后進行訪問,但不能隨機訪問。好處是每次插入或刪除會配置或者釋放一個元素空間,對空間的運用絕對精準。列表的定義在頭文件<list>中。
其定義方式和vector相似

list<int> lst1;
list<int> lst2(3);
list<int> lst3(3,2); //三個元素的list,初始值都是2
list<int> lst4(lst2); 
list<int> lst5(lst.begin(), lst.end()); //用lst初始化lst5
lst.push_front(8); //頭部加入元素
lst.pop_front(); //頭部元素刪除
lst.push_back(xx); //尾部加入元素
lst.pop_back(); //尾部元素刪除

遍歷list

iterator begin(); //返回指向第一個元素的迭代器
iterator end();
reverse_iterator rbegin(); //返回指向第一個元素的逆向迭代器
reverse_iterator rend();
// 如下
for (list<int>::reverse_iterator rit = lst.rbegin(); rit != lst.rend(); ++rit)
{
    cout<<**rit<<endl;
}

列表容器的操作

lst.empty(); //lst為空時返回true
lst.size(); //返回list中元素的個數
lst.max_size(); //返回lst最大能容納的元素個數
//為容器添加新內容
lst.assign(InputIterator first, InputIterator last);
lst.assign(size_type a, const value_type& val); //給lst賦值n個值為val的元素
//
list<int> lst;
list<int> lst2;
list<int> lst3;
lst.assign(5,10); //添加5元素,值為10
lst2.assign(lst.begin(), lst.end()); //用lst為lst2賦值
int a[]={1,2,3,4};
lst3.assign(a,a+4); //將a的元素都賦值給lst3

插入元素
(2020.12.10 Thur)
list中插入元素需要成員函數insert()實現。分為若干版本。

iterator insert(iterator position, const value& val); 
//position是要插入這個list的迭代器,val是要插入的值,比如下面這個例子
list<int> lst;
list<int>::iterator it;
it = lst.begin();
lst.inset(it, 9);
iterator insert(iterator position, size_type n, const value& val);
//插入n個值為val的元素
lst.insert(it, 2, 29); //在迭代器指針it所指的位置加入2個29
template <class InputIterator>;
void insert (iterator position, InputIterator first, InputIterator last);
//first和last是選擇的把值插入到這個list中的值所在的容器的迭代器
list<int> lst;
list<int>::iterator it;
vector<int> v(2,39);
lst.insert(it, v.begin(), v.end());

刪除元素

iterator erase(iterator position);
iterator erase(iterator first, iterator last); //刪除[first, last)中的值,可以不用變量接收其返回值
list<int> lst(5,10);
list<int>::iterator it1,it2;
it1 = lst.begin();
it2 = lst.end();
it1 = lst.erase(it1);
lst.erase(it1, it2);
lst.erase(lst.begin(), lst.end()); //清空lst

雙隊列deque

deque(double ended queue)雙向隊列,和vector相似,但是它允許在容器頭部快速插入和刪除,就像在尾部一樣。
deque結合了vector和list的優缺點,優化了的對序列兩端元素進行添加和刪除操作的基本序列容器。

它允許較為快速的隨機訪問,但不像vector那樣把所有的對象保存在一個連續的內存塊,而是采用多個連續的存儲塊,并且在一個映射結構中保存對這些塊及其順序的跟蹤。向deque兩端添加或刪除元素的開銷很小,不需要重新分配空間,所以向末端添加元素比vector更高效。

關聯容器

關聯容器中的元素通過關鍵字來保存和訪問,主要有映射(map)和集合(set)兩類,支持通過鍵來高效的查找和讀取元素。map的元素以鍵-值對(map-value)的形式組織,鍵用作元素在map類型下進行索引,而值則表示所存儲和讀取的數據。set僅包含一個鍵,并有效的支持關于某個鍵是否存在的查詢。set和map類型的對象不允許為同一個鍵添加第二個元素。如果一個鍵必須有多個實例,則需使用多重映射(multimap)或多重集合(multiset)類型,其允許多個元素擁有相同的鍵。

(2020.12.11 Fri)

映射map

map是STL的一個關聯容器,提供一對一的數據處理能力,其元素由key和value兩個分量足成的對偶(key, value)。key唯一,給定一個key就能唯一確定一個value。map的類型通常稱為關聯數組,和數組很相似,但下標是關鍵字key而非整數。map類型定義在頭文件#include <map>中。

map是有序的且不允許重復key(關鍵字)的關聯容器。有序的實現時依靠關鍵字類型中的'<'來實現。

#include <map>
map<key_type, value_type> tmpMap; //初始化,定義一個空map
map<key_type, value_type> tmpMap{
    {key1, value1},
    {key2, value2},
    ...
};   //注意結尾有個分號;
map<key_type, value_type> tmpMap(existingMap); //從已有的一個map復制過來
map<key_type, value_type> tmpMap(x, y); //x, y分別是已有map對象的迭代器范圍
map1 = map2; //直接復制
類型別名 說明
key_type 關鍵字類型
value_type pair<const key_type, mapped_type>
mapped_type 關鍵字關聯的類型
map<int, string> myMap;
myMap::value_type a; //a為pair<const int, string>類型
myMap::key_type b; //b為int類型
myMap::mapped_type c; //c為string類型
pair類型

pair標準類型被包含在頭文件#include <utility>中,一個pair保存兩個數據成員,pair是一個用來生成特定類型的模板。當創建一個pair時,用戶必須提供兩個類型名,pair的數據成員將具有對應的類型。一般來說,一個pair類型的對象,存儲的是一個鍵值對(key-value)。

pair<string, string> a; //保存兩個string
pair<string, size_t> b; //保存一個string和size_t
pair<int, vector<int>> c; //
pair<string, string> author{'hello', 'world'}; //初始化一個pair,名author,兩個初值是hello world
pair<string, string> author('hello', world'); //同上

pair的數據成員是public,成員名為first和second,用戶可以用普通的成員訪問符'.'來進行訪問。

操作 說明
pair<T1, T2> p; p的成員數據類型分別是T1和T2,默認初始化
pair<T1, T2> p(v1,v2); 同上,使用v1,v2進行初始化
pair<T1, T2> p = {v1, v2}; 等價上式
make_pair(v1, v2); 返回一個v1和v2初始化的pair,其類型由v1和v2推斷而來
p.first 返回p的first成員
p.second 返回p的second成員
p1 relop p2 執行關系運算,利用數據類型中的關系運算
p1 == p2 相等性判斷,必須first和second同時滿足要求
p1!=p2 不等判斷

pair對象也可以作為函數的返回。

map的使用

插入數據

#include <map>
map<int, string> m;
m.insert(pair<int, string>(1,'m_first')); //插入數據
m.insert(pair<int, string>(2,'m_second'));
map<int, string>::iterator iter;
for (iter = m.begin(); iter!= m.end(); iter++)
{ 
    cour<< iter->first << ' '<< iter->second<< endl;
}

插入value_type數據,value_type類型代表的是這個容器中元素的類型

map<string, string> m;
m.insert(map<string, string>::value_type('001', 'm_first')); //插入第一個value_type數據
m.insert(map<string, string>::value_type('002', 'm_second')); //插入第二個value_type數據

用key鍵為map賦值

map<string, string> m;
m['both'] = 1;
int a = m['both'];

其他指令

m.size(); //map m的數據數量
 //遍歷,正向
for (map<int, string>::iterator iter = m.begin(); iter!=m.end() ; iter++)
{
    cout<<iter->first << ' ' << iter->second<< endl;
}
 //遍歷,反向
for (map<int, string>::iterator iter = m.rbegin(); iter!=m.rend() ; iter++)
{
    cout<<iter->first << ' ' << iter->second<< endl;
}
//計數和查找
m.count(k); //m中key k的個數,0或者1
m.find(k); //找到m中的k索引的元素,并返回指向該元素的迭代器
//刪除
m.erase(k); //刪除key為k的元素,返回size_type的類型,表示刪除的元素個數
m.erase(b, e); //刪除迭代器b和e中間的元素,返回void型
m.erase(p); //刪除迭代器p指向的元素,返回void型
set類容器

set只是單純的key的集合。當只想知道一個key是否存在時,使用set容器最合適。set容器支持大多數map的操作,但不支持下標操作,沒有定義mapped_type類型。在set類型中,value_type不是pair類型,而是與key_type類型相同。set容器中存儲的key也是唯一的。

#include <set>
vector<int> ivec;
for (...)
{ ivec.push_back(...);}
set<int> iset(ivec.begin(), ivec.end());
cout<<iset.size()<<endl; //返回set的尺寸
iset.insert('the'); // 添加元素
iset.find('a');
iset.count('a');

容器適配器

用基本容器實現新的容器,用于描述更高級的數據結構。

容器適配器有三種stack, queue和priority_queue。默認情況下stack衍生自deque。queue也同樣來自deque。

種類 默認順序容器 可用順序容器 說明
stack deque vector, list, deque
queue deque list, deque 基礎容器必須提供push_front()計算
priority_queue vector vector, deque 基礎容器必須提供隨機訪問功能
#include <stack>
#include <queue> //調用queue和priority_queue時需要引入

stack的成員函數中,s.pop()是刪除棧頂元素,s.push()是在棧頂插入元素,s.top()獲得指向棧頂元素的引用。

queue的成員函數中,q.push()向隊尾插入一個元素,q.pop()從隊首刪除元素,q.front()返回指向隊首元素的引用,q.back()指向隊尾元素的引用。

priority_queue與queue的不同之處在于,包含最大值的元素位于隊首,且只能在隊首執行操作。

#include <queue>
priority_queue<int> q;
q.push(1); //插入一個元素
q.pop(); //刪除隊首的元素,即最大的元素
q.size();
q.empty();
q.top(); //隊列中最大元素的引用

容器的選擇

  1. vector的特點
    • 指定一塊如同數組一樣的連續存儲,但空間可以動態擴展。它可以像數組一樣操作,并可以動態操作。通常體現在push_back()和pop_back()
    • 隨機訪問方便,像數組一樣被訪問
    • 節省空間,因連續存儲,在存儲數據的區域都是沒有被浪費的,但是要明確一點,vector大多情況下并不是滿存的,在未存儲的區域實際是浪費
    • 在內部進行插入、刪除操作效率非常低,基本上是被禁止的。vector被設計成只能在后端進行追加和刪除操作,原因是vector內部的實現是按照順序表的原理
    • 只能在vector的最后進行push和pop,不能在頭進行這個操作
    • 動態添加的數據超過vector默認分配的大小時,需要對內存重新分配、拷貝和釋放,這個操作消耗性能。達到最佳性能,最好創建vector時指定空間大小
  2. list的特點
    • 不使用連續的內存空間,可以隨意進行動態操作
    • 可以再內部任何位置快速的插入和刪除,可在兩端進行push和pop
    • 不能進行內部的隨機訪問
    • 比vector占用更多內存
  3. deque的特點
    • 隨機訪問方便,支持[ ]操作符,性能沒有vector好
    • 可在內部進行插入和刪除操作,性能不及list
    • 可以在兩端進行push和pop
  4. vector/list/deque三者比較
    vector查詢性能最好,在末端增加數據的性能也好,除非它重新申請內存段;適合高效的隨機存儲。
    list的插入和刪除元素的性能最好,查詢性能差;適合大量的插入和刪除操作且不關心隨機存儲的需求。
    deque介于兩者之間,比list更好查詢,比vector更好插入和刪除。如果用戶需要隨機存儲又關心兩端數據的插入和刪除,deque最佳。

Reference

  1. 聚慕課教育研發中心 編著,C++從入門到項目實踐(超值版),清華大學出版社
  2. 劉蕾編著,21天學通C++(第五版),電子工業出版社
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內容