1. 程式人生 > >C++中vector的使用

C++中vector的使用

               

向量std::vector是一種物件實體,能夠容納許多各種型別相同的元素,包括使用者自定義的類,因此又被稱為序列容器。與string相同,vector同屬於STL(Standard Template Library)中的一種自定義的資料型別,可以廣義上認為是陣列的增強版,相當於一個動態陣列。

         在使用它時,需要包含標頭檔案vector: #include <vector>

         vector是一個類模板,不是一種資料型別:

template<class T, class Alloc = allocator<T> > class vector
;
 vector可以存放任何型別的物件(但必須是同一類物件)。vector物件可以在執行時高效地新增元素。vector中元素是連續儲存的,所以除了能夠通過迭代器訪問外,還可以通過常規的指標偏移量訪問元素。換句話說,可以將指向vector元素的指標傳入以指向陣列元素的指標作為引數的函式。

         vector會在需要時自動調整所佔記憶體的大小。與對應的靜態陣列相比,vector 所佔的記憶體通常要更多,因為它還分配了額外的記憶體以應對將來可能的擴張。於是,vector 就不必在每次插入元素時都重新分配一次記憶體了,除非這塊預留的記憶體用盡。已分配記憶體的總大小可以通過 capacity() 函式查詢。所佔的額外的記憶體可以通過呼叫 shrink_to_fit()(c++11) 返還給系統。

從效能方面考慮,記憶體重分配操作的代價通常很大。如果事先知道元素個數,可以使用 reserve() 函式消除重新分配操作。

         vector值初始化:

(1)、如果沒有指定元素初始化,標準庫自行提供一個初始化值進行初始化;

(2)、如果儲存的是含有建構函式的類型別的元素,標準庫使用該型別的建構函式初始化;

(3)、如果儲存的是沒有建構函式的類型別的元素,標準庫產生一個帶初始值的物件,使用這個物件進行值初始化。

vector容器記憶體放的所有物件都是經過初始化的。如果沒有指定儲存物件的初始值,那麼對於內建型別將用0初始化,對於類型別將呼叫其預設建構函式進行初始化(如果有其它建構函式而沒有預設建構函式,那麼此時必須提供元素初始值才能放入容器中)。

一個容器就是一些特定型別物件的集合。順序容器(sequential container)為程式設計師提供了控制元素儲存和訪問順序的能力。這種順序不依賴於元素的值,而是與元素加入容器時的位置相對應。

         標準庫中的順序容器包括:

         (1)、vector:可變大小陣列。支援快速隨機訪問。在尾部之外的位置插入或刪除元素可能很慢。

         (2)、deque:雙端佇列。支援快速隨機訪問。在頭尾位置插入/刪除速度很快。

         (3)、list:雙向連結串列。只支援雙向順序訪問。在list中任何位置進行插入/刪除操作速度都很快。

         (4)、forward_list:單向連結串列。只支援單向順序訪問。在連結串列任何位置進行插入/刪除操作速度都很快。

         (5)、array:固定大小陣列。支援快速隨機訪問。不能新增或刪除元素。

         (6)、string:與vector相似的容器,但專門用於儲存字元。隨機訪問快。在尾部插入/刪除速度快。

         除了固定大小的array外,其它容器都提供高效、靈活的記憶體管理。我們可以新增和刪除元素,擴張和收縮容器的大小。容器儲存元素的策略對容器操作的效率有著固定的,有時是重大的影響。在某些情況下,儲存策略還會影響特定容器是否支援特定操作。

         例如,string和vector將元素儲存在連續的記憶體空間中。由於元素是連續儲存的,由元素的下標來計算其地址是非常快速的。但是,在這兩種容器的中間位置新增或刪除元素就會非常耗時:在一次插入或刪除操作後,需要移動插入/刪除位置之後的所有元素,來保持連續儲存。而且,新增一個元素有時可能還需要分配額外的儲存空間。在這種情況下,每個元素都必須移動到新的儲存空間中。

         list和forward_list兩個容器的設計目的是令容器任何位置的新增和刪除操作都很快速。作為代價,這兩個容器不支援元素的隨機訪問:為了訪問一個元素,我們只能遍歷整個容器。而且,與vector、deque和array相比,這兩個容器的額外記憶體開銷也很大。

         deque是一個更為複雜的資料結構。與string和vector類似,deque支援快速的隨機訪問。與string和vector一樣,在deque的中間位置新增或刪除元素的代價(可能)很高。但是,在deque的兩端新增或刪除元素都是很快的,與list或forward_list新增刪除元素的速度相當。

         forward_list和array是新C++標準增加的型別。與內建陣列相比,array是一個種更安全、更容易使用的陣列型別。與內建陣列類似,array物件的大小是固定的。因此,array不支援新增和刪除元素以及改變容器大小的操作。forward_list的設計目標是達到與最好的手寫的單向連結串列資料結構相當的效能。因此,forward_list沒有size操作,因為儲存或計算其大小就會比手寫連結串列多出額外的開銷。對其他容器而言,size保證是一個快速的常量時間的操作。

         通常,使用vector是最好的選擇,除法你有很好的理由選擇其他容器。

         以下是一些選擇容器的基本原則:

         (1)、除法你有很好的理由選擇其他容器,否則應該使用vector;

         (2)、如果你的程式有很多小的元素,且空間的額外開銷很重要,則不要使用list或forward_list;

         (3)、如果程式要求隨機訪問元素,應使用vector或deque;

         (4)、如果程式要求在容器的中間插入或刪除元素,應使用list或forward_list;

