vector容器與iterator迭代器

來源:互聯網
上載者:User
vector容器
vector 是同一種類型的對象的集合,每個對象都有一個對應的整數索引值。和 string 對象一樣,標準庫負責管理儲存元素的相關記憶體。我們把 vector 稱為 容器 ,是因為它可以包含其他對象。一個容器中的所有對象都必須是同一種類型的。

使用vector之前,必須包含相應的標頭檔。
#include <vector>

using std::vector;

vector是一個類模板(class template)。模板允許程式員編寫單個類或函數定義,這個類和函數定義可用於不同的資料類型上。因此,我們可以定義儲存string對象的vector,或儲存int值的vector,又或是儲存自訂的類類型對象(如Sales_item對象)的vector。
聲明從類模板產生的某種類型的對象,需要提供附加資訊,資訊的種類取決於模板。以vector為例,必須說明vector儲存何種對象的類型,通過將類型放在類模板名稱後面的角括弧中來指定類型:

vector<int> ivec; // ivec holds objects of type intvector<Sales_item> Sales_vec; // holds Sales_items


 

和其他變數定義一樣,定義vector對象要指定類型和一個變數的列表。上面的第一個定義,類型是vector<int>,該類型即是含有若干int類型對象的vector,變數名為ivec。第二個定義的變數名是Sales_vec,它所儲存的元素是Sales_item類型的對象。

vector不是一種資料類型,而只是一個類模板,可用來定義任意多種資料類型。vector類型的每一種都指定了其儲存元素的類型。因此,vector<int>和vector <string>都是資料類型。

vector對象的定義和初始化

vector類定義了好幾種建構函式,用來定義和初始化vector對象。下表3-4列出了這些建構函式:

vector<T> v1;

vector檔案類型為T的對象。預設建構函式v1為空白。

vector<T> v2(v1);

v2是v1的一個副本。

vector<T> v3(n, i);

v3包含n個值為i的元素。

vector<T> v4(n);

v4含有值初始化的元素的n個副本。

建立確定個數的元素

若要建立非空的vector對象,必須給出初始化元素的值。當把一個vector對象複製到另一個vector對象時,新複製的vector中每一個元素都初始化為原vector中相應元素的副本。但這兩個vector對象必須儲存同一種元素類型:

vector<int> ivec1; // ivec1 holds objects of type intvector<int> ivec2(ivec1); // ok: copy elements of ivec1 into ivec2vector<string> svec(ivec1); // error: svec holds strings, not ints


 

可以用元素個數和元素值對vector對象進行初始化。建構函式用元素個數來決定vector對象儲存元素的個數,元素值指定每個元素的初始值:

vector<int> ivec4(10, -1); // 10 elements, each initialized to -1vector<string> svec(10, "hi!"); // 10 strings, each initialized to "hi!"


 

關鍵概念:vector對象動態增長

vector對象(以及其他標準庫容器物件)的重要屬性就在於可以在運行時高效地添加元素。因為vector增長的效率高,在元素值已知的情況下,最好是動態地添加元素。這種增長方式不同於C語言中的內建資料類型,也不同於大多數其他程式設計語言的資料類型。特別地,如果讀者習慣了C或Java的風格,由於vector元素連續儲存,可能希望最好是預先分配合適的空間。但事實上,為了達到連續性,C++的做法恰好相反。 雖然可以對給定元素個數的 vector 對象預先分配記憶體,但更有效方法是先初始化一個空 vector 對象,然後再動態地增加元素。

值初始化

如果沒有給出元素的初始化式,那麼標準庫將提供一個值初始化的(value initialized)元素初始化式。這個由庫產生的初始值用於初始化容器中的每個元素。而元素初始化式的值取決於儲存在vector中元素的資料類型。

如果vector儲存內建類型(如int類型)的元素,那麼標準庫將用0值建立元素初始化值:

vector<string> fvec(10); // 10 elements, each initialized to 0

如果向量儲存類類型(如string)的元素,標準庫將用該類型的預設建構函式建立元素初始值:

vector<string> svec(10); // 10 elements, each an empty string

對於有自訂建構函式但沒有預設建構函式的類,在初始化這種類型的Vector對象時,程式員就不能僅提供元素個數,還需要提供元素初始值。

元素類型可能是沒有定義任何建構函式的類類型。這種情況下,標準庫仍產生一個帶初始值的對象,這個對象的每個成員進行了值初始化。

vector的操作

vector標準庫提供許多類似於string對象的操作,下表列出了幾種最重要的vector操作。

v.empty()

如果v為空白,則返回true,否則返回false。

v.size()

返回v中元素的個數。

v.push_back(t)

在v的末尾增加一個值為t的元素。

