順序容器


容器庫概覽

容器類型

  順序容器、關聯容器和無序容器。程序員

迭代器

  與容器同樣,迭代器有着公共的接口,若是一個迭代器提供某個操做,那麼全部提供相同操做的迭代器對這個操做的實現方式都是相同的。好比,標準容器類型上的全部迭代器都容許咱們訪問容器中的元素,而全部迭代器都是經過解引用運算符來實現這個操做的。 相似的,標準容器的全部迭代器都定義了遞增運算符 ,用於從當前元素移動到下一個元素。
  容器迭代器支持的全部操做以下:
編程

  其中有一個例外不符合公共接口特色: forward_list迭代器不支持遞減運算符。
  迭代器支持的算術運算以下:
數組

  注意: 以上的這些運算只能應用與string、vector、deque和array的迭代器,咱們不能將它們用於其餘任何容器類型的迭代器。安全

迭代器使用左閉合範圍蘊含的編程假定

   標準庫使用左閉合範圍是由於這種範圍有三種方便的性質。假定begin和end構成一個合法的迭代器範圍,則數據結構

  • 若是begin和end相等,則範圍爲空。
  • 若是begin與end不等,則範圍至少包含一個元素,且begin指向該範圍中的第一個元素。
  • 咱們能夠對begin遞增若干次,使得begin==end。

  這些性質意味着咱們能夠像下面的代碼同樣用一個循環來處理一個元素範圍,而這是安全的:app

while(begin != end){
  *being = val;
  ++begin;
}

容器類型成員

   除了已經知道的常見迭代器容器,大多數容器還提供反向迭代器。簡單來講,反向迭代器就是一種反向遍歷容器的迭代器,與正向迭代器相比,各類操做的含義也都發生了顛倒。好比,對一個反向迭代器執行++操做,會獲得上一個元素。
  每一個容器都定義了多個類型,以下所示:
函數

begin和end成員

  這兩個操做有多個版本,其中帶r的版本返回反向迭代器,以c開頭的版本則返回const迭代器:性能

list<string> a= {"A", "B", "C"};
auto it1 = a.begin();         //list<string>::iterator
auto it2 = a.rbegin();        //list<string>::reverse_iterator
auto it3 = a.cbegin();        //list<string>::const_iterator
auto it4 = a.crbegin();       //list<string>::const_reverse_iterator

  當auto與begin或end結合使用時,得到的迭代器類型依賴於容器類型,與咱們想如何使用迭代器絕不相干。但以c開頭的版本仍是能夠得到const_iterator的,而無論容器的類型是什麼:設計

//顯示指定類型
list<string>::iterator it5 = a.begin();
list<string>::const_iterator it6 = a.begin();
//是itertor仍是const_iterator依賴於a的類型
auto it7 = a.begin();     //僅當a是const時,it7是const_iterator
auto it8 = a.cbegin();     //it8是const_iterator

容器定義和初始化

  每一個容器類型都定義了一個默認構造函數 ,除array 以外,其餘容器的默認構造函數都會建立一個指定類型的空容器,且均可以接受指定容器大小和元素初始值的參數:
指針

  注意: 只有順序容器的構造函數才接受大小參數,關聯函數 並不支持。
  標準庫array具備固定大小: 與內置數組同樣,標準庫array的大小也是類型的一部分,當定義一個array時,除了指定元素類型,還要指定容器的大小:

array<int, 42>::size_type i;             // 數組類型包括元素類型和大小
array<int>::size_type j;                  // 錯誤:array<int>不是一個類型

  若是咱們對array進行列表初始化,初始值的數目必須等於或小於array的大小,若是初始值數目小於array的大小,則它們被用來初始化array中靠前的元素,全部剩餘元素都會進行值初始化。

賦值和swap

   與內置數組不一樣,標準庫array類型容許賦值 ,賦值號左右兩邊的運算對象必須具備相同的類型:

