第9章:順序容器

  • #1.順序容器概述
  • #2.容器庫概覽
    • 迭代器
    • 容器類型成員
    • begin和end成員
    • 容器定義和初始化
    • 賦值和swap
    • 容器大小操作
    • 關系運算符
  • #3.順序容器操作
    • 向順序容器添加元素
    • 訪問元素
    • 刪除元素
    • 特殊的forward_list操作
    • 改變容器大小
    • 容器操作可能使迭代器失效
  • #4.vector對象是如何增長的
  • #5.額外的string操作
    • 構造string的其他方法
    • 改變string的其他方法
    • string搜索操作
    • compare函數
    • 數值轉換
  • #6.容器適配器

#.1 順序容器概述

下表列出了標準庫中的順序容器需五,所有順序容器都提供了快速順序訪問元素的能力。但是阅仔,這些容器在以下方面都有不同性能折中:

  • 向容器添加或從容器中刪除元素的代價
  • 非順序訪問容器中元素的代價
順序容器類型 描述
vector 可變大小數組鄙皇。支持隨機訪問胚嘲。在尾部之外的位置增刪元素可能很慢嫌佑。
deque 雙端隊列杆故。支持隨機訪問迅箩。在頭尾位置增刪元素速度很快。
list 雙向鏈表处铛。只支持順序訪問饲趋。在list中任何位置進行增刪操作速度很快拐揭。
forward_list 單向鏈表。只支持單向順序訪問奕塑。在鏈表中任一位置進行增刪操作速度很快堂污。
array 固定大小數組。只支持快速隨機訪問龄砰。不能增刪元素盟猖。
string 與vector相似的容器,專門用于保存字符换棚。隨機訪問快扒披。在尾部增刪速度快。
確定使用哪種順序容器

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

  • 除非你有很好的理由選擇其他容器圃泡,否則應使用vector碟案。
  • 如果你的程序有很多小的元素,且空間的額外開銷很重要颇蜡,則不要使用list或forward_list价说。
  • 如果程序要求隨機訪問元素,應使用vector或deque风秤。
  • 如果程序需要在頭尾位置插入和刪除元素鳖目,但不會在中間位置進行插入或刪除操作,則使用deque缤弦。
  • 如果程序只有在讀取輸入時才需要在容器中間插入元素领迈,隨后需要隨機訪問元素,則
    • 首先碍沐,確定是否真的需要在容器中間位置添加元素狸捅。通常可以很容易地向vector追加數據累提。
    • 如果必須在中間位置插入元素尘喝,考慮在輸入階段使用list,一旦輸入完成斋陪,將list的內容拷貝到一個vector中朽褪。

==通常,使用vector是最好的選擇无虚,除非你有很好的理由選擇其他容器缔赠。==


#2. 容器庫概覽

2.1 迭代器

與容器一樣,迭代器有著公共的接口:如果一個迭代器提供某個操作友题,那么所有提供相同操作的迭代器對這個操作的實現方式都是相同的嗤堰。

迭代器范圍

一個迭代器范圍由一對迭代器表示,兩個迭代器分別指向同一個容器中的元素或者是尾元素之后的位置咆爽。這兩個迭代器通常被稱為begin和end梁棠。迭代器范圍中的元素包含first所表示的元素以及從first開始直至last(不包含last)之間的所有元素。這種元素范圍稱為左閉合區(qū)間

[begin,end)

==迭代器范圍的概念是標準庫的基礎斗埂。==

使用左閉合范圍蘊含的編程假定

標準庫使用左閉合范圍是因為這種范圍有三種方便的性質符糊。假定begin和end構成一個合法的迭代器范圍,則

  • 如果begin和end相等呛凶,則范圍為空
  • 如果begin和end不等男娄,則范圍至少包含一個元素,且begin指向該范圍中的第一個元素
  • 我們可以對begin遞增若干次漾稀,使得begin==end
while(begin != end) {
    *begin = val; //范圍非空模闲,begin指向一個元素
    ++begin; //移動迭代器,指向下一個元素
}

2.2 容器類型成員

每個容器都定義了多個類型崭捍,如size_type尸折,iterator,const_iterator等殷蛇。除了已經使用過的迭代器实夹,大多數容器還提供反向迭代器。為了使用這些類型粒梦,我們必須顯式使用其類名:

//iter是通過list<string>定義的一個迭代器類型
list<string>::iterator iter;
//count是通過vector<int>定義的一個difference_type類型
vector<int>::difference_type count;

2.3 begin和end成員