v[n]

返回v中位置為n的元素。

v1 = v2

把v1的元素替換為v2中元素的副本。

v1 == v2

如果v1與v2相等,則返回true。

!=, <, <=, >, >=

保持這些操作符慣有的含義。

vector對象的size

empty和size操作類似於string類型的相關操作。成員函數size返回相應vector類定義的size_type的值。

使用size_type類型時,必須指出該類型是在哪裡定義的。vector類型總是包括vector的元素類型:

vector<int>::size_type // okvector::size_type // error


 

vector添加元素

push_back()操作接受一個元素值,並將它作為一個新的元素添加到vector對象的後面,也就是“插入(push)”到vector對象的“後面(back)”:

// read words from the standard input and store them as elements in a vectorstring word;vector<string> text; // empty vectorwhile (cin >> word) {text.push_back(word); // append word to text}


 

該迴圈從標準輸入讀取一系列string對象,逐一追加到vector對象的後面。首先定義一個空的vector對象text。每迴圈一次就添加一個新元素到vector對象,並將從輸入讀取的word值賦予該元素。當迴圈結束時,text就包含了所有讀入的元素。

vector的下標操作

vector中的對象是沒有命名的,可以按vector中對象的位置來訪問它們。通常使用下標操作符來擷取元素。vector的下標操作類似於string類型的下標操作。

vector的下標操作符接受一個值,並返回vector中該對應位置的元素。vector元素的位置從0開始。下例使用for迴圈把vector中的每個元素值都重設為0:

// reset the elements in the vector to zerofor (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)ivec[ix] = 0;


 

和string類型的下標操作符一樣,vector下標操作的結果為左值,因此可以像迴圈體中所做的那樣實現寫入。另外,和string對象的下標操作類似,這裡用size_type類型作為vector下標的類型。

在上例中,即使ivec為空白,for迴圈也會正確執行。ivec為空白則調用size返回0,並且for中的測試比較ix和0。第一次迴圈時,由於ix本身就是0,則條件測試失敗,for迴圈體一次也不執行。

關鍵概念:安全的泛型程式設計

習慣於C或Java編程的C++程式員可能會覺得難以理解,for迴圈的判斷條件用!=而不是用<來測試vector下標值是否越界。C程式員難以理解的還有,上例中沒有在for迴圈之前就調用size成員函數並儲存其返回的值,而是在for語句頭中調用size成員函數。C++程式員習慣於優先選用!=而不是<來編寫迴圈判斷條件。
調用size成員函數而不儲存它返回的值,在這個例子中同樣不是必需的,但這反映了一個良好的編程習慣。在C++中,有些資料結構(如vector)可以動態增長。上例中迴圈僅需要讀取元素,而不需要增加新的元素。但是,迴圈可以容易地增加新元素,如果確實增加了新元素的話,那麼測試已儲存的size值作為迴圈的結束條件就會有問題,因為沒有將新加入的元素計算在內。所以我們傾向於在每次迴圈中測試size的當前值,而不是在進入迴圈時,儲存size值的副本。

我們知道,C++中有些函數可以聲明為內聯(inline)函數。編譯器遇到內嵌函式時就會直接擴充相應代碼,而不是進行實際的函數調用。像size這樣的小庫函數幾乎都定義為內嵌函式,所以每次迴圈過程中調用它的運行時代價是比較小的。

下標操作不添加元素

初學C++的程式員可能會認為vector的下標操作可以添加元素,其實不然:

vector<int> ivec; // empty vectorfor (vector<int>::size_type ix = 0; ix != 10; ++ix)ivec[ix] = ix; // disaster: ivec has no elements


 

上述程式試圖在ivec中插入10個新元素,元素值依次為0到9的整數。但是,這裡ivec是空的vector對象,而且下標只能用於擷取已存在的元素。

這個迴圈的正確寫法應該是:

for (vector<int>::size_type ix = 0; ix != 10; ++ix)ivec.push_back(ix); // ok: adds new element with value ix


 

必須是已存在的元素才能用下標操作符進行索引。通過下標操作進行賦值時,不會添加任何元素。

警告:僅能對確知已存在的元素進行下標操作

對於下標操作符([]操作符)的使用有一點非常重要,就是僅能提取確實已存在的元素,例如:

vector<int> ivec; // empty vectorcout << ivec[0]; // Error: ivec has no elements!vector<int> ivec2(10); // vector with 10 elementscout << ivec[10]; // Error: ivec has elements 0...9


 

試圖擷取不存在的元素必然產生執行階段錯誤。和大多數同類錯誤一樣,不能確保執行過程可以捕捉到這類錯誤,運行程式的結果是不確定的。由於取不存在的元素的結果是未定義的,因而不同的實現會導致不同的結果,但程式運行時幾乎肯定會以某種有趣的方式失敗。

