深入浅出之容器
一、容器的定義
容器是用來管理某一類對象的集合。在C++中屬于標準模板庫(STL)。C++標準模板庫里提供了3類容器:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
容器的優點
???????1)容器類是一種對特定代碼重用問題的良好的解決方案。
???????2)可以自行擴展。當不知道需要存儲多少對象時,就不知道應當開辟多大內存空間,而容器不需要預先設定空間長度,只需要創建一個對象并合理調用其提供的方法,其余的細節則由它自身完成,它自己申請內存或釋放內存,并使用最優算法執行所有命令。
???????3)容器類自動申請和釋放內存,因此無需進行new和delete操作。
?
二、順序容器
C++標準模板庫里提供有3種順序容器,即vector,list和deque。其中vector與deque是以數組為基礎的,list是以鏈表為基礎的。
2.1 向量vector容器
2.1.1、什么是vector?
向量(Vector)是一個封裝了動態大小數組的順序容器(Sequence Container)。跟任意其它類型容器一樣,它能夠存放各種類型的對象。可以簡單的認為,向量是一個能夠存放任意類型的動態數組。?
2.1.2、特性
1.順序序列
順序容器中的元素按照嚴格的線性順序排序。可以通過元素在序列中的位置訪問對應的元素。
2.動態數組
支持對序列中的任意元素進行快速直接訪問,甚至可以通過指針算述進行該操作。操供了在序列末尾相對快速地添加/刪除元素的操作。
3.能夠感知內存分配器的(Allocator-aware)
容器使用一個內存分配器對象來動態地處理它的存儲需求。?
2.1.3、基本函數實現
1.構造函數 vector():創建一個空vector vector(int nSize):創建一個vector,元素個數為nSize vector(int nSize,const t& t):創建一個vector,元素個數為nSize,且值均為t vector(const vector&):復制構造函數 vector(begin,end):復制[begin,end)區間內另一個數組的元素到vector中2.增加函數 void push_back(const T& x):向量尾部增加一個元素X iterator insert(iterator it,const T& x):向量中迭代器指向元素前增加一個元素x iterator insert(iterator it,int n,const T& x):向量中迭代器指向元素前增加n個相同的元素x iterator insert(iterator it,const_iterator first,const_iterator last):向量中迭代器指向元素前插入另一個相同類型向量的[first,last)間的數據3.刪除函數 iterator erase(iterator it):刪除向量中迭代器指向元素 iterator erase(iterator first,iterator last):刪除向量中[first,last)中元素 void pop_back():刪除向量中最后一個元素 void clear():清空向量中所有元素4.遍歷函數 reference at(int pos):返回pos位置元素的引用 reference front():返回首元素的引用 reference back():返回尾元素的引用 iterator begin():返回向量頭指針,指向第一個元素 iterator end():返回向量尾指針,指向向量最后一個元素的下一個位置 reverse_iterator rbegin():反向迭代器,指向最后一個元素 reverse_iterator rend():反向迭代器,指向第一個元素之前的位置5.判斷函數 bool empty() const:判斷向量是否為空,若為空,則向量中無元素6.大小函數 int size() const:返回向量中元素的個數 int capacity() const:返回當前向量張紅所能容納的最大元素值 int max_size() const:返回最大可允許的vector元素數量值7.其他函數 void swap(vector&):交換兩個同類型向量的數據 void assign(int n,const T& x):設置向量中第n個元素的值為x void assign(const_iterator first,const_iterator last):向量中[first,last)中元素設置成當前向量元素8.看著清楚 1.push_back 在數組的最后添加一個數據 2.pop_back 去掉數組的最后一個數據 3.at 得到編號位置的數據 4.begin 得到數組頭的指針 5.end 得到數組的最后一個單元+1的指針 6.front 得到數組頭的引用 7.back 得到數組的最后一個單元的引用 8.max_size 得到vector最大可以是多大 9.capacity 當前vector分配的大小 10.size 當前使用數據的大小 11.resize 改變當前使用數據的大小,如果它比當前使用的大,者填充默認值 12.reserve 改變當前vecotr所分配空間的大小 13.erase 刪除指針指向的數據項 14.clear 清空當前的vector 15.rbegin 將vector反轉后的開始指針返回(其實就是原來的end-1) 16.rend 將vector反轉構的結束指針返回(其實就是原來的begin-1) 17.empty 判斷vector是否為空 18.swap 與另一個vector交換數據2.1.4、基本用法
#include < vector> using namespace std;2.1.5、簡單介紹
Vector<類型>vi(I,i+2);//得到i索引值為3以后的值
2.2 列表list容器
列表list的特點:
1)雙向鏈表組成。
?2)其數據由若干個節點構成,每個節點包括一個信息塊(即實際存儲的數據)、一個前驅指針和一個后驅指針。可以向前也可以向后訪問,但不能隨機訪問;
3)根據其結構可知隨機檢索的性能很差,vector是直接找到元素的地址,而它需要從頭開始按順序依次查找,因此檢索靠后的元素時非常耗時;
?4)可以不使用連續的內存空間,這樣可以隨意進行動態操作;
5)可以在內部的任意位置快速插入或刪除,也可以在兩端進行操作;
6)? 不能進行內部隨機訪問,即不支持【】操作和vector.at();
7)相對于vector要占用更多內存。
//初始化 list<int> lst1; //創建空list list<int> lst2(3); //創建含有三個元素的list list<int> lst3(3,2); //創建含有三個元素的值為2的list list<int> lst4(lst2); //使用lst2初始化lst4//常用的操作方法 lst1.assign(lst2.begin(),lst2.end()); //分配值 lst1.push_back(10); //添加值 lst1.pop_back(); //刪除末尾值 lst1.begin(); //返回首值的迭代器 lst1.end(); //返回末尾位置下一位的迭代器 lst1.clear();//清空值 bool isEmpty1 = lst1.empty(); //判斷為空 lst1.erase(lst1.begin(),lst1.end()); //刪除元素 lst1.front(); //返回第一個元素的引用 lst1.back(); //返回最后一個元素的引用 lst1.insert(lst1.begin(),3,2); //從指定位置插入3個值為2的元素 lst1.rbegin(); //返回第一個元素的前向指針 lst1.remove(2); //相同的元素全部刪除 lst1.reverse(); //反轉 lst1.size(); //含有元素個數 lst1.sort(); //排序 lst1.unique(); //刪除相鄰重復元素 #include <QCoreApplication> #include <list> using namespace std; int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);//! 創建一個列表list<int> link;//! 增加元素for (int i=0;i<10;i++){link.push_front(i);}//! 讀取元素list<int>::iterator p = link.begin();while(p != link.end()){qDebug("%d",*p);p++;}//! 刪除一個元素link.remove(5);p = link.begin();while(p != link.end()){qDebug("%d",*p);p++;}return a.exec(); }?2.3 雙端隊列deque容器
?雙端隊列deque類由雙端隊列組成,允許在隊列兩端進行操作。雙端隊列支持隨機訪問迭代器,也支持下標進行操作【】進行訪問。
#include <QCoreApplication> #include <queue> using namespace std; int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);//! 創建一個雙端隊列deque<int> d;//! 初始化d.push_back(1);d.push_back(2);d.push_back(3);d.push_front(10);//d.insert(d.begin()+1, 10);//! 讀取內容for(int i = 0; i < d.size(); i ++)qDebug("%d",d[i]);deque<int>::iterator it;for(it = d.begin(); it != d.end(); it ++)qDebug("%d",*it);deque<int>::reverse_iterator rit;for(rit = d.rbegin(); rit != d.rend(); rit ++)qDebug("%d",*rit);return a.exec(); } 輸出結果是: 10 1 2 3 10 1 2 3 3 2 1 10三、關聯容器
關聯容器能通過關鍵字直接訪問。
3.1 映射(map)
映射map就是標準模板庫中的一個關聯容器,它提供一對一的數據處理能力。map的元素是由key和value兩個分量組成的對偶(key,value)。元素的鍵key是唯一的,給定一個key,就能唯一地確定與其相關聯的另一個分量value。
例如:map(string,string)mapstu;
1.使用map要包含頭文件 #include<map>
2.映射(map)和多重映射(multimap)是基于某一類新key的鍵集的存在,提供對TYPE類型的數據進行快速和高效的檢索。multimap允許鍵值重復,map不允許。鍵和值的數據類型是不相同的。
3.map的構造函數和析構函數????????
map c???????產生一個空的map/multimap,其中不含任何元素 map c(op)???????以op為排序準則,產生一個空的map/multimap map c1(c2)??????產生某個map/multimap的副本,所有元素均被復制 map c(beg,end)???????以區間[beg,end]內的元素產生一個map/multimap map c(beg,end,op)?????以op為排序準則,利用[beg,end]內的元素生成一個map/multimap c.~map()??????銷毀所有元素,釋放內存 以上map可以是以下形式 map<key,elem>????????一個map,以less<>(operator<)為排序準則 map<key,elem,op>?????一個map,以op為排序準則 multimap<key,elem>????一個multimap,以less<>(operator<)為排序準則 multimap<key,elem,op>?????一個multimap,以op為排序準則4.map的主要成員函數???????
iterator begin()???????返回指向第一個元素的迭代器 iterator end()???????返回指向末尾的迭代器(最后一個元素之后) void clear()?????????清空容器 bool empty()???????判斷是否為空 insert(pair<key,value> &elem)????????插入一個pair類型的元素,返回插入元素的位置 iterator insert(iterator pos,pair<key,value> &elem)?????????插入一個pair類型的元素,返回插入元素的位置,pos是插入操作的搜尋起點 void insert(iterator start,iterator end)?????????插入[start,end)之間的元素到容器中 void erase(iterator loc)?????????刪除loc所指元素 void erase(iterator start,iterator end)???????刪除[start,end)之間的元素 size_type erase(constkeytype &key)???????刪除key值為key的元素,并返回被刪除元素的個數 iterator find(const keytype &key)????????返回一個迭代器,指向鍵值為key的元素,如果沒有找到返回end() size_type count(const keytype &key)???返回鍵值等于key的元素的個數 size_type size()????????返回元素的個數 void swap(map &from)????????交換兩個map中的元素4.元素的訪問
??map<string,float>::iterator pos;
?
3.2 集合(set)
關于set,必須說明的是set關聯式容器。set作為一個容器也是用來存儲同一數據類型的數據類型,并且能從一個數據集合中取出數據,在set中每個元素的值都唯一,而且系統能根據元素的值自動進行排序。應該注意的是set中數元素的值不能直接被改變。C++ STL中標準關聯容器set, multiset, map, multimap內部采用的就是一種非常高效的平衡檢索二叉樹:紅黑樹,也成為RB樹(Red-Black Tree)。RB樹的統計性能要好于一般平衡二叉樹,所以被STL選擇作為了關聯容器的內部結構。
?關于set有下面幾個問題:
(1)為何map和set的插入刪除效率比用其他序列容器高?
大部分人說,很簡單,因為對于關聯容器來說,不需要做內存拷貝和內存移動。說對了,確實如此。set容器內所有元素都是以節點的方式來存儲,其節點結構和鏈表差不多,指向父節點和子節點。結構圖可能如下:
A
? / \
B C
/ \ / \
? D E F G
因此插入的時候只需要稍做變換,把節點的指針指向新的節點就可以了。刪除的時候類似,稍做變換后把指向刪除節點的指針指向其他節點也OK了。這里的一切操作就是指針換來換去,和內存移動沒有關系。
(2)為何每次insert之后,以前保存的iterator不會失效?
iterator這里就相當于指向節點的指針,內存沒有變,指向內存的指針怎么會失效呢(當然被刪除的那個元素本身已經失效了)。相對于vector來說,每一次刪除和插入,指針都有可能失效,調用push_back在尾部插入也是如此。因為為了保證內部數據的連續存放,iterator指向的那塊內存在刪除和插入過程中可能已經被其他內存覆蓋或者內存已經被釋放了。即使時push_back的時候,容器內部空間可能不夠,需要一塊新的更大的內存,只有把以前的內存釋放,申請新的更大的內存,復制已有的數據元素到新的內存,最后把需要插入的元素放到最后,那么以前的內存指針自然就不可用了。特別時在和find等算法在一起使用的時候,牢記這個原則:不要使用過期的iterator。
(3)當數據元素增多時,set的插入和搜索速度變化如何?
如果你知道log2的關系你應該就徹底了解這個答案。在set中查找是使用二分查找,也就是說,如果有16個元素,最多需要比較4次就能找到結果,有32個元素,最多比較5次。那么有10000個呢?最多比較的次數為log10000,最多為14次,如果是20000個元素呢?最多不過15次。看見了吧,當數據量增大一倍的時候,搜索次數只不過多了1次,多了1/14的搜索時間而已。你明白這個道理后,就可以安心往里面放入元素了。
2.set中常用的方法
begin()???? ,返回set容器的第一個元素end() ,返回set容器的最后一個元素clear()?? ??? ?,刪除set容器中的所有的元素empty() ,判斷set容器是否為空max_size() ,返回set容器可能包含的元素最大個數size() ,返回當前set容器中的元素個數rbegin ,返回的值和end()相同rend() ,返回的值和rbegin()相同 #include <QCoreApplication> #include <set> #include <string> #include <iostream> using namespace std; int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);set<int>s;int n = 10;for(int i=1;i<=n;i++){s.insert(i);}set<int>::iterator it;for(it=s.begin ();it!=s.end ();it++){printf("%d\n",*it);}//s.end()沒有值cout<<"s.begain() "<<*s.begin ()<<endl;//lower_bound()--返回指向大于(或等于)某值的第一個元素的迭代器cout<<"lower_buond 3 "<<*s.lower_bound (3)<<endl;//upper_bound()--返回大于某個值元素的迭代器cout<<"upper_bound 3 "<<*s.upper_bound (3)<<endl;//find()--返回一個指向被查找到元素的迭代器cout<<"find() 3 "<<*s.find (3)<<endl;cout<<"s.size() "<<s.size ()<<endl;return a.exec(); }四、容器適配器
五、迭代器
迭代器(iterators)是STL的一個重要組成部分。每個容器都有自己的迭代器,可以把迭代器看做一個容器使用的特殊指針,可以用來存取容器內存儲的數據。
格式:<容器名> <數據類型>iterator 迭代器變量
迭代器有5種,分別是輸入、輸出、向前、雙向和隨機存取。
1) 輸入迭代器:只能向前移動,每次只能移動一步,只能讀迭代器指向的數據,而且只能讀一次。
2)輸出迭代器:只能向前移動,每次只能移動一次,只能寫迭代器指向的數據,而且只能寫一次。
3)前向迭代器:不僅具有輸入和輸出功能,還具有多次讀或者寫功能;
4) 雙向迭代器:以前向迭代器為基礎加上向后移動的能力;
5)隨機訪問迭代器:為雙向迭代器加上迭代器運算的能力,即有向前或向后跳轉一個任意的距離的能力。
迭代器是一種檢查容器內元素并遍歷元素的數據類型。C++更趨向于使用迭代器而不是下標操作,因為標準庫為每一種標準容器(如vector)定義了一種迭代器類型,而只用少數容器(如vector)支持下標操作訪問容器元素。
5.1?.定義和初始化
?每種容器都定義了自己的迭代器類型,如vector:vector<int>::iterator iter; //定義一個名為iter的變量
?每種容器都定義了一對名為begin和en的函數,用于返回迭代器。下面對迭代器進行初始化操作:vector<int> ivec;
vector<int>::iterator iter1=ivec.bengin(); //將迭代器iter1初始化為指向ivec容器的第一個元素
vector<int>::iterator iter2=ivec.end(); //將迭代器iter2初始化為指向ivec容器的最后一個元素的下一個位置
注意end并不指向容器的任何元素,而是指向容器的最后元素的下一位置,稱為超出末端迭代器。如果vector為空,則begin返回的迭代器和end返回的迭代器相同。一旦向上面這樣定義和初始化,就相當于把該迭代器和容器進行了某種關聯,就像把一個指針初始化為指向某一空間地址一樣。
5.2?.常用操作
iter+n //在迭代器上加(減)整數n,將產生指向容器中錢前面(后面)第n個元素的迭代器。新計算出來的迭代器必須指向容器中的元素或超出容器末端的下一個元素 iter-niter1+=iter2 //將iter1加上或減去iter2的運算結果賦給iter1。兩個迭代器必須指向容器中的元素或超出容器末端的下一個元素 iter1-=iter2iter1-iter2 //兩個迭代器的減法,得出兩個迭代器的距離。兩個迭代器必須指向容器中的元素或超出容器末端的下一個元素>,>=,<,<= //元素靠后的迭代器大于靠前的迭代器。兩個迭代器必須指向容器中的元素或超出容器末端的下一個元素 *iter //對iter進行解引用,返回迭代器iter指向的元素的引用 iter->men //對iter進行解引用,獲取指定元素中名為men的成員。等效于(*iter).men ++iter //給iter加1,使其指向容器的下一個元素 iter++ --iter //給iter減1,使其指向容器的前一個元素 iter-- iter1==iter2 //比較兩個迭代器是否相等,當它們指向同一個容器的同一個元素或者都指向同同一個容器的超出末端的下一個位置時,它們相等 iter1!=iter25.3、迭代器const_iterator
每種容器還定義了一種名為const_iterator的類型。該類型的迭代器只能讀取容器中的元素,不能用于改變其值。之前的例子中,普通的迭代器可以對容器中的元素進行解引用并修改,而const_iterator類型的迭代器只能用于讀不能進行重寫。例如可以進行如下操作:
for(vector<int>::const_iterator iter=ivec.begin();iter!=ivec.end();++iter)cout<<*iter<<endl; //合法,讀取容器中元素值for(vector<int>::const_iterator iter=ivec.begin();iter!=ivec.end();++iter)*iter=0; //不合法,不能進行寫操作const_iterator和const? iterator是不一樣的,后者對迭代器進行聲明時,必須對迭代器進行初始化,并且一旦初始化后就不能修改其值。這有點像常量指針和指針常量的關系。例如:
vector<int> ivec(10); const vector<int>::iterator iter=ivec.begin(); *iter=0; //合法,可以改變其指向的元素的值 ++iter; //不合法,無法改變其指向的位置5.4 例子
#include <QCoreApplication> #include <vector> using namespace std; int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);//! 創建一份向量存儲容器intvector<int> obj;//! 通過push_back在數組后面添加數據for (int i= 0; i < 10; i++){obj.push_back(i);}//! 通過pop_back去除數組最后5個數據for (int i = 0; i < 5; i++){obj.pop_back();} // //! size容器中實際數據個數 // for (int i = 0; i < obj.size(); i++){ // qDebug("%d",obj[i]); // }for(vector<int>::iterator iter=obj.begin();iter != obj.end();++iter){qDebug("%d",*iter);}return a.exec(); } 輸出結果是: 0 1 2 3 46.?STL標準容器的選擇
1. 除非有很好的理由選擇其他容器, 否則應使用 vector.
2.如果有很多小的元素, 且空間的額外開銷很重要, 則不要使用 list 或 forward_list.
3.如果程序要求隨機訪問元素, 應使用?vector 或 deque.
4.如果程序需要在頭尾?插入/刪除?元素,且不會在中間插入元素, 則使用?deque.
5.如果需要在中間插入元素, 應使用?list 或 forwar_list.
6.如果程序需要在中間插入元素, 且隨后需要隨機訪問元素, 則可以考慮在輸入時使用 list, 再拷貝到 vector 中
7.set 的 插入/刪除 查找效率非常高, 但不支持隨機訪問
PS.如果實在不確定使用哪種元素, 那么可以在程序中只使用 vector 與list.
總結
- 上一篇: 深入浅出之文件操作
- 下一篇: 深入浅出之string