begin和end操作生成指向容器中第一個元素和尾元素之后位置的迭代器亮航。這兩個迭代器最常見的用途是形成一個包含容器中所有元素的迭代器范圍。begin和end有多個版本:帶r的的版本返回反向迭代器匀们;以c開頭的版本返回const迭代器:

list<string> a = {"Milton","Shakespeare","Austen"};
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

==當不需要寫訪問時缴淋,應使用cbeign和cend。==

2.4 容器定義和初始化

每個容器類型都定義了一個默認構造函數泄朴。除array之外重抖,其他容器的默認構造函數都會創(chuàng)建一個指定類型的空容器,且都可以接受指定容器的大小和元素初始值參數祖灰。

類型 定義
C c 默認構造函數仇哆。如果C是一個array,則C中元素按默認方式初始化夫植;否則C為空讹剔。
C c1(c2) | C c1 = c2 c1初始化為c2的拷貝。c1和c2必須是相同類型详民。(即延欠,它們必須是相同類型的容器,保存的元素類型相同沈跨;對于array類型由捎,兩者的大小還必須相同)
C c{a,b,c,...} | C c = {a,b,c,...} c初始化為初始化列表中元素的拷貝。列表中元素類型必須與c的元素類型相容饿凛。對于array類型狞玛,類表中的元素數目必須等于或小于array的大小软驰,任何遺漏的元素都進行值初始化。
C c(b,e) c初始化為迭代器b和e指定范圍中的元素的拷貝心肪。范圍中元素的類型必須與c的元素類型相容
C seq(n) seq包含n個元素锭亏,這些元素進行了值初始化;此構造函數是explicit的硬鞍。
C seq(n,t) seq包含n個初始化為值t的元素
將一個容器初始化為另一個容器的拷貝

將一個新容器創(chuàng)建為另一個容器的拷貝的方法有兩種:

  • 可以直接拷貝整個容器慧瘤。
  • (array除外)拷貝由一個迭代器對指定的元素范圍。

為了創(chuàng)建一個容器為另一個容器的拷貝固该,兩個容器的類型及其元素類型必須匹配锅减。不過,當傳遞迭代器參數來拷貝一個范圍時伐坏,就不要求容器類型是相同的了怔匣。而且, 新容器和原容器中元素類型也可以不同桦沉,只要能將要拷貝的元素轉為要初始化的容器的元素類型即可劫狠。

//每個容器有三個元素,用給定的初始化器進行初始化
list<string> authors = {"Milton","Shakespeare","Austen"};
vector<const char*> articles = {"a","an","the"};

list<string> list2(authors); //正確:類型匹配
deque<string> authList(authors); //錯誤:容器類型不匹配
vector<string> words(articles); //錯誤:容器類型必須匹配

//正確:可以將const char*元素轉換為string
forward_list<string> words(articles.beign(),articles.end());

==當將一個容器初始化為另一個容器的拷貝時永部,兩個容器的容器類型和元素類型都必須相同独泞。==

列表初始化

在新標準中,我們可以對一個容器進行列表初始化苔埋。

//每個容器有三個元素懦砂,用給定的初始化器進行初始化
list<string> authors = {"Milton","Shakespeare","Austen"};
vector<const char*> articles = {"a","an","the"};
與順序容器大小相關的構造函數

除了與關聯容器相同的構造函數外,順序容器還提供了一個構造函數组橄,它接受一個容器大小和一個元素初始值荞膘。如果我們不提供初始值,則標準庫會創(chuàng)建一個值初始化器:

vector<int> ivec(10,-1); //10個int元素玉工,每個初始化為-1
list<string> svec(10,"hi!"); //10個string元素羽资,每個初始化為"hi!"
forward_list<int> ilist(10); //10個int元素,每個初始化為0

==只有順序容器的構造函數才接受大小參數遵班,關聯容器不支持屠升。==

標準庫array具有固定大小

與內置數組一樣,標準庫array的大小也是類型的一部分狭郑。當定義一個array時腹暖,除了指定元素類型,還必須指定容器大泻踩:

array<string, 10>; //類型為:保存10個string的數組

為了使用array類型脏答,我們必須同時指定元素類型和容器大小:

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

2.5 賦值和swap

下表列出的與賦值相關的運算符可用于所有容器。賦值運算符將其左邊容器中的全部元素替換為右邊容器中的元素的拷貝:

c1 = c2; //將c1中的內容替換為c2中元素的拷貝
c1 = {a,b,c}; //賦值后殖告,c1大小為3
容器賦值運算 定義
c1 = c2 將c1中的元素替換為c2中元素的拷貝阿蝶。c1和c2必須具有相同的類型
c = {a,b,c,...} 將c中的元素替換為初始化列表中元素的拷貝(array不適用)
swap(c1,c2) | c1.swap(c2) 交換c1和c2中的元素。c1和c2必須具有相同的類型黄绩。swap通常比從c2向c1拷貝元素快得多
seq.assign(b,e) 將seq中的元素替換為迭代器b和e所表示的范圍中的元素羡洁。迭代器b和e不能指向seq中的元素。
seq.assign(il) 將seq中元素替換為初始化列表il中的元素
seq.assign(n,t) 將seq中的元素替換為n個值為t的元素
使用assign(僅順序容器)

順序容器定義了一個assign成員宝与,允許我們從一個不同但相容類型賦值焚廊,或者從容器的一個子序列賦值冶匹。assign操作用參數所指定的元素的拷貝替換左邊容器中的所有元素习劫。例如,我們可以用assign實現一個vector中一段char *值賦予一個list中的string:

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

==由于其舊元素被替換嚼隘,因此傳遞給assign的迭代器不能指向調用assign的容器诽里。==

使用swap

swap操作交換兩個相同類型容器的內容,調用swap之后飞蛹,兩個容器中的元素將會交換:

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

調用swap后谤狡,svec1將包含24個元素,svec2將包含10個元素卧檐。除array外墓懂,交換兩個容器內容的操作保證會很快——元素本身并未交換,swap只是交換了兩個容器的內部數據結構霉囚。

==除array外捕仔,swap不對任何元素進行拷貝、刪除或插入操作盈罐,因此可以保證在常數時間內完成榜跌。==

2.6 容器大小操作

除了一個例外,每個容器類型都有三個與大小相關的操作盅粪。成員函數size返回容器中元素的數目钓葫;empty當size為0時返回布爾值true,max_size返回一個大于或等于該類型容器所能容納最大元素的值票顾。forward_list支持max_size和empty不支持size础浮。

2.7 關系運算符

每個容器類型都支持相等運算符(==和!=);除了無序關聯容器外的所有容器都支持關系運算符(>奠骄、<霸旗、>=、<=)戚揭。關系運算符左右兩邊的運算對象必須是相同類型的容器诱告,且必須保存相同類型的元素。

比較兩個容器實際上是進行容器元素的逐對比較。這些運算符的工作方式與string的關系運算類似:

  • 如果兩個容器具有相同的大小且所有元素都兩兩對應相等精居,則這兩個容器相等锄禽;否則,兩個容器不相等靴姿。
  • 如果兩個容器大小不相同沃但,但較小容器中每個元素都等于較大容器中的對應元素,則較小容器小于較大容器佛吓。
  • 如果兩個容器都不是另一個容器的前綴子序列宵晚,則它們的比較結果取決于第一個不相等元素的比較結果。
vector<int> v1 = {1,3,5,7,9,12};
vector<int> v2 = {1,3,9};
vector<int> v3 = {1,3,5,7};
vector<int> v4 = {1,3,5,7,9,12};
v1 < v2 //true;v1[2]<v2[2]
v1 < v3 //false;所有元素都相等维雇,但v3中元素數目更少
v1 == v4 //true;每個元素都相等淤刃,且v1和v4大小相同
v1 == v2 //false;v2元素數目比v1小
容器的關系運算符使用元素的關系運算符完成比較

容器的相等運算符實際上是使用元素的==運算符實現比較的,而其他關系運算符是使用元素的<運算符吱型。如果元素類型不支持所需運算符逸贾,那么保存這種元素的容器就不能使用相應的關系運算符。

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

==只有當其元素類型也定義了相應的比較運算符時津滞,才可以使用關系運算符比較兩個容器铝侵。==


#3. 順序容器操作

順序容器和關聯容器的不同之處在于兩者組織元素的方式。

3.1 向順序容器添加元素

除array外触徐,所有標準庫容器都提供靈活的內存管理咪鲜。在運行時可以動態(tài)添加或刪除來改變容器大小

  • 這些操作會改變容器的大凶拆摹疟丙;array不支持這些操作。
  • forward_list有自己專有版本的insert和emplace;forward_list不支持push_back和emplace_back孔祸。
  • vector和string不支持push_front和emplace_front隆敢。