本警告適用於任何使用下標操作的時候,如string類型的下標操作,以及將要簡要介紹的內建數組的下標操作。

不幸的是,試圖對不存在的元素進行下標操作是程式設計過程中經常會犯的嚴重錯誤。所謂的“緩衝區溢位”錯誤就是對不存在的元素進行下標操作的結果。這樣的缺陷往往導致PC機和其他應用中最常見的安全問題。

vector迭代器

除了使用下標來訪問vector對象的元素外,標準庫還提供了另一種檢測元素的方法:使用迭代器(iterator)。迭代器是一種允許程式員檢查容器內元素,並實現元素遍曆的資料類型。

標準庫為每一種標準容器(包括vector)定義了一種迭代器類型。迭代器類型提供了比下標操作更一般化的方法:所有的標準庫容器都定義了相應的迭代器類型,而只有少數的容器支援下標操作。因為迭代器對所有的容器都適用,現代C++程式更傾向於使用迭代器而不是下標操作訪問容器元素,即使對支援下標操作的vector類型也這樣。

容器的iterator類型

每種容器類型都定義了自己的迭代器類型,如vector:

vector<int>::iterator iter;

這條語句定義了一個名為iter的變數,它的資料類型是由vector<int>定義的iterator類型。每個標準庫容器類型都定義了一個名為iterator的成員,這裡的iterator與迭代器實際類型的含義相同。

不同的容器類定義了自己的iterator類型,用於訪問容器內的元素。換句話說,每個容器定義了一種名為iterator的類型,而這種類型支援(概念上的)迭代器的各種行為。

begin和end操作

每種容器都定義了一對命名為begin和end的函數,用於返回迭代器。如果容器中有元素的話,由begin返回的迭代器指向第一個元素:

vector<int>::iterator iter = ivec.begin();

上述語句把iter初始化為由名為begin的vector操作返回的值。假設vector不空,初始化後,iter即指該元素為ivec[0]。

由end操作返回的迭代器指向vector的“末端元素的下一個”。通常稱為超出末端迭代器(off-the-end iterator),表明它指向了一個不存在的元素。如果vector為空白,begin返回的迭代器與end返回的迭代器相同。

由end操作返回的迭代器並不指向vector中任何實際的元素,相反,它只是起一個哨兵(sentinel)的作用,表示我們已處理完vector中所有元素。

vector迭代器的自增和解引用運算

迭代器類型定義了一些操作來擷取迭代器所指向的元素,並允許程式員將迭代器從一個元素移動到另一個元素。

迭代器類型可使用解引用操作符(*操作符)來訪問迭代器所指向r 元素:

*iter = 0;

解引用操作符返回迭代器當前所指向的元素。假設iter指向vector對象ivec的第一個元素,那麼*iter和ivec[0]就是指向同一個元素。上面這個語句的效果就是把這個元素的值賦為0。

迭代器使用自增操作符向前移動迭代器指向容器中下一個元素。從邏輯上說,迭代器的自增操作和int型對象的自增操作類似。對int對象來說,操作結果就是把int型值“加1”,而對迭代器對象則是把容器中的迭代器“向前移動一個位置”。因此,如果iter指向第一個元素,則++iter指向第二個元素。

由於end操作返回的迭代器不指向任何元素,因此不能對它進行解引用或自增操作。

迭代器的其他運算

另一對可執行於迭代器的操作就是比較:用==或!=操作符來比較兩個迭代器,如果兩個迭代器對象指向同一個元素,則它們相等,否則就不相等。

迭代器應用的程式樣本

假設已聲明了一個vector<int>型的ivec變數,要把它所有元素值重設為0,可以用下標操作來完成:

// reset all the elements in ivec to 0for (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix)ivec[ix] = 0;


 

上述程式用for迴圈遍曆ivec的元素,for迴圈定義了一個索引ix,每迴圈迭代一次ix就自增1。for迴圈體將ivec的每個元素賦值為0。

更典型的做法是用迭代器來編寫迴圈:

// equivalent loop using iterators to reset all the elements in ivec to 0for (vector<int>::iterator iter = ivec.begin();iter != ivec.end(); ++iter)*iter = 0; // set element to which iter refers to 0


 

for迴圈首先定義了iter,並將它初始化為指向ivec的第一個元素。for迴圈的條件測試iter是否與end操作返回的迭代器不等。每次迭代iter都自增1,這個for迴圈的效果是從ivec第一個元素開始,順序處理vector中的每一元素。最後,iter將指向ivec中的最後一個元素,處理完最後一個元素後,iter再增加1,就會與end操作的傳回值相等,在這種情況下,迴圈終止。