array<int, 10> a1 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
array<int, 10> a2 = {0};     //全部元素值均爲0
a1 = a2;                     // 替換a1中的元素
a2 = {0};                    // 錯誤:不能將一個花括號列表賦予數組

  注意: 因爲右邊運算對象的大小可能與左邊運算對象的大小不一樣,所以array類型不支持assign ,也不容許用花括號包圍的值列表進行賦值。
  容器賦值運算以下:

  使用assign(僅順序容器): 順序容器(array除外)還定義了一個名爲assign的成員,容許咱們從一個不一樣但相容的類型賦值 ,或者從容器的一個子序列賦值。assign操做 用參數指定的元素(的拷貝)替換左邊容器中的全部元素。例如,咱們能夠用assign實現將一個vector中的一段char*賦值予一個list中的string:

list<string> names;
vector<const char*> oldstyle;
names = oldstyle;   // 錯誤:容器類型不匹配
// 正確:能夠將const char*轉換爲string
name.assign(oldstyle.cbegin(), oldstyle.cend());

注意: 因爲其舊元素被替換,所以傳遞給assign的迭代器不能指向調用assign的容器。
  assign的第二個版本 接受一個整型值和一個元素值。它用於指定數目且具備相同給定值的元素替換同期中原有的元素:

// 等價於slist1.clear();
// 後跟slist1.insert(slist1.begin(), 10, "Hiya!");
list<string> slist1(1);             // 1個元素,爲空string
slist1.assign(10, "Hiya!");         // 10個元素,每一個都是」Hiya!「

  使用swap: swap操做主要用於交換兩個相同類型容器的類型 ,調用swap以後,兩個容器中的元素將會交換:

vector<string> svec1(10);     // 10個元素的vector
vector<string> svec2(24);     //24個元素的vector
swap(vec1, vec2);

  調用swap後,svec1將包含24個string元素,svec2將包含10個string,除array外,交換兩個容器的操做保證會很快——元素自己並未交換swap只是交換了兩個容器的內部數據結構。
  注意: 除array外,swap不對任何元素進行拷貝、刪除或插入操做,所以能夠保證在常數時間內完成。
  使用swap須要注意如下幾點:

  • 元素不會被移動的事實意味着,除string外,指向容器的迭代器、引用和指針在swap操做以後都不會失效 ,它們仍然指向swap操做以前所指向的那些元素。可是,在swap以後,這些元素已經屬於不一樣的容器了。例如,將定iter在swap以前指向svec1[3]的string,那麼在swap以後它指向svec3[3]的元素。與其餘容器不一樣,對一個string調用swap會致使迭代器、引用和指針失效。
  • 與其餘容器不一樣,swap兩個array會真正交換它們的元素, 所以交換兩個array所需的時間與array中元素的數目成正比。對於array,在swap操做以後,指針、引用和迭代器所綁定的元素保持不變,但元素值已經與另外一個array中對應元素的值進行了交換。
  • 在新標準庫中,容器既提供函數版本的swap ,也提供非成員版的swap。而早期標準庫版本只提供成員函數版本的swap。非成員版本的swap在泛型編程 中是很是重要的。統一使用非成員版本的swap是一個好習慣。

容器大小操做

  除了一個例外,每一個容器類型都有三個相關的操做。成員函數size 返回容器中元素的數目;empty 當size爲0時返回布爾值true,不然返回false;max_size 返回一個大於或等於該類型容器所能容納的最大元素數的值。forward_list支持max_size和empty,但不支持size。

