將容器類模板實例化時,會指明容器中存放的元素是什麼類型的:可以存放基本類型的變量,也可以存放對象。
對象或基本類型的變量被插入容器中時,實際插入的是對象或變量的一個複製品。
STL 中的許多算法(即函數模板),如排序、查找等算法,在執行過程中會對容器中的元素進行比較。這些算法在比較元素是否相等時通常用運算符進行,比較大小通常用<
運算符進行,因此,被放入容器的對象所屬的類最好重載==
和<
運算符,以使得兩個對象用==
和<
進行比較是有定義的。
順序容器
順序容器有以下三種:可變長動態數組 vector、雙端隊列 deque、雙向鏈表 list。
它們之所以被稱爲順序容器,是因爲元素在容器中的位置同元素的值無關,即容器不是排序的。
關聯容器
關聯容器有以下四種:set、multiset、map、multimap。關聯容器內的元素是排序的。插入元素時,容器會按一定的排序規則將元素放到適當的位置上,因此插入元素時不能指定位置。
默認情況下,關聯容器中的元素是從小到大排序(或按關鍵字從小到大排序)的,而且用<
運算符比較元素或關鍵字大小。因爲是排好序的,所以關聯容器在查找時具有非常好的性能。
棧/隊列
除了以上兩類容器外,STL 還在兩類容器的基礎上屏蔽一部分功能,突出或增加另一部分功能,實現了三種容器適配器:棧 stack、隊列 queue、優先級隊列 priority_queue。
概述
容器都是類模板。它們實例化後就成爲容器類。用容器類定義的對象稱爲容器對象。
例如,vector<int>
是一個容器類的名字,vector<int> a;
就定義了一個容器對象 a,a 代表一個長度可變的數組,數組中的每個元素都是 int 類型的變量;
任何兩個容器對象,只要它們的類型相同,就可以用 <、<=、>、>=、==、!= 進行詞典式的比較運算。假設 a、b 是兩個類型相同的容器對象,這些運算符的運算規則如下。
- a == b:若 a 和 b 中的元素個數相同,且對應元素均相等,則
a == b
的值爲 true,否則值爲 false。元素是否相等是用==
運算符進行判斷的。 - a<b:規則類似於詞典中兩個單詞比較大小,從頭到尾依次比較每個元素,如果發生 a 中的元素小於 b 中的元素的情況,則
a<b
的值爲 true;如果沒有發生 b 中的元素小於 a 中的元素的情況,且 a 中的元素個數比 b 少,a<b
的值也爲 true;其他情況下值爲 false。元素比較大小是通過<
運算符進行的。 - a > b:等價於 b < a。
所有容器都有以下兩個成員函數:
- int size():返回容器對象中元素的個數。
- bool empty():判斷容器對象是否爲空。
順序容器和關聯容器還有以下成員函數:
- begin():返回指向容器中第一個元素的迭代器。
- end():返回指向容器中最後一個元素後面的位置的迭代器。
- rbegin():返回指向容器中最後一個元素的反向迭代器。
- rend():返回指向容器中第一個元素前面的位置的反向迭代器。
- erase(...):從容器中刪除一個或幾個元素。該函數參數較複雜,此處省略。
- clear():從容器中刪除所有元素。
如果一個容器是空的,則 begin() 和 end() 的返回值相等,rbegin() 和 rend() 的返回值也相等。
順序容器還有以下常用成員函數:
- front():返回容器中第一個元素的引用。
- back():返回容器中最後一個元素的引用。
- push_back():在容器末尾增加新元素。
- pop_back():刪除容器末尾的元素。
- insert(...):插入一個或多個元素。該函數參數較複雜,此處省略。
迭代器(iterator)
要訪問順序容器和關聯容器中的元素,需要通過“迭代器(iterator)”進行。迭代器是一個變量,相當於容器和操縱容器的算法之間的中介。迭代器可以指向容器中的某個元素,通過迭代器就可以讀寫它指向的元素。從這一點上看,迭代器和指針類似
1) 正向迭代器,定義方法如下:容器類名::iterator 迭代器名;
2) 反向迭代器,定義方法如下:容器類名::reverse_iterator 迭代器名;
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v; //v是存放int類型變量的可變長數組,開始時沒有元素
for (int n = 0; n<5; ++n)
v.push_back(n); //push_back成員函數在vector容器尾部添加一個元素
vector<int>::iterator i; //定義正向迭代器
for (i = v.begin(); i != v.end(); ++i) { //用迭代器遍歷容器
cout << *i << " "; //*i 就是迭代器i指向的元素
*i *= 2; //每個元素變爲原來的2倍
}
cout << endl;
//用反向迭代器遍歷容器
for (vector<int>::reverse_iterator j = v.rbegin(); j != v.rend(); ++j)
cout << *j << " ";
return 0;
}
程序的輸出結果是:
0 1 2 3 4
8 6 4 2 0
第 6 行,vector 容器有多個構造函數,如果用無參構造函數初始化,則容器一開始是空的。
第 10 行,begin 成員函數返回指向容器中第一個元素的迭代器。++i 使得 i 指向容器中的下一個元素。end 成員函數返回的不是指向最後一個元素的迭代器,而是指向最後一個元素後面的位置的迭代器,因此循環的終止條件是i != v.end()
。
第 16 行定義了反向迭代器用以遍歷容器。反向迭代器進行++
操作後,會指向容器中的上一個元素。rbegin 成員函數返回指向容器中最後一個元素的迭代器,rend 成員函數返回指向容器中第一個元素前面的位置的迭代器,因此本循環實際上是從後往前遍歷整個數組。
如果迭代器指向了容器中最後一個元素的後面或第一個元素的前面,再通過該迭代器訪問元素,就有可能導致程序崩潰,這和訪問 NULL 或未初始化的指針指向的地方類似。
不同容器的迭代器的功能
1) 正向迭代器。假設 p 是一個正向迭代器,則 p 支持以下操作:++p,p++,*p。此外,兩個正向迭代器可以互相賦值,還可以用==
和!=
運算符進行比較。
2) 雙向迭代器。雙向迭代器具有正向迭代器的全部功能。除此之外,若 p 是一個雙向迭代器,則--p
和p--
都是有定義的。--p
使得 p 朝和++p
相反的方向移動。
3) 隨機訪問迭代器。隨機訪問迭代器具有雙向迭代器的全部功能。若 p 是一個隨機訪問迭代器,i 是一個整型變量或常量,則 p 還支持以下操作:
- p+=i:使得 p 往後移動 i 個元素。
- p-=i:使得 p 往前移動 i 個元素。
- p+i:返回 p 後面第 i 個元素的迭代器。
- p-i:返回 p 前面第 i 個元素的迭代器。
- p[i]:返回 p 後面第 i 個元素的引用。
此外,兩個隨機訪問迭代器 p1、p2 還可以用 <、>、<=、>= 運算符進行比較。p1<p2
的含義是:p1 經過若干次(至少一次)++
操作後,就會等於 p2。其他比較方式的含義與此類似。
對於兩個隨機訪問迭代器 p1、p2,表達式p2-p1
也是有定義的,其返回值是 p2 所指向元素和 p1 所指向元素的序號之差(也可以說是 p2 和 p1 之間的元素個數減一)。
下面的程序中,每個循環演示了一種做法。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(100); //v被初始化成有100個元素
for(int i = 0;i < v.size() ; ++i) //size返回元素個數
cout << v[i]; //像普通數組一樣使用vector容器
vector<int>::iterator i;
for(i = v.begin(); i != v.end (); ++i) //用 != 比較兩個迭代器
cout << * i;
for(i = v.begin(); i < v.end ();++i) //用 < 比較兩個迭代器
cout << * i;
i = v.begin();
while(i < v.end()) { //間隔一個輸出
cout << * i;
i += 2; // 隨機訪問迭代器支持 "+= 整數" 的操作
}
}
迭代器的輔助函數
STL 中有用於操作迭代器的三個函數模板,它們是:
- advance(p, n):使迭代器 p 向前或向後移動 n 個元素。
- distance(p, q):計算兩個迭代器之間的距離,即迭代器 p 經過多少次 + + 操作後和迭代器 q 相等。如果調用時 p 已經指向 q 的後面,則這個函數會陷入死循環。
- iter_swap(p, q):用於交換兩個迭代器 p、q 指向的值。
下面的程序演示了這三個函數模板的 用法。
#include <list>
#include <iostream>
#include <algorithm> //要使用操作迭代器的函數模板,需要包含此文件
using namespace std;
int main()
{
int a[5] = { 1, 2, 3, 4, 5 };
list <int> lst(a, a+5);
list <int>::iterator p = lst.begin();
advance(p, 2); //p向後移動兩個元素,指向3
cout << "1)" << *p << endl; //輸出 1)3
advance(p, -1); //p向前移動一個元素,指向2
cout << "2)" << *p << endl; //輸出 2)2
list<int>::iterator q = lst.end();
q--; //q 指向 5
cout << "3)" << distance(p, q) << endl; //輸出 3)3
iter_swap(p, q); //交換 2 和 5
cout << "4)";
for (p = lst.begin(); p != lst.end(); ++p)
cout << *p << " ";
return 0;
}
程序的輸出結果是:
1) 3
2) 2
3) 3
4) 1 5 3 4 2
STL中“大”、“小”和“相等”的概念
stl中關聯容器內部的元素是排序的。STL 中的許多算法也涉及排序、查找。這些容器和算法都需要對元素進行比較。
默認情況下,比較大小是通過<
運算符進行的,和>
運算符無關。在STL中提到“大”、“小”的概念時,以下三個說法是等價的:
x 比 y 小。表達式x<y
爲真。y 比 x 大。
一定要注意,y比x大
意味着x<y爲真
,而不是y>x爲真
。y>x
的結果如何並不重要。
在 STL 中,x和y相等
也往往不等價於x==y爲真
。對於在未排序的區間上進行的算法,如順序查找算法 find,查找過程中比較兩個元素是否相等用的是==
運算符;但是對於在排好序的區間上進行查找、合併等操作的算法(如折半查找 binary_search,關聯容器自身函數 find)來說,x和y相等
是與x<y和y<x同時爲假
等價的,與==
運算符無關。
看上去x<y和y<x同時爲假
就應該和x==y爲真
等價,其實不然。例如下面的 class A:
class A{ public:bool operator< (const A & a)const {return false;} };
可以看到,對任意兩個類 A 的對象 x、y,x<y
和y<x
都是爲假的。也就是說,對 STL 的關聯容器和許多算法來說,任意兩個類 A 的對象都是相等的,這與==
運算符的行爲無關。
綜上所述,使用 STL 中的關聯容器和許多算法時,往往需要對<
運算符進行適當的重載,使得這些容器和算法可以用<
運算符對所操作的元素進行比較。最好將<
運算符重載爲全局函數,因爲在重載爲成員函數時,在有些編譯器上會出錯(由其 STL 源代碼的寫法導致)。