for迴圈體內的語句用解引用操作符來訪問當前元素的值。和下標操作符一樣,解引用操作符的傳回值是一個左值,因此可以對它進行賦值來改變它的值。上述迴圈的效果就是把ivec中所有元素都賦值為0。

通過上述對代碼的詳細分析,可以看出這段程式與用下標操作符的版本達到相同的操作效果:從vector的第一個元素開始,把vector中每個元素都置為0。
如果vector為空白,程式是安全的。如果ivec為空白,則begin返回的迭代器不指向任何元素,由於沒有元素,所以它不能指向任何元素——在這種情況下,從begin操作返回的迭代器與從end操作返回的迭代器的值相同,因此for語句中的測試條件立即失敗。

const_iterator

前面的程式用vector::iterator改變vector中的元素值。每種容器類型還定義了一種名為const_iterator的類型,該類型只能訪問容器內元素,但不能改變其值。

當我們對普通iterator類型解引用時,得到對某個元素的非const引用。而如果我們對const_iterator類型解引用時,則可以得到一個指向const對象的引用,如同任何常量一樣,該對象不能進行重寫。

例如,如果text是vector<string>類型,程式員想要遍曆它,輸出每個元素,可以這樣編寫程式:

// use const_iterator because we won't change the elementsfor (vector<string>::const_iterator iter = text.begin();iter != text.end(); ++iter)cout << *iter << endl; // print each element in text


 

除了是從迭代器讀取元素值而不是對它進行賦值之外,這個迴圈與前一個相似。由於這裡只需要藉助迭代器進行讀,不需要寫,這裡把iter定義為const_iterator類型。當對const_iterator類型解引用時,返回的是一個const值。不允許用const_iterator進行賦值:

for (vector<string>::const_iterator iter = text.begin();iter != text.end(); ++ iter)*iter = " "; // error: *iter is const


 

使用const_iterator類型時,我們可以得到一個迭代器,它自身的值可以改變,但不能用來改變其所指向的元素的值。可以對迭代器進行自增以及使用解引用操作符來讀取值,但不能對該元素值賦值。

不要把const_iterator對象與const的iterator對象混淆起來。聲明一個const迭代器時,必須初始化迭代器。一旦被初始化後,就不能改變它的值:

vector<int> nums(10); // nums is nonconstconst vector<int>::iterator cit = nums.begin();*cit = 1; // ok: cit can change its underlying element++cit; // error: can't change the value of citconst vector<int> nines(10, 9); // cannot change elements in nines// error: cit2 could change the element it refers to and nines is constconst vector<int>::iterator cit2 = nines.begin();// ok: it can't change an element value, so it can be used with a const vector<int>vector<int>::const_iterator it = nines.begin();*it = 10; // error: *it is const++it; // ok: it isn't const so we can change its value// an iterator that cannot write elementsvector<int>::const_iterator// an iterator whose value cannot changeconst vector<int>::iterator


 

迭代器的算術操作

除了一次移動迭代器的一個元素的增量操作符外,vector的迭代器(很少有其他標準庫容器迭代器)也支援其他的算術操作。這些操作稱為迭代器算術操作(iterator arithmetic),包括:

l

 iter + niter - n


可以對迭代器對象加上或減去一個整型值。這樣做將產生一個新的迭代器,其位置在iter所指元素之前(加)或之後(減)n個元素的位置。加或減之後的結果必須指向iter所指vector中的某個元素,或者是vector末端的後一個元素。加上或減去的值的類型應該是vector的size_type或difference_type類型(參考下面的解釋)。

l iter1 - iter2

該運算式用來計算兩個迭代器對象的距離,該距離是名為difference_type的signed整數類型的值,這裡的difference_type類型類似於size_type類型,也是由vector定義的。difference_type是signed類型,因為減法運算可能產生負數的結果。該類型可以保證足夠大以儲存任何兩個迭代器對象間的距離。iter1與iter2兩者必須都指向同一vector中的元素,或者指向vector末端之後的下一個元素。

可以用迭代器算術操作來移動迭代器直接指向某個元素,例如,下面語句直接定位於vector的中間元素:

vector<int>::iterator mid = vi.begin() + vi.size()/2;


 

上述代碼用來初始化mid,使其指向vi中最靠近正中間的元素。這種直接計算迭代器的方法,與用迭代器逐個元素自增操作到達中間元素的方法是等價的,但前者的效率要高得多。

任何改變vector長度的操作都會使已存在的迭代器失效。例如,在調用push_back之後,就不能再信賴指向vector的迭代器的值了。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.