關係運算符

  每一個容器都支持相等運算符(==和!=);除了無序關聯容器 外的全部容器都支持關係運算符(>,>=,<,<=)。關係運算符左右兩邊的運算對象必須是相同類型的容器,且必須保存相同類型的元素。即,咱們只能將一個vetor< int> 與另外一個vector< int> 進行比較,而不能將一個vector< int> 與一個list< int> 或一個vector< double> 進行比較。
  比較兩個容器其實是進行元素的逐對比較 ,這些運算符的工做方式與string的關係運算相似:

  • 若是兩個容器具備相同大小且全部元素都兩兩對應相等,則這兩個容器相等,不然兩個容器不等。
  • 若是兩個容器大小不一樣,但較小容器中每一個元素都等於較大容器中的對應元素,則較小容器小於較大容器。
  • 若是兩個容器都不是另外一個容器的前綴子序列,則它們的比較結果取決於第一個不相等的元素的比較結果。

  容器的關係運算符使用元素的關係運算符完成比較: 容器的相等運算符 其實是使用元素的==運算符 來實現比較的,而其餘關係運算符 是使用元素的<運算符 。若是元素類型 不支持所需運算符,那麼保存這種元素的容器就不能使用相應的關係運算符。例如,對於沒有定義==和< 運算符的類類型 ,就不能比較兩個該類類型元素的容器:

vector<Sale_data> storeA, storeB;
if(storeA < storeB)                 // 錯誤:Sales_data沒有<運算符

注意: 只有當其元素類型也定義了相應的比較運算符時,咱們纔可使用關係運算符來比較兩個容器。

順序容器概述

  全部順序容器都提供了快速順序訪問 元素的能力,可是,這些容器在如下方面都有不一樣的性能折中:

  • 向容器添加 或從容器中刪除 元素的代價
  • 非順序訪問 容器中元素的代價

  順序容器類型以下:

  在某些狀況下,存儲策略 還會影響特定容器是否支持特定操做:
  一、string和vector: 將元素保存在連續的內存空間。因爲元素是連續存儲 的,經過元素的下標來計算其地址是很是快速的。可是,在這兩種容器的中間位置添加或刪除元素就會很是耗時。
  二、list和forward_list: 這兩個容器的設計目的是令容器任何位置的添加和刪除操做 都很快速,但做爲代價,這兩個容器不支持元素的隨機訪問
  三、deque: 與string和vector同樣,在deque的中間位置添加或刪除元素的代價(可能)很高。可是,在deque的兩端添加或刪除元素都是很快的,與list或forward_list添加刪除元素的速度至關。
  四、array: 與內置數組相比,array是一種更安全、更容易使用的數組類型。和內置數組相似,array對象的大小是固定的,所以,array不支持添加和刪除元素以及改變容器大小的操做。
  注意: forward_list和array是新C++標準增長的類型。
肯定使用哪一種順序容器:

  • 除非你有很好的理由選擇其餘容器,不然應使用vector。
  • 若是你的程序有不少小的元素 ,且空間的額外開銷很重要 ,則不要使用list或forward_list。
  • 若是程序要求隨機訪問元素,應使用vector或deque。
  • 若是程序須要在容器的中間插入或刪除元素 ,應使用list或forward_list。
  • 若是程序須要在容器的頭尾位置插入或刪除元素 ,但不會在中間位置進行插入或刪除操做,則使用deque。

注意: 一般,使用vector是最好的選擇,除非你有很好的理由選擇其餘容器。

順序容器操做

向順序容器添加元素

​ 除array外,全部標準庫容器都提供了靈活的內存管理,在運行時能夠動態添加或刪除元素來改變容器大小:

  當咱們使用以上這些操做時,必需要記得不一樣容器使用不一樣的策略來分配元素空間,而這些操做直接影響性能。在一個vector或string的尾部以外的任何位置,或是一個deque的首尾部以外的任何位置添加元素,都須要移動元素。並且,向一個vector或string添加元素可能引發整個對象存儲空間的從新分配 ,從新分配一個對象的存儲空間須要分配新的內存,並將元素從舊的空間移動到新的空間中。
  一、使用push_back: 除了array和forward_list以外,每一個容器(包括string類型)都支持push_back。不過須要注意的是,容器元素是拷貝,當咱們用一個對象來初始化容器時,或將一個對象插入到容器中時,實際上放入到容器中的時對象值的一個拷貝,而不是對象自己。
  二、使用push_front: 除了push_back,forward_listlistdaque 容器還支持名爲push_front的相似操做。此操做將元素插入到容器頭部。
  注意: deque像vector同樣提供了隨機訪問元素的能力 ,但它還提供了vector所不支持的push_front。deque保證在容器首尾進行插入和刪除元素的操做都只花費常數時間。與vector同樣,在deque首尾以外的位置插入元素將會很耗時。
  三、在容器中的特定位置添加元素: vectordequeliststring 都支持insert成員forward_list 提供了特殊版本的insert成員。  
  每一個insert函數都接受一個迭代器做爲其第一個參數,因爲迭代器可能指向容器尾部以後不存在的元素的位置,並且在容器開始位置插入元素是頗有用的功能,因此insert函數將元素插入迭代器所指向的位置以前

