(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(); //隊列中最大元素的引用
容器的選擇
- vector的特點
- 指定一塊如同數組一樣的連續存儲,但空間可以動態擴展。它可以像數組一樣操作,并可以動態操作。通常體現在push_back()和pop_back()
- 隨機訪問方便,像數組一樣被訪問
- 節省空間,因連續存儲,在存儲數據的區域都是沒有被浪費的,但是要明確一點,vector大多情況下并不是滿存的,在未存儲的區域實際是浪費
- 在內部進行插入、刪除操作效率非常低,基本上是被禁止的。vector被設計成只能在后端進行追加和刪除操作,原因是vector內部的實現是按照順序表的原理
- 只能在vector的最后進行push和pop,不能在頭進行這個操作
- 動態添加的數據超過vector默認分配的大小時,需要對內存重新分配、拷貝和釋放,這個操作消耗性能。達到最佳性能,最好創建vector時指定空間大小
- list的特點
- 不使用連續的內存空間,可以隨意進行動態操作
- 可以再內部任何位置快速的插入和刪除,可在兩端進行push和pop
- 不能進行內部的隨機訪問
- 比vector占用更多內存
- deque的特點
- 隨機訪問方便,支持[ ]操作符,性能沒有vector好
- 可在內部進行插入和刪除操作,性能不及list
- 可以在兩端進行push和pop
- vector/list/deque三者比較
vector查詢性能最好,在末端增加數據的性能也好,除非它重新申請內存段;適合高效的隨機存儲。
list的插入和刪除元素的性能最好,查詢性能差;適合大量的插入和刪除操作且不關心隨機存儲的需求。
deque介于兩者之間,比list更好查詢,比vector更好插入和刪除。如果用戶需要隨機存儲又關心兩端數據的插入和刪除,deque最佳。
Reference
- 聚慕課教育研發中心 編著,C++從入門到項目實踐(超值版),清華大學出版社
- 劉蕾編著,21天學通C++(第五版),電子工業出版社