(5)、如果程式需要在頭尾位置插入或刪除元素,但不會在中間位置進行插入或刪除操作,則使用deque;

(6)、如果程式只有在讀取輸入時才需要在容器中間位置插入元素,隨後需要隨機訪問元素,則:首先,確定是否真的需要在容器中間位置新增元素。當處理輸入資料時,通常可以很容器地向vector追加資料,然後再呼叫標準庫的sort函式來重排容器中的元素,從而避免在中間位置新增元素。如果必須在中間位置插入元素,考慮在輸入階段使用list,一旦輸入完成,將list中的內容拷貝到一個vector中。

如果你不確定應該使用哪種容器,那麼可以在程式中只使用vector和list公共的操作:使用迭代器,不使用下標操作,避免隨機訪問。這樣,在必要時選擇使用vector或list都很方便。

一般來說,每個容器都定義在一個頭檔案中,檔名與型別名相同。即,deque定義在標頭檔案deque中,list定義在標頭檔案list中,以此類推。容器均定義為模板類。

順序容器幾乎可以儲存任意型別的元素。特別是,我們可以定義一個容器,其元素的型別是另一個容器。這種容器的定義與任何其他容器型別完全一樣:在尖括號中指定元素型別(此種情況下,是另一種容器型別)。

下面是測試程式碼vector.cpp:

#include <vector>#include <iostream>#include <assert.h>#include "vector.hpp"int test_vector_init()std::vector<int> a; // 宣告一個int型向量a,size為0 std::vector<int> b(10); // 宣告一個初始size為10的向量 std::vector<int> c(10, 1); // 宣告一個初始size為10且初始值都為1的向量 std::vector<int> d(b); // 宣告並用向量b初始化向量d std::vector<int> e(c.begin(), c.begin() + 3); // 將c向量中從第0個到第2個(共3個)作為向量e的初始值,size為3 int n[] = { 1, 2, 3, 4, 5 }; std::vector<int> f(n, n + 5); // 將陣列n的前5個元素作為向量f的初值,size為5 std::vector<int> g(&n[1], &n[4]); // 將n[1] - n[4]範圍內的元素作為向量g的初值,size為3 std::vector<std::string> v(5, "hello"); std::vector<std::string> v2(v.begin(), v.end()); assert(v == v2); assert(v.begin() + v.size() == v.end()); assert(v.end() - v.size() == v.begin()); std::vector<int> array{ 9, 7, 5, 3, 1 }; std::cout << "array size: "<< array.size() << std::endl; // 5 return 0;}int test_vector_access()int n[10]; for (int i = 0; i < 10; i++) {  n[i] = i; } std::vector<int> a(n, n + 10); // 對其中部分元素進行輸入 std::cin >> a[2]; std::cin >> a[5]; std::cin >> a[6]; // 輸出 for (int i = 0; i < a.size(); i++) {  std::cout << a[i] << " "; } std::cout << std::endl// 使用遍歷器(又稱迭代器)進行輸出 // vector類的迭代器除了支援通用的字首自增運算子外,還支援算術運算:it + n、it - n、it2 - it1 std::vector<int>::iterator t; for (t = a.begin(); t != a.end(); t++) {  std::cout << *t << " "; } std::cout << std::endlfor (std::vector<int>::const_iterator p = a.begin(); p != a.end(); p++) {  std::cout << *p << " "; } std::cout << std::endlsize_t i = 0int* x = a.data(); //返回指向記憶體中陣列第一個元素的指標 *x = -111std::cout << "a[0]: " << a[0] << std::endl; // -111 return 0;}int test_vector_operation()int n[10], m[15]; for (int i = 0; i < 10; i++) {  n[i] = i;  m[i] = i + 20; } std::vector<int> a(n, n + 10); std::vector<int> b(m, m + 15); std::vector<int> x; x = a; // 賦值 std::cout << "x size: "<<a.size() << std::endl; // 獲取向量中的元素個數, 10 std::cout << "x capacity: " << x.capacity() << std::endl; // 返回當前儲存空間能夠容納的元素數,容器x能夠儲存的元素個數,10 (x.capacity() >= x.size()) std::cout << "x max size: " << x.max_size() << std::endl; // 容器x能容納的最大元素個數, 4611686018427387903 x.reserve(15); // 預留儲存空間,確保x.capacity() >= 15 std::cout << "x capacity after reserve: " << x.capacity() << std::endl; // 15 x.shrink_to_fit(); x.resize(5); // 改變容器中可儲存元素的個數,確保返回後,有x.size() == 5, 如果之前x.size() < 5, 那麼用預設值補全 std::cout << "x size after resize: " << x.size() << std::endl; // 5 std::vector<int>::reference ref1 = b.front(); // 返回容器中第一個元素的引用(容器必須非空), 20 std::vector<int>::reference ref2 = x.back(); // 返回容器中最後一個元素的引用(容器必須非空), 4 int value = b[5]; // 返回下標為5的元素的引用(下標從0開始,如果下標不正確,則屬於未定義行為), 25 std::vector<int>::reference ref3 = b.at(5); // 返回下標為pos的元素的引用;如果下標不正確,則丟擲異常, 25 x.push_back(-100); // 將元素新增到容器末尾,向容器末尾新增一個元素 value = x[x.size() - 1]; // -100 std::cout << "x size after push_back: " << x.size() << std::endl; // 6 x.pop_back(); // 刪除最後一個元素, 彈出容器中最後一個元素(容器必須非空) value = x[x.size() - 1]; // 4 std::cout << "x size after pop_back: " << x.size() << std::endl; // 5 x.assign(10, -1); // 賦值,用指定元素序列替換容器內所有元素 std::cout << "x size after assign: " << x.size() << std::endl; // 10 std::cout << "x[0]: " << x[0] << std::endl; // -1 std::cout << "a is empty: "<< a.empty() << std::endl; // 判斷向量是否為空, 0 std::cout << "a size: " << a.size() << std::endl; // 獲取向量中的元素個數, 10 a.clear(); // 刪除全部內容, 清空向量中的元素,相當於呼叫erase(begin(), end()) std::cout << "size after clear: " << a.size() << std::endl; // 0 std::vector<int> c; c = b; // 將b向量複製到c向量中 std::cout << "a == b ?: " << (a == b) << std::endl; // == 、 != 、>、 >= 、<、 <= , 採用字典排序策略比較,a向量與b向量比較, 相等則返回1, 0 std::cout << "c == b ?: " << (c == b) << std::endl; // == 、 != 、>、 >= 、<、 <= , 採用字典排序策略比較,c向量與b向量比較, 相等則返回1, 1 // 插入和刪除操作將發生元素的移動(為了保持連續儲存的性質),所以之前的迭代器可能失效 // 任何改變容器大小的操作都可能造成以前的迭代器失效 std::cout << "b size: " << b.size() << std::endl; // 15 b.insert(b.begin(), -1); // 將-1插入到向量b的起始位置前 std::cout << "b[0]: " << b[0] << std::endl; // -1 std::cout << "b size: " << b.size() << std::endl; // 16 b.insert(b.begin() + 5, 3, -1); //將-1分別插入到向量元素位置的5-8處(共3個元素) for (int i = 0; i < b.size(); i++) {  std::cout << b[i] << "  "; // -1 20 21 22 23 -1 -1 -1 24 25 ... } std::cout<<std::endlstd::vector<int> d(5, 1); std::vector<int> e(10); e.insert(e.begin(), d.begin(), d.end()); //將d.begin(), d.end()之間的全部元素插入到e.begin()前 for (int i = 0; i < e.size(); i++) {  std::cout << e[i] << "  "; // 1 1 1 1 1 0 0 0 ... } std::cout << std::endlstd::cout << "b size: " << b.size() << std::endl; // 19 b.erase(b.begin()); // 將起始位置的元素刪除 std::cout << "b size: " << b.size() << std::endl; // 18 b.erase(b.begin(), b.begin() + 3); // 將(b.begin(), b.begin()+3)之間的元素刪除 std::cout << "b size: " << b.size() << std::endl; // 15 b.swap(c); // 交換vector的內容, a向量與c向量進行交換 for (int i = 0; i < b.size(); i++) {  std::cout << b[i] << "  "; } std::cout << std::endlfor (int i = 0; i < c.size(); i++) {  std::cout << c[i] << "  "; } std::cout << std::endlreturn 0;}int test_vector_two_dimension()// reference: http://www.cnblogs.com/mr-wid/archive/2013/01/22/2871105.html std::vector< std::vector<int> > b(10, std::vector<int>(5, -1)); // 對部分資料進行輸入 std::cin >> b[1][1]; std::cin >> b[2][2]; std::cin >> b[3][3]; // 全部輸出 for (int m = 0; m < b.size(); m++) { //b.size()獲取行向量的大小  for (int n = 0; n < b[m].size(); n++) { //獲取向量中具體每個向量的大小   std::cout << b[m][n] << "  ";  }  std::cout << std::endl; } return 0;}
主要參考文獻:

1. https://software.intel.com/zh-cn/blogs/2011/08/10/c-vector

2. http://zh.cppreference.com/w/cpp/container/vector 

3. http://www.cplusplus.com/reference/vector/vector/