vector<string> svec
// vector不支持push_front,但沃爾瑪呢能夠插入到begin()以前
// 警告:插入到vector末尾以外的任何位置均可能很慢
svec.insert(sevec.begin(), "hello!")

  注意: 雖然某些容器不支持push_front 操做,可是它們對於insert操做並沒有相似的限制(插入開始位置)。所以咱們能夠將元素插入到容器的開始位置,而沒必要擔憂是否支持push_front。將元素插入到vector、deque和string中的任何位置都是合法的,可是這樣作可能很耗時
  除了第一個迭代器參數以外,insert函數還能夠接受更多的參數,着與容器構造函數相似。其中一個版本接受一個元素數目一個值 ,它將指定數量的元素添加到指定位置以前,這些元素都按給定值初始化:

// 將10個"Anna"插入到svec的末尾
svec.insert(svec.end(), 10, "Anna");

  接受一對迭代器或一個初始化列表的insert版本將給定範圍中的元素插入到指定位置以前:

vector<string> v = {"A", "B", "C", "D"};
//將v的最後兩個元素添加到slist的開始位置
slist.insert(slist.begin(), v.end()-2, v.end());

slist.insert(slist.end(),{"these", "words", "will", "go", "at", "the", "end"};
//運行時錯誤:迭代器表示要拷貝的範圍,不能指向與目的位置相同的容器
slist.insert(slist.begin(), slist.begin(), slist.end());

  注意: 在新標準下,接受元素個數或範圍的insert版本返回指向第一個新加入元素的迭代器(在舊版本的標準庫中,這些操做返回void),若是範圍爲空,不插入任何元素,insert操做會將第一個參數返回。
  四、使用insert的返回值: 經過使用insert的返回值,能夠在容器中一個特定位置反覆插入元素:

list<string> lst;
auto iter = lst.begin();
while(cin>>word)
  iter = lst.insert(iter, word);  // 等價於調用push_front

  五、使用emplace操做: 新標準引入了三個成員——emplace_front、emplace和emplace_back,這些操做構造而不是拷貝元素。這些操做分別對應push_front、insert和push_back,容許咱們將元素放置在容器頭部、一個指定位置以前或容器尾部。
  當調用push或insert成員函數時,咱們將元素類型的對象傳遞給它們,這些對象被拷貝 到容器中。而當咱們調用一個emplace成員函數時,則是將參數傳遞給元素類型的構造函數,emplace成員使用這些參數在容器管理的內存中直接構造元素。 例如假定c保存Sales_data元素:

// 在c的末尾構造一個Sales_data對象
// 使用三個參數的Sales_data構造函數
c.emplace_back("9999-99999", 25, 15.99);

// 錯誤:沒有接受三個參數的push_back版本
c.push_back("9999-99999", 25, 15.99);
// 正確:建立一個臨時的Sales_data對象傳遞給push_back
c.push_back(Sales_data("9999-99999", 25, 15.99);

  emplace函數的參數根據元素類型而變化,參數必須與元素類型的構造函數相匹配

// iter 指向c 中一個元素,其中保存了Sa1es_data 元素
c.emp1ace_back(); //使用Sales data 的默認構造函數
c.emplace(iter, " 999- 999999999 " ) ; // 使用Sa1es_data(string)
//使用Sales_data 的接受一個ISBN 、一個count 和一個price 的構造函數
c.emplace_front(" 978 - 0590353403 " , 25 , 15 . 99) ;

  注意: emplace函數在容器中直接構造元素,傳遞給emplace函數的參數必須與元素類型的構造函數相匹配。

訪問元素

  包括array在內的每一個順序容器都有一個front成員函數 ,而除forward_list以外的全部容器都有一個back成員函數 ,這兩個操做分別返回首元素和尾元素的引用

// 在解引用一個迭代器或調用front或back以前檢查是否有元素
if(!c.empty()){
  // val和val2是c中第一個元素值的拷貝
  auto val =*c.begin(),val2 = c.front();
  // val3和val4是c中最後一個元素值的拷貝
  auto last = c.end();
  auto val3 = *(--last);         // 不能遞減forward_list迭代器
  auto val4 = c.back();          // forward_list不支持
}

注意: 迭代器end指向的是容器尾元素以後的(不存在的)元素,爲了獲取尾元素,必須首先遞減此迭代器。在調用front和back(或解引用begin和end返回的迭代器)以前,要確保c非空,若是容器爲空,if中操做的行爲將是未定義的。
  在順序容器中訪問元素的相關操做以下:

  一、訪問成員函數返回的是引用: 在容器中訪問元素的成員函數(即,front、back、下標和at),返回的元素都是引用
  二、下標操做和安全的隨機訪問: 提供快速隨機訪問的容器(string、vector、deque和array)也都提供下標運算符。下標運算符接受一個下標參數。返回容器中該位置的元素的引用
  注意: 保證下標有效是程序員的責任,下標運算符並不檢查是否在合法範圍內。使用越界的下標是一種嚴重的程序設計錯誤,並且編譯器並不檢查這種錯誤。

刪除元素

  與添加元素的多種方式相似,(非array)容器也頗有多種刪除元素的方式:

  注意: 刪除元素的成員函數並不檢查其參數,在刪除元素以前,程序員必須確保它(們)是存在的。
  一、pop_front和pop_back成員函數: 與vector和string不支持push_front同樣,這些類型也不支持pop_front。相似的,forward_list不支持pop_back。
  二、從容器內部刪除一個元素: 成員函數erase 從容器中指定位置刪除元素,咱們能夠刪除由一個迭代器指定的單個元素,也能夠刪除由一對迭代器指定的範圍內的全部元素。兩種形式的erase都返回指向刪除的(最後一個)元素以後位置的迭代器。即,若j是i以後的元素,那麼erase(i)將返回指向j的迭代器。

// 刪除list中的全部奇數元素
list<int> lst = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto it=lst.begin();
while(it != lst.end()){
  if(*it % 2)            
    it = lst.erase(it);   // 刪除此元素
  else
    ++it;
}

  三、刪除多個元素: 接受一對迭代器的erase版本容許咱們刪除一個範圍內的元素:

// 刪除兩個迭代器表示的範圍內的元素
// 返回指向最後一個被刪元素以後位置的迭代器
elem1 = slist.erase(elem1, elem2);            // 調用後,elem1 == elem2

  爲了刪除一個容器中的全部元素,咱們既能夠調用clear,也能夠用begin和end得到的迭代器做爲參數調用erase:

slist.clear();    // 刪除容器中全部元素
slist.erase(slist.begin(), slist.end());              // 等價調用

  四、特殊的forward_list操做:

  在一個forward_list中添加或刪除元素的操做是經過改變給定元素以後的元素來完成的,這樣咱們老是能夠訪問到被添加或刪除操做所影響的元素。因爲這些操做與其餘容器上的操做的實現方式不一樣,forward_list並未定義insert、emplace和erase,而是定義了名爲insert_afteremplace_aftererase_after 的操做。爲了支持這些操做,forward_list也定義了before_begin,它返回一個首前迭代器 。這個迭代器容許咱們在鏈表首元素以前並不存在的元素「以後」添加或刪除元素(即在鏈表首元素以前添加刪除元素):

  當forward_list中添加或刪除元素時,咱們必須關注兩個迭代器——一個指向咱們要處理的元素,另外一個指向其前驅:

forward_list<int> flst = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto prev = flst.before_begin();                           // 表示flst的「首前元素」
auto curr = flst.begin();                                  // 表示flst中的第一個元素
while(curr !=flst.end()){
  if(*curr %2)
    curr = flst.erase_after(prev);                         // 移除它並移動curr
  else{
    // 移動迭代器curr,指向下一個元素,prev指向curr以前的元素
    prev = curr;                                         
    ++curr;
  }
}

  五、改變容器大小: 咱們能夠用resize 來增大或縮小容器,與往常同樣,array不支持resize。若是當前大小大於所要求的大小,容器後部地元素會被刪除;若是當前大小小於新大小,會將新元素添加到容器後部:

list<int> ilist(10, 42);      // 10個int:每一個的值都是42
ilist.resize(15);             // 將5個值爲0的元素添加到ilist的末尾
ilist.resize(25, -1);         // 將10個值爲-1的元素添加到ilist的末尾
ilist.resize(5);              // 從ilist末尾刪除20個元素

  resize操做接受一個可選的元素值參數,用來初始化添加到容器中的元素。若是調用者未提供此參數,新元素進行值初始化。若是容器保存的是類類型 元素,且resize向容器添加新元素,則咱們必須提供初始值,或者元素類型必須提供一個沒人構造函數。

  六、容器操做可能使迭代器失效: 向容器中添加元素和從容器中刪除元素的操做可能會是指向容器元素的指針引用迭代器 失效。一個失效的指針、引用或迭代器將再也不表示任何元素。使用失效的指針、引用或迭代器是一種嚴重的程序設計錯誤,極可能引發與使用爲初始化指針同樣的問題。
  在向容器添加元素後:

  • 若是容器是vector或string,且存儲空間被從新分配,則指向容器的迭代器、指針和引用都會失敗 。若是存儲空間未從新分配,指向插入位置以前的元素的迭代器、指針和引用仍有效,但指向插入位置以後元素的迭代器、指針和引用將會失效。
  • 對於deque,插入到除首尾位置以外的任何位置都會致使迭代器、指針和引用失效。若是在首尾位置添加元素,迭代器會失效,但指向存在的元素的引用和指針不會失效。
  • 對於list和forward_list,指向容器的迭代器(包括尾後迭代器首前迭代器 )、指針和引用仍有效。

  當咱們刪除一個元素後:

  • 對於vector和string,指向被刪除元素以前元素的迭代器、引用和指針仍有效。注意,當咱們刪除元素時,尾後迭代器老是會失效。
  • 對於deque,若是在首尾以外的任何位置刪除元素,那麼指向被刪除元素外其餘元素的迭代器、引用或指針也會失效。若是是刪除deque的尾元素,則尾後迭代器 也會失效,但其餘迭代器、引用和指針不受影響;若是是刪除首元素,這些也不會受影響。
  • 對於list和 forward_list ,指向容器其餘位置的迭代器(包括尾後迭代器首前迭代器 )、引用和指針仍有效。

  警告: 使用失效的迭代器、指針或引用是嚴重的運行時錯誤。
  注意: 因爲向迭代器添加元素和從迭代器刪除元素的代碼可能會使迭代器失效,所以必須保證每次改變容器的操做以後都正確地從新定位迭代器,這個建議對vectorstringdeque 尤其重要。
  七、編寫改變容器的循環程序: 添加/刪除vector、string或daque元素的循環程序必須考慮迭代器、引用和指針可能失效的問題。程序必須保證每一個循環步中都更新迭代器、引用或指針。若是循環中調用的是insert或erase,那麼更新迭代器很容易。這些操做都返回迭代器,咱們能夠用來更新:

// 傻瓜循環,刪除偶數元素,複製每一個奇數元素
vector<int> vi = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto iter = vi.begin();   //調用begin而不是cbegin,由於咱們要改變vi
while(iter !=vi,end()){
  if(*iter % 2){
    iter = vi.insert(iter, *iter);    // 複製當前元素
    iter += 2;             // 向前移動迭代器,跳過當前元素以及插入到它以前的元素
  }
  else
    iter = vi.erase(iter);  // 刪除偶數元素
    // 不該向前移動迭代器,iter指向咱們刪除的元素以後的元素
}

  八、不要保存end返回的迭代器: 當咱們添加/刪除vector或string的元素後,或在deque中首元素以外任何位置添加/刪除元素後,原來end返回的迭代器老是會失效。所以,添加或刪除元素的循環程序必須反覆調用end,而不能在循環以前保存end返回的迭代器,一直看成容器末尾使用。一般C++新標準庫實現中end()操做都很快,部分就是由於這個緣由。

vector對象是如何增加的

  假定容器中元素是連續存儲的,且容器的大小是可變的,考慮向vector或string中添加元素會發生什麼:若是沒有空間容納新元素,容器不可能簡單地將它添加到內存中其餘位置——由於元素必須連續儲存。 容器必須分配新的內存空間來保存已有元素和新元素,將已有元素從舊元素從久位置移動到新空間中,而後添加新元素,釋放存儲空間。 若是咱們沒添加一個新元素,vector就執行一次這樣的內存分配和釋放操做,性能會慢到不可接受。
  爲了不上述這種代價,標準庫實現者採用來能夠減小容器空間從新分配次數的策略。當不得不獲取新的內存空間時,vector和string的實現一般會分配比新的空間需求更大的內存空間。 容器預留這些空間做爲備用,可用來保存更多的元素。這樣,就不須要每次添加新元素都從新分配容器的內存空間了。
  一、管理容量的成員函數: vector和sting類型 提供來一些成員函數,容許咱們與它的實現中內存分配部分互動。capacity操做 告訴咱們容器在不擴張內存空間的狀況下能夠容納多少個元素。reserve操做 容許咱們通知容器它應該準備保存多少個元素。

  注意: reserve並不改變容器中元素的數量,它僅影響vector預先分配多大的內存空間。只有當內存空間超過當前容量時,reserve調用纔會改變vector的容量,若是需求大小大於當前容量,reserve至少分配與需求同樣大的內存空間(可能更大)。
  調用reserve永遠也不會減小容器佔用的內存空間。相似的resize成員函數 只改變容器中元素的數目,而不是容器的容量。咱們一樣不能用resize來減小容器預留的內存空間。
   在C++11新標準庫 中,咱們能夠調用shrink_to_fit 來要求deque、vector或string退回不須要的內存空間。此函數指出咱們再也不須要任何多餘的內存空間。可是,具體的實現能夠選擇忽略此請求,也就是說,調用shrink_to_fit也並不保證必定退回內存空間。
  注意: 調用shrink_to_fit只是一個請求,標準庫並不保證退還內存。
  capacity和size: 理解capacity和size的區別很是重要。容器的size是指它已經保存的元素的數目;而capacity則是在不分配新的內存空間的前提下它最多能夠保存多少元素(包括已經保存的元素)。

額外的string操做

  一、構造string的其餘方法:

  這些構造函數接受一個string或一個const char*參數,還接受(可選的)指定拷貝多少個字符的參數:

const char *cp = "Hello World!!!";    // 以空字符結束的數紐
char noNu l l [] = ( 'H', 'i' ) ;   // 不是以空字符結束
string sl (cp);            // 拷貝cp 中的字符直到遇到空字符; sl == "Hello Wor1d!!!"
string s2 (noNull, 2 );    // 從noNul1 拷貝兩個字符; s2 == "Hi "
string s3 (noNu11);        // 未定義noNu11 不是以空字符結束
string s4 (cp + 6, 5 ) ;   // 從cp[6]開始拷貝5個字符; s4 == "Wor1d"
string s5 (sl, 6, 5 );     // 從sl[6]開始拷貝5個字符; s5 == "World"
string s6 (sl, 6) ;        // 從sl[6]開始拷貝,直至sl 末尾; s6 == "Wor1d' ! !"
string s7 (sl, 6, 20 ) ;   // 正確,只拷貝到sl末尾; s7 == "World!!!"
string s8 (sl, 16);        // 拋出一個out_of_range 異常

  二、substr操做: substr操做返回一個string,它時原始string的一部分或所有的拷貝,能夠傳遞給substr一個可選的開始位置和計數值:

string s("hello world");
string s2 = s.substr(0, 5);        // s2 = "hello"
string s3 = s.substr(6);           // s3 = "world"
string s4 = s.substr(6, 11);       // s4 = "world"
string s5 = s.substr(12);          // 拋出一個out_of_range異常

  注意: 若是開始位置超過了string的大小,則substr函數拋出一個out_of_range異常。若是開始位置加上計數值大於string的大小,則substr會調用整計數值,只拷貝到string的末尾。
  三、append和replace函數:
  append操做是在string末尾進行插入操做的一種簡寫形式:

string s("C++ Primer"),s2 =s;
s.insert(s.size(), "4th Ed.");    // s == 」C++ primer 4th Ed「
s2.append(" 4th Ed.");            // 等價方法:將」4th Ed.「追加到s2; s == s2

  replace操做是調用erase和insert的一種簡寫形式:

// 將」4th「替換爲」5th「的等價方法
s.erase(11, 3);             // s == "C++ Primer Ed."
s.insert(11, "5th");        // s == "C++ Primer 5th Ed."
//從位置11開始,刪除3個字符並插入」5th「
s2.replace(11, 3, "5th");   //等價方法:s == s2

  四、改變string的多種重載函數:
  assign和append函數無須指定要替換string中哪一個部分:assign老是替換string中的全部內容, append老是將新字符追加到string末尾。
  replace函數提供來兩種指定刪除元素範圍的方式,能夠經過一個位置和一個長度來指定範圍,也能夠經過一個迭代器範圍來指定。
  insert函數容許咱們用兩種方式指定插入點:用一個下標或一個迭代器,在兩種狀況下,新元素都會插入到給定下標或迭代器以前的位置。
  五、string搜索操做: string類提供了6個不一樣的搜索函數,每一個函數都有4個重載版本。每一個搜索操做都會返 回一個string::size_type值(unsigned類型),表示匹配發生位置的下標,若是搜索失敗,則返回一個名爲string::npos 的static成員。**標準庫將npos定義爲一個const string::size_type類型,並初始化爲值-1。

容器適配器

  除了順序容器外,標準庫還定義來三個順序容器適配器:stackqueuepriority_queue適配器 是標準庫中的一個通用概念。容器、迭代器和函數都有適配器 。本質上,一個適配器是一種機制 ,能使某種事物的行爲看起來像另一種事物同樣。一個容器適配器接受一個已有的容器類型,使其行爲看起來像一種不一樣的類型。例如,stack適配器接受一個順序容器(除array或forward_list外 ),並使其操做起來像一個stack同樣。全部容器適配器 都支持的操做和類型以下:

  一、定義一個適配器: 對於一個給定的適配器,可使用哪些容器是有限制的。全部適配器都要求容器具備添加和刪除元素的能力 。所以,適配器不能構造在array之上。相似的,咱們也不能用forward_list來構造適配器,由於全部適配器都要求容器具備添加、刪除以及訪問尾元素的能力。

  • stack 只要求push backpop_backback 操做,所以可使用除array和forward list 以外的任何容器類型來構造stack。
  • queue 適配器要求backpush_backfrontpush front 。 所以它能夠構造於list 或deque 之上, 但不能基於vector 構造。
  • priority_queue 除了frontpush_backpop_back 操做以外還要求隨機訪問能力,所以它能夠構造於vector 或deque 之上,但不能基於list構造。

  二、棧適配器:
  

  三、隊列適配器:
  

  
  注意: 每一個容器適配器都基於底層容器類型的操做定義來本身的特殊操做,咱們只可使用適配器操做,而不能使用底層容器類型的操做。

相關文章
相關標籤/搜索