C++ STL與迭代器

將容器類模板實例化時,會指明容器中存放的元素是什麼類型的:可以存放基本類型的變量,也可以存放對象。

對象或基本類型的變量被插入容器中時,實際插入的是對象或變量的一個複製品。

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 是一個雙向迭代器,則--pp--都是有定義的。--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<yy<x都是爲假的。也就是說,對 STL 的關聯容器和許多算法來說,任意兩個類 A 的對象都是相等的,這與==運算符的行爲無關。

綜上所述,使用 STL 中的關聯容器和許多算法時,往往需要對<運算符進行適當的重載,使得這些容器和算法可以用<運算符對所操作的元素進行比較。最好將<運算符重載爲全局函數,因爲在重載爲成員函數時,在有些編譯器上會出錯(由其 STL 源代碼的寫法導致)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章