向順序容器添加元素的操作 定義
c.push_back(t) | c.emplace_back(args) 在c的尾部創(chuàng)建一個值為t或由args創(chuàng)建的元素。返回void
c.push_front(t) | c.emplace_front(args) 在c的頭部創(chuàng)建一個值為t或者由args創(chuàng)建的元素崔慧。返回void
c.insert(p,t) | c.emplace(p,args) 在迭代器p指向的元素之前創(chuàng)建一個值為t或由args創(chuàng)建的元素拂蝎。返回指向新添加元素的迭代器
c.insert(p,n,t) 在迭代器p指向的元素之前插入n個值為t的元素窥突。返回指向新添加的第一個元素的迭代器缝左;若n為0,則返回p
c.insert(p,b,e) 將迭代器b和e指定范圍內元素插入到迭代器p指向的元素之前知给。b和e不能指向c中的元素皇钞。返回指向新添加的第一個元素的迭代器悼泌;若返回為空,則返回p
c.insert(p,il) il是一個花括號包圍的元素值列表夹界。將這些給定值插入到迭代器p指向的元素之前馆里。返回指向新添加的第一個元素的迭代器;若列表為空,則返回p

==向一個vector鸠踪、string或deque插入元素會使所有指向容器的迭代器丙者、引用和指針失效。==

使用push_back

push_back將一個元素追加到一個vector的尾部营密。除array和forward_list之外械媒,每個順序容器都支持push_back。

==當我們用一個對象來初始化容器時评汰,或將一個對象插入到容器中纷捞,實際上放入到容器中的是對象值的一個拷貝,而不是對象本身被去。==

使用push_front

除了push_back主儡,list、forward_list和deque容器還支持名為push_front的類似操作编振。此操作將元素插入到容器頭部:

list<int> ilist;
//將元素添加到ilist開頭
for (size_t i = 0; i < 4; i++) {
    ilist.push_front(i); //每個元素都插入到list的新的開始位置缀辩。
}

此循環(huán)將元素0臭埋、1踪央、2、3添加到ilist頭部瓢阴。每個元素都插入到list新的開始位置畅蹂。即,當我們插入1時荣恐,它會被放置在0之前液斜,2被放置到1之前,依此類推叠穆。

在容器中的特定位置添加元素

insert成員提供了更一般的添加功能少漆,它允許我們在容器中的任意位置插入0個或多個元素。

vector<string> svec;
list<string> slist;

slist.insert(slist.begin(),"Hello!");
//vector不支持push_front硼被,但我們可以插入到begin()之前
//警告:插入到vector末尾之外的任何位置都可能很慢
svec.insert(svec.begin, "Hello");

==將元素插入到vector示损、deque和string中任何位置都是合法的。然而嚷硫,這樣做可能很耗時检访。==

插入范圍內元素

除了第一個迭代器參數之外,insert函數還可以接受更多的參數仔掸,這與容器構造函數類似脆贵。其中一個版本接受一個元素數目和一個值,它將指定數量的元素添加到指定位置之前起暮,這些元素按給定值初始化:

svec.insert(svec.end,10,"Anna");

將10個元素插入到svec的末尾卖氨,并將所有元素都初始化為string "Anna"。

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

vector<string> svec = {"quasi","simba","frollo","scar"};
//將svec的最后兩個元素添加到slist起始位置
slist.insert(svec.begin(), svec.end() - 2, svec.end());
slist.insert(svec.end(), {"these","words","will","go","at","the","end"});
//運行時錯誤:迭代器表示要拷貝的范圍,不能指定與目的位置相同的容器
slist.insert(slist.begin(),slist.begin(),slist.end());

如果我們傳遞給insert一對迭代器筒捺,它們不能指向添加元素的目標容器持搜。

使用insert的返回值

通過使用insert的返回值,可以在容器中一個特定位置反復插入元素:

list<string> lst;
auto iter = lst.begin();
while(cin >> word) {
    iter = list.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對象
c.emplace_back("978-0590353403",25,15.99);

==emplace函數在容器中直接構造元素。傳遞給emplace函數的參數必須與元素類型的構造函數相匹配舔哪。==

3.2 訪問元素

包括array在內的每個順序容器都有一個front成員函數欢顷,而除forward_list之外的所有順序容器都有一個back成員函數。這兩個操作分別返回首元素和尾元素的引用:

//在解引用一個迭代器或者調用front或back之前檢查是否有元素
if (!svec.empty()) {
    //val和val2是c中第一個元素值的拷貝
    auto val = *svec.begin(), val2 = svec.front();
    //val3和val4是c中最后一個元素值的拷貝
    auto last = svec.end();
    auto val3 = *(--last); //不能遞減forward_list
    auto val4 = svec.back(); //forward_list不支持
}  
  • at和下表操作只適用于string捉蚤、vector抬驴、deque和array。
  • back不適用于forward_list缆巧。
在順序容器中訪問元素的操作 定義
c.back() 返回c中尾元素的引用布持。若c為空,函數行為未定義
c.front() 返回c中首元素的引用陕悬。若c為空题暖,函數行為未定義
c[n] 返回c中下標為n的元素的引用,n是一個無符號整數墩莫。若n>=c.size()芙委,則函數行為未定義
c.at[n] 返回下標為n的元素的引用。如果下標越界狂秦,則拋出——out_of_range異常

==對一個空容器調用front和back灌侣,就像使用一個越界的下標一樣,是一種嚴重的程序設計錯誤裂问。==

訪問成員函數返回的是引用

在容器中訪問元素的成員函數(即侧啼,front牛柒、back、下標和at)返回的都是引用痊乾。如果容器是一個const對象皮壁,則返回值是const的引用。如果容器不是const的哪审,則返回值是普通引用蛾魄,我們可以用來改變元素的值:

if (!svec.empty()) {
    svec.front() = "hi"; //給svec的第一個元素賦值
    auto &v = svec.back(); //獲得指向最后一個元素的引用
    v = "last"; //改變svec中的元素
    auto v2 = svec.back(); //v2不是一個引用,它是svec.back()的一個拷貝
    v2 = "lst"; //未改變svec中的元素
}
下標操作和安全的隨機訪問

提供快速訪問的容器(string湿滓、vector滴须、deque和array)也都提供下標運算符。下標運算符并不檢查下標是否在合法范圍內叽奥。使用越界下標是一種嚴重的程序設計錯誤扔水,而且編譯器并不檢查這種錯誤。

vector<string> svec = {"quasi","simba","frollo","scar"};
for (auto i = 0; i < svec.size(); i++) {
    cout << svec[i] << " ";
}
cout << endl;

3.3 刪除元素

與添加元素的多種方式類似朝氓,(非array)容器也有多種刪除元素的方式魔市。

  • 這些操作會改變容器大小,所以不適用于array赵哲。
  • forward_list有特殊版本的erase待德,forward_list不支持pop_back;vector和string不支持pop_front誓竿。
順序容器刪除操作 定義
c.pop_back() 刪除c中尾元素磅网。若c為空谈截,則函數行為未定義筷屡。函數返回void
c.pop_front() 刪除c中首元素。若c為空簸喂,則函數行為未定義毙死。函數返回void
c.erase(p) 刪除迭代器p所指定的元素,返回一個指向被刪元素之后元素的迭代器喻鳄,若p指向尾元素扼倘,則返回尾后迭代器。若p是尾后迭代器除呵,則函數行為未定義
c.erase(b,e) 刪除迭代器b和e所指定范圍內的元素再菊。返回一個指向最后一個被刪元素之后的元素的迭代器,若e本身就是尾后迭代器颜曾,則函數也返回尾后迭代器
c.clear() 刪除c中的所有元素纠拔。返回void

==刪除deque中除首尾位置之外的任何元素都會使所有迭代器、引用和指針失效泛豪。指向vector或string中刪除點之后位置的迭代器稠诲、引用和指針都會失效侦鹏。==

pop_front和pop_back成員函數

pop_front和pop_back成員函數分別刪除首元素和尾元素。

while(!ilist.empty()) {
    process(ilist.front()); //對ilist的首元素進行一些處理
    ilist.pop_front(); //完成處理后刪除首元素
}
從容器內部刪除一個元素

成員函數erase從容器指定位置刪除元素臀叙。我們可以刪除由一個迭代器指定的單個元素略水,也可也刪除由一對迭代器指定的范圍內的所有元素。兩種形式的erase都返回指向刪除的最后一個元素之后位置的迭代器劝萤。

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

3.4 特殊的forward_list操作

forward_list<int> flst = {0,1,2,3,4,5,6,7,8,9};
auto prev = flst.before_begin(); //表示前首元素
auto curr = flst.begin(); //表示首元素
while (curr != flst.end()) { //有元素處理
    if (*curr % 2) { //若元素為奇數
        curr = flst.erase_after(prev); //刪除它并移動
    }else {
        prev = curr; //移動迭代器指向下一元素
        ++curr;
    }
}

3.5 改變容器的大小

用resize來增大或縮小容器。如果當前大小大于所要求的大小床嫌,容器后部的元素會被刪除驶赏;如果當前大小小于新大小,會將新元素添加到容器后部:

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

如果resize縮小容器既鞠,則指向被刪除元素的迭代器煤傍、引用和指針都會失效;對vector嘱蛋、string或deque進行resize可能導致迭代器蚯姆、指針和引用失效。

3.6 容器操作可能使迭代器失效

向容器中添加元素和從容器中刪除元素的操作可能會使指向容器元素的指針洒敏、引用或迭代器失效龄恋。

在向容器添加元素后:

  • 如果容器是vector或string,且存儲空間被重新分配凶伙,則指向容器的迭代器郭毕、指針和引用都會失效。如果存儲空間未重新分配函荣,指向插入位置之前的元素的迭代器显押、指針和引用仍有效。但指向插入位置之后的將會失效傻挂。
  • 對于deque乘碑,插入到除首尾元素之外的任何位置都會導致迭代器、指針和引用失效金拒。
  • 對于list和forward_list兽肤,指向容器的迭代器、指針和引用仍有效绪抛。
編寫改變容器的循環(huán)程序

添加/刪除vector资铡、string或deque元素的循環(huán)必須考慮迭代器、引用和指針可能失效的問題幢码。程序必須保證每個循環(huán)步中都更新迭代器笤休、引用或指針。如果循環(huán)中調用的是insert或erase蛤育,那么更新迭代器很容易宛官。這些操作都返回迭代器葫松,我們可以用來更新:

vector<int> vi = {0,1,2,3,4,5,6,7,8,9};
auto it = vi.begin();
while(it != vi.end()) {
    if (*it % 2) {
        it = vi.insert(it, *it); //賦值當前元素
        it += 2; //向前移動迭代器,跳過當前元素以及插入到它之前的元素
    }else {
        it = vi.erase(it); //刪除偶元素
        //不應向前移動元素底洗,iter指向我們刪除的元素之后的元素
    }
}

在調用erase后腋么,不必遞增迭代器,erase返回的迭代器以及指向序列中下一個元素亥揖。調用insert后珊擂,需要遞增迭代器兩次。insert在給定位置之前插入新元素费变,然后返回指向新元素的迭代器摧扇。

不要保存end返回的迭代器

當我們添加/刪除vector或string的元素后,或在deque中首元素之外任何位置添加/刪除元素后挚歧,原來end返回的迭代器總是會失效扛稽。

//災難:此循環(huán)的行為是未定義的
vector<int> vi = {0,1,2,3,4,5,6,7,8,9};
auto begin = vi.begin(), 
     end = vi.end(); //保存尾迭代器的值是一個壞主意
while (begin != end) { //此循環(huán)是未定義的
    ++begin;//向前移動begin,因為我們想在此元素之后插入元素
    vi.insert(begin, 8); //插入元素
    ++begin; //向前移動begin滑负,跳過我們剛剛加入的元素
}
    
//更安全的方法:在每個循環(huán)步添加/刪除元素后都重新計算end
while(begin != v.end()) {
    //....
    ++begin; //向前移動begin在张,因為我們想在此元素之后插入元素
    begin = v.insert(begin,42); //插入新值
    ++begin; //向前移動begin,跳過我們剛剛加入的元素
}

將end操作返回的迭代器保存在一個名為end的局部變量中矮慕。在循環(huán)體中帮匾,向容器中添加了一個元素,這個操作使保存在end中的迭代器失效了痴鳄。這個迭代器不再指向vi中的任何元素瘟斜,或是vi中尾元素之后的位置。

==如果在一個循環(huán)中插入/刪除deque痪寻、string或vector中的元素螺句,不要緩存end返回的迭代器。
必須在每次插入操作后重新調用end()槽华,而不能在循環(huán)開始前保存它返回的迭代器壹蔓。==


#4. vector對象是如何增長的

為了支持快速訪問,vector將元素連續(xù)存儲——每個元素緊挨著前一個元素存儲猫态。

管理容量的成員函數

shrink_to_fit只適用于vector、string和deque披摄。
capacity和reserve只適用于vetor和string亲雪。

容器大小管理操作 定義
c.shrink_to_fit() 請將capacity()減小為與size()相同的大小
c.capacity() 不重新分配內存空間的話,c可以保存多少元素
c.reserve(n) 分配至少容納n個元素的內存空間

==reserve并不改變容器中元素的數量疚膊,它僅影響vector預先分配多大的內存空間义辕。==

capacity和size

容器的size是指它已經保存的元素的數目;而capacity則是在不分配新的內存空間的前提下它最多可以保存多少元素寓盗。

vector<int> vi;
//size的值為0灌砖,capacity的值依賴于具體實現
cout << "vi:size:" << vi.size()
     << " capacity:" << vi.capacity() << endl;
//向vi添加24個元素
for (vector<int>::size_type i = 0; i < 24; i++) {
    vi.push_back(i);
}
//size為24璧函,capacity大于等于24,具體依賴于實現
cout << "vi:size:" << vi.size()
     << " capacity:" << vi.capacity() << endl;

==每個vector實現都可以選擇自己內存分配策略基显。但是必須遵守的一條原則是:只有當迫不得已時才可以分配新的內存空間蘸吓。==


#5. 額外的string操作

5.1 構造string的其他方法

構造string的其他方法 定義
string s(cp,n) s是cp指向的數組中前n個字符的拷貝。此數組至少應該包含n的字符
string s(s2,pos2) s是string s2從下標pos2開始的字符拷貝撩幽。若pos2>s2.size()库继,構造函數的行為未定義
string s(s2,pos2,len2) s是string s2從下標pos2開始len2個字符的拷貝。若pos2>s2.size()窜醉,構造函數的行為未定義宪萄。不管len2的值是多少,構造函數至多拷貝s2.size()-pos2個字符

這些構造函數接受一個string或一個const char*參數榨惰,還接受指定拷貝多少個字符的參數拜英。

const char* cp = "Hello World!!!";
char noNull[] = { 'h','i' };
string s1(cp); //拷貝cp中的字符直到遇到空字符
string s2(noNull, 2); //從noNull拷貝兩個字符
string s3(noNull); //未定義的,noNull不是以空字符結束
substr操作

substr操作返回一個string琅催,它是原始string的一部分或全部的拷貝聊记。

s.substr(pos,n); //返回一個string,包含s中從pos開始的n個字符的拷貝恢暖。
                 //pos的默認值為0排监,n的默認值為s.size()-pos,即拷貝從pos開始所有的字符杰捂。

5.2 改變string的其他方法

string類型支持順序容器的賦值運算符以及assign舆床、insert和erase操作。

const char *cp = "Stately,plump Buck";
string s("Hello World");
s.assign(cp, 7); //替換s的內容為Stately,賦予s的是從cp指向地址開始的7個元素
s.insert(s.size(), cp + 7); //將字符插入到s[size()]處元素之前的位置

關于insert操作的說明:

string s = "some string";
string s1 = "some other string";
s.insert(0, s1); //在s中0位置之前插入s1的拷貝
s.insert(0, s1, 0, s1.size()); //在s[0]之前插入s1中s1[0]開始的s1.size()個字符
append和replace函數

string類定義了兩個額外的成員函數:append和replace嫁佳,這兩個函數可以改變string的內容挨队。
append操作是在string末尾進行插入操作的一種簡寫形式:

string s("C++ Primer"), s2 = s; // 將s2和s初始化為"C++ Primer"
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

5.3 string搜索操作

string類提供了6個不同的搜索函數蒿往,每個函數都有4個重載版本盛垦。每個搜索操作都返回一個string::size_type值,表示匹配發(fā)生位置的下標瓤漏。如果搜索失敗腾夯,則返回一個名為sting::npos的static成員。

string搜索操作 定義
s.find(args); 查找s中args第一次出現的位置
s.rfind(args); 查找s中args最后一次出現的位置
s.find_first_of(args); 在s中查找args中任何一個字符第一次出現的位置
s.find_last_of(args); 在s中查找args中任何一個字符最后一次出現的位置
s.find_first_not_of(args); 在s中查找第一個不在args中的字符
s.find_last_not_of(args); 在s中查找args最后一次出現的位置
指定在哪里開始搜索

我們可以傳遞給find操作一個可選的開始位置蔬充。

string::size_type pos = 0;
while((pos = name.find_first_of(numbers,pos)) 
                            != string::npo) {
    cout << "find number at index: " << pos 
            << " element is " << name[pos] <<endl;
    ++pos; //移動下一個字符        
}
逆向搜索

rfind成員函數搜索最后一個匹配蝶俱,即子字符串最靠右的出現位置:

string river("Mississippi");
auto first_pos = river.find("is"); //返回1
auto last_pos = river.rfind("is"); //返回4

5.4 compare函數

根據s是等于、大于還是小于參數指定的字符串饥漫,s.compare()返回0榨呆、正數或負數。

5.5 數值轉換

int i = 42;
string s = to_string(i); //將整數i表示為字符表示形式
double d = stod(s); //將字符串s轉換為浮點數

要轉換為數值的string中的第一個非空白符必須是數值中可能出現的字符:

#include <iomanip>

void main() {
    string s2 = "pi = 3.1415926";
    string s3 = s2.substr(s2.find_first_of("+-.1234567890"));
    double d = stod(s3);
    cout <<std::setprecision(10)<< d << endl; //設置輸出精度
}

#6. 容器適配器

除了順序容器外庸队,標準庫還定義了三個順序容器適配器:stack积蜻、queue和priority_queue闯割。適配器是標準庫中一個通用的概念。

定義一個適配器
每個適配器都定義兩個構造函數:默認構造函數創(chuàng)建一個空對象竿拆,接受一個容器的構造函數拷貝該容器來初始化適配器宙拉。

deque<int> deq;
stack<int> stk(deq); //從deq拷貝元素到stk
//在vector上實現的空棧
stack<string, vector<string>> str_stk;
棧適配器

棧默認基于deque實現,也可以在list或vector之上實現如输。

棧操作 定義
s.pop() 刪除棧頂元素鼓黔,但不返回該元素值
s.push(item) | s.emplace(args) 創(chuàng)建一個新元素壓入棧頂,該元素通過拷貝或移動item而來不见,或者由args構造
s.top() 返回棧頂元素澳化,但不將元素彈出棧
stack<int> stk; //空棧
for (size_t i = 0; i < 10; i++) {
    stk.push(i); //保存0-9十個數
}
while(!stk.empty()) {
    int value = stk.top(); //返回棧頂元素,但不將元素彈出棧
    cout << value;
    stk.pop();
}
隊列適配器

queue默認基于deque實現稳吮,priority_queue默認基于vector實現缎谷;
queue也可以用list或vector實現,priority_queue也可以用deque實現灶似。

queue和priority_queue操作 含義
q.pop() 返回queue的首元素或priority_queue的最高優(yōu)先級的元素列林,但不刪除此元素。
q.front() 返回首元素或尾元素酪惭,但不刪除此元素
q.back() 只適用于queue
q.top() 返回最高優(yōu)先級元素希痴,但不刪除該元素[只適用于priority_queue]
q.push(item) | q.emplace(args) 在queue末尾或priority_queue中恰當的位置創(chuàng)建一個元素,其值為item春感,或者由args構造

標準庫queue使用一種先進先出(first-in砌创,first-out,FIFO)的存儲和訪問策略鲫懒。進入隊列的對象被放置到對尾嫩实,而離開隊列的對象從隊首被刪除。

priority_queue允許我們?yōu)殛犃兄械脑亟?yōu)先級窥岩。新加入的元素會排在所有優(yōu)先級比它低的已有元素之前甲献。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市颂翼,隨后出現的幾起案子晃洒,更是在濱河造成了極大的恐慌,老刑警劉巖疚鲤,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件锥累,死亡現場離奇詭異,居然都是意外死亡集歇,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門语淘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诲宇,“玉大人际歼,你說我怎么就攤上這事」美叮” “怎么了鹅心?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長纺荧。 經常有香客問我旭愧,道長,這世上最難降的妖魔是什么宙暇? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任输枯,我火速辦了婚禮,結果婚禮上占贫,老公的妹妹穿的比我還像新娘桃熄。我一直安慰自己,他們只是感情好型奥,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布瞳收。 她就那樣靜靜地躺著,像睡著了一般厢汹。 火紅的嫁衣襯著肌膚如雪螟深。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天烫葬,我揣著相機與錄音界弧,去河邊找鬼。 笑死厘灼,一個胖子當著我的面吹牛夹纫,可吹牛的內容都是我干的。 我是一名探鬼主播设凹,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼舰讹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了闪朱?” 一聲冷哼從身側響起月匣,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎奋姿,沒想到半個月后锄开,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡称诗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年萍悴,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡癣诱,死狀恐怖计维,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情撕予,我是刑警寧澤鲫惶,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站实抡,受9級特大地震影響欠母,放射性物質發(fā)生泄漏。R本人自食惡果不足惜吆寨,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一赏淌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鸟废,春花似錦猜敢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至添寺,卻和暖如春胯盯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背计露。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工博脑, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人票罐。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓叉趣,卻偏偏與公主長得像,于是被迫代替她去往敵國和親该押。 傳聞我的和親對象是個殘疾皇子疗杉,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內容