日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > c/c++ >内容正文

c/c++

C++primer第九章 顺序容器 9.3 顺序容器操作

發(fā)布時(shí)間:2023/12/13 c/c++ 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++primer第九章 顺序容器 9.3 顺序容器操作 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

9.3順序容器操作

  • 順序容器和關(guān)聯(lián)容器的不同之處在于兩者組織元素的方式。這些不同之處直接關(guān)系到了元素如何存儲(chǔ)、訪問、添加以及刪除。上一節(jié)介紹了所有容器都支持的操作(羅列于表9.2(第295頁(yè)))。本章剩余部分將介紹順序容器所特有的操作。

9.3.1向順序容器添加元素

  • 除array外,所有標(biāo)準(zhǔn)庫(kù)容器都提供靈活的內(nèi)存管理。在運(yùn)行時(shí)可以動(dòng)態(tài)添加或刪除元素來(lái)改變?nèi)萜鞔笮 1?.5列出了向順序容器(非array)添加元素的操作。

  • 當(dāng)我們使用這些操作時(shí),必須記得不同容器使用不同的策略來(lái)分配元素空間,而這些策略直接影響性能。在一個(gè)vector或string的尾部之外的任何位置,或是一個(gè)deque的首尾之外的任何位置添加元素,都需要移動(dòng)元素。而且,向一個(gè)vector或string添加元素可能引起整個(gè)對(duì)象存儲(chǔ)空間的重新分配。重新分配一個(gè)對(duì)象的存儲(chǔ)空間需要分配新的內(nèi)存,并將元素從舊的空間移動(dòng)到新的空間中。

使用push_back

  • 在3.3.2節(jié)(第90頁(yè))中,我們看到push_back將一個(gè)元素追加到一個(gè)vector的尾部除array和forward_list之外,每個(gè)順序容器(包括string類型)都支持push_back
  • 例如,下面的循環(huán)每次讀取一個(gè)string到word中,然后追加到容器尾部:
  • //從標(biāo)準(zhǔn)輸入讀取數(shù)據(jù),將每個(gè)單詞放到容器末尾
  • string word;while(cin>>word)container.push_back(word);
  • 對(duì)push_back的調(diào)用在container尾部創(chuàng)建了一個(gè)新的元素,將container的size增大了.該元素的值為word的一個(gè)拷貝。container的類型可以是list、vector或deque
  • 由于string是一個(gè)字符容器,我們也可以用push_back在string末尾添加字符:
void pluralize(size_tent,string&word) { if(ent>1) word.push_back(*sr);//等價(jià)于word+='s' }

容器元素是拷貝

  • 當(dāng)我們用一個(gè)對(duì)象來(lái)初始化容器時(shí),或?qū)⒁粋€(gè)對(duì)象插入到容器中時(shí),實(shí)際上放入到容器中的是對(duì)象值的一個(gè)拷貝,而不是對(duì)象本身。就像我們將一個(gè)對(duì)象傳遞給非引用參數(shù)(參見3.2.2節(jié),第79頁(yè))一樣,容器中的元素與提供值的對(duì)象之間沒有任何關(guān)聯(lián)。隨后對(duì)容器中元素的任何改變都不會(huì)影響到原始對(duì)象,反之亦然。

使用push_front

  • 除了push_back,list,forward_list和deque容器還支持名為push_front的類似操作。此操作將元素插入到容器頭部
  • list<int>ilist;//將元素添加到ilist開頭
  • for(size_tix=0;ix!=4;++ix)ilist.push_front(ix);
  • 此循環(huán)將元素0、1、2、3添加到ilist頭部。每個(gè)元素都插入到list的新的開始位置(newbeginning)。即,當(dāng)我們插入1時(shí),它會(huì)被放置在0之前,2被放置在1之前,依此類推。因此,在循環(huán)中以這種方式將元素添加到容器中,最終會(huì)形成逆序。在循環(huán)執(zhí)行完畢后,ilist保存序列3、2、1、0。注意,deque像vector一樣提供了隨機(jī)訪問元素的能力,但它提供了vector所不支持的push_frontdeque保證在容器首尾進(jìn)行插入和刪除元素的操作都只花費(fèi)常數(shù)時(shí)間。與vector一樣,在deque首尾之外的位置插入元素會(huì)很耗時(shí)。

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

  • push_back和push_front操作提供了一種方便地在順序容器尾部或頭部插入單個(gè)元素的方法insert成員提供了更一般的添加功能,它允許我們?cè)谌萜髦腥我馕恢貌迦?個(gè)或多個(gè)元素
  • vector、deque、list和string都支持insert成員。forward_list提供了特殊版本的insert成員,我們將在9.3.4節(jié)(第312頁(yè))中介紹。每個(gè)insert函數(shù)都接受一個(gè)迭代器作為其第一個(gè)參數(shù)。迭代器指出了在容器中什么位置放置新元素。它可以指向容器中任何位置,包括容器尾部之后的下一個(gè)位置。由于迭代器可能指向容器尾部之后不存在的元素的位置,而且在容器開始位置插入元素是很有用的功能,所以insert函數(shù)將元素插入到迭代器所指定的位置之前
  • 例如,下面的語(yǔ)句slist.insert(iter,"Hello!");//將"Hello!添加到iter之前的位置
  • 將一個(gè)值為"Hello"的string插入到iter指向的元素之前的位置。雖然某些容器不支持push_front操作,但它們對(duì)于insert操作并無(wú)類似的限制(插入開始位置),比如vector。因此我們可以將元素插入到容器的開始位置,而不必?fù)?dān)心容器是否支持push_front:
  • vector<string>svec;list<string>slist;
  • //等價(jià)于調(diào)用slist.push_front("Hello!'*);slist.insert(slist.begin(),"Hello!");//vector不支持push_front,但我們可以插入到begin()之前
  • //警告:插入到vector末尾之外的任何位置都可能很慢 svec.insert(svec.begin(),"Hello!");

插入范圍內(nèi)元素

  • 除了第一個(gè)迭代器參數(shù)之外,insert函數(shù)還可以接受更多的參數(shù),這與容器構(gòu)造函數(shù)類似。其中一個(gè)版本接受一個(gè)元素?cái)?shù)目和一個(gè)值,它將指定數(shù)量的元素添加到指定位置之前,這些元素都按給定值初始化:
  • svec.insert(svec.end(),10,"Anna");
  • 這行代碼將10個(gè)元素插入到svec的末尾,并將所有元素都初始化為string"Anna"?接受一對(duì)迭代器或一個(gè)初始化列表的insert版本將給定范圍中的元素插入到指定位置之前:
  • vector<string>v={"quasin","simba',"frollo","nscarH");
  • //將v的最后兩個(gè)元素添加到slist的開始位置
  • slist.insert(slist.begin(),v.end()-2, v.end());
  • slist.insert(slist.end(),("these",“words”,"will",ngon,natn,"the",“end”});
  • slist.insert(slist.begin(), slist.begin(), slist.end);??/ / 運(yùn)行時(shí)錯(cuò)誤:迭代器表示要拷貝的范圍,不能指向與目的位置相同的容器
  • 如果我們傳遞給insert 一對(duì)迭代器,它們不能指向添加元素的目標(biāo)容器。 在新標(biāo)準(zhǔn)下,接受元素個(gè)數(shù)或范圍的insert版本返回指向第一 "新加入元素的迭代器。(在舊版本的標(biāo)準(zhǔn)庫(kù)中,這些操作返回void。)如果范圍為空,不插入任何元素.insert操作會(huì)將第一個(gè)參數(shù)返回。

?使 用 insert 的返回值

  • 通過(guò)使用insert 的返回值,可以在容器中一個(gè)特定位置反復(fù)插入元素:
  • list<string> 1st; auto iter = 1st.begin(); while (cin ? word) iter = 1st. insert (iter, word) ; // 等價(jià)于調(diào)用 push_front
  • 在循環(huán)之前,我們將iter初始化為lst.begin()。第一次調(diào)用insert會(huì)將我們剛剛讀入的string插入到iter所指向的元素之前的位置。insert返回的迭代器恰好指向這個(gè)新元素。我們將此迭代器賦予iter并重復(fù)循環(huán),讀取下一個(gè)單詞。只要繼續(xù)有單詞讀入,每步while循環(huán)就會(huì)將一個(gè)新元素插入到iter之前,并將iter改變?yōu)樾录尤朐氐奈恢谩4嗽貫?新的)首元素。因此,每步循環(huán)將一個(gè)新元素插入到list首元素之前的位置。

使用emplace操作

  • 新標(biāo)準(zhǔn)引入了三個(gè)新成員emplace_front、emplace和emplace_back這些操作構(gòu)造而不是拷貝元素。這些操作分別對(duì)應(yīng)push_front、insert和push_back,允許我們將元素放置在容器頭部、一個(gè)指定位置之前或容器尾部。當(dāng)調(diào)用push或insert成員函數(shù)時(shí),我們將元素類型的對(duì)象傳遞給它們,這些對(duì)象被拷貝到容器中。而當(dāng)我們調(diào)用一個(gè)emplace成員函數(shù)時(shí),則是將參數(shù)傳遞給元素類型的構(gòu)造函數(shù)。
  • emplace成員使用這些參數(shù)在容器管理的內(nèi)存空間中直接構(gòu)造元素。之前的是(push_front、insert和push_back)對(duì)元素的拷貝,浪費(fèi)內(nèi)存。例如,假定c保存Sales_data(參見7.1.4節(jié),第237頁(yè))元素:
  • //在c的末尾構(gòu)造一個(gè)Sales_data對(duì)象
  • c.emplace_back("978-0590353403",25,15.99);??//使用三個(gè)參數(shù)的Sales_data構(gòu)造函數(shù)
  • c.push_back(M978-0590353403n,25,15.99);? ? ?//錯(cuò)誤:沒有接受三個(gè)參數(shù)的push_back版本
  • c.push_back(Sales_data(H978-0590353403n,25,15.99));? ??//正確:創(chuàng)建一個(gè)臨時(shí)的Sales_data對(duì)象傳遞給push_back
  • 其中對(duì)emplace_back的調(diào)用和第二個(gè)push_back調(diào)用都會(huì)創(chuàng)建新的Sales_data對(duì)象。在調(diào)用emplace_back時(shí),會(huì)在容器管理的內(nèi)存空間中直接創(chuàng)建對(duì)象。而調(diào)用push_back則會(huì)創(chuàng)建一個(gè)局部臨時(shí)對(duì)象,并將其壓入容器中。emplace函數(shù)的參數(shù)根據(jù)元素類型而變化,參數(shù)必須與元素類型的構(gòu)造函數(shù)相匹配:
  • //iter指向c中一個(gè)元素,其中保存了Salesdata元素
  • c.emplace_back();//使用Sales_data的默認(rèn)構(gòu)造函數(shù)
  • c.emplace(iter,”999-999999999”);//使用Sales_data(string)
  • //使用Sales_data的接受一個(gè)ISBN、一個(gè)count和一個(gè)price的構(gòu)造函數(shù) c.emplace_front("978-0590353403**,25,15.99);
  • emplace函數(shù)在容器中直接構(gòu)造元素”傳遞給emplace函數(shù)的參數(shù)必須與元素類型的構(gòu)造函數(shù)相匹配“

9.3.2訪問元素?fù)?/span>

  • 表9.6列出了我們可以用來(lái)在順序容器中訪問元素的操作。如果容器中沒有元素,訪問操作的結(jié)果是未定義的。包括array在內(nèi)的每個(gè)順序容器都有一個(gè)front成員函數(shù),而除forward_list
  • 之外的所有順序容器都有一個(gè)back成員函數(shù)。這兩個(gè)操作分別返回首元素和尾元素的引用:
  • //在解引用一個(gè)迭代器或調(diào)用front或back之前檢查是否有元素
  • if(!c.empty())(
  • //val和val_2是c中第一個(gè)元素值的拷貝
  • auto val=*c.begin(),val2=c.front();
  • //val3和va_4是c中最后一個(gè)元素值的拷貝
  • auto last=c.end();auto val3=*(--last);//不能遞減forward_list迭代器
  • auto val4=c.back();//forward_list不支持
  • 此程序用兩種不同方式來(lái)獲取c中的首元素和尾元素的引用。直接的方法是調(diào)用front和back。而間接的方法是通過(guò)解引用begin返回的迭代器來(lái)獲得首元素的引用,以及通過(guò)遞減然后解引用end返回的迭代器來(lái)獲得尾元素的引用。
  • 這個(gè)程序有兩點(diǎn)值得注意:迭代器end指向的是容器尾元素之后的(不存在的)元素。為了獲取尾元素,必須首先遞減此迭代器。另一個(gè)重要之處是,在調(diào)用front和back(或解引用begin和end返回的迭代器)之前,要確保c非空。如果容器為空,if中操作的行為將是未定義的

訪問成員函數(shù)返回的是引用

  • 在容器中訪問元素的成員函數(shù)(即,front、back、下標(biāo)和at)返回的都是引用如果容器是一個(gè)const對(duì)象,則返回值是const的引用。如果容器不是const的,則返回值是普通引用,我們可以用來(lái)改變?cè)氐闹?#xff1a;
  • if(!c.empty())(
  • c.front()=42;? ? ? ? ? ? //將42賦予c中的第一個(gè)元素
  • auto&v=c.back();? ? //獲得指向最后一個(gè)元素的引用? &v指向c存儲(chǔ)的最后一個(gè)位置空間
  • v=1024;? ? ? ? ? ? ? ? ? //改變c中的元素
  • auto v2=c.back();? ?//v2不是一個(gè)引用,它是c.back()的一個(gè)拷貝 ,將c存儲(chǔ)的最后一個(gè)空間的數(shù)據(jù)復(fù)制一份到v2
  • v2=0;}? ? ? ? ? ? ? ? ? ? //未改變c中的元素
  • 與往常一樣,如果我們使用auto變量來(lái)保存這些函數(shù)的返回值(auto只需要一個(gè)元素,不需要很多),并且希望使用此變量來(lái)改變?cè)氐闹?#xff0c;必須記得將變量定義為引用類型

下標(biāo)操作和安全的隨機(jī)訪問

  • 提供快速隨機(jī)訪問的容器(string、vector、deque和array)也都提供下標(biāo)運(yùn)算符(參見3.3.3節(jié),第91頁(yè))。就像我們已經(jīng)看到的那樣,下標(biāo)運(yùn)算符接受一個(gè)下標(biāo)參數(shù),返問容器中該位置的元素的引用。給定下標(biāo)必須"在范圍內(nèi)”(即,大于等于0,且小于容器的大小)。保證下標(biāo)有效是程序員的責(zé)任,下標(biāo)運(yùn)算符并不檢查下標(biāo)是否在合法范圍內(nèi)。使用越界的下標(biāo)是一種嚴(yán)重的程序設(shè)計(jì)錯(cuò)誤,而且編譯器并不檢查這種錯(cuò)誤。
  • 如果我們希望確保下標(biāo)是合法的,可以使用at成員函數(shù)。at成員函數(shù)類似下標(biāo)運(yùn)算符,但如果下標(biāo)越界,at會(huì)拋出一個(gè)out_of_range異常(參見5.6節(jié),第173頁(yè)):
  • vector<string>svec;//空vector
  • cout<<svec[0];//運(yùn)行時(shí)錯(cuò)誤:svec中沒有元素!
  • cout<<svec.at(0);//拋出一個(gè)out_of_range異常

9.3.3刪除元素

  • 與添加元素的多種方式類似,(非array)容器也有多種刪除元素的方式。表9.7列出了這些成員函數(shù)

pop_front和pop_back成員函數(shù)

  • pop_front和pop_back成員函數(shù)分別刪除首元素和尾元素。與vector和string不支持push_front一樣,這些類型也不支持pop_front。類似的,forward_list不支持pop_back。與元素訪問成員函數(shù)類似,不能對(duì)一個(gè)空容器執(zhí)行彈出操作。
  • 這些操作返回void。如果你需要彈出的元素的值,就必須在執(zhí)行彈出操作之前保存它:
while(!list.empty()){process(ilist.front());// 對(duì)ilist的首個(gè)元素進(jìn)行一些處理ilist.pop_front();// 完成之后刪除首個(gè)元素 }

從容器內(nèi)部刪除一個(gè)元素

  • 成員函數(shù)erase從容器中指定位置刪除元素。我們可以刪除由一個(gè)迭代器指定的單個(gè)元素,也可以刪除由一對(duì)迭代器指定的范圍內(nèi)的所有元素兩種形式的erase都返回指向刪除的(最后一個(gè))元素之后位置的迭代器。即,若j是i之后的元素,那么erase(i)將返回指向j的迭代器,和insert相反,假設(shè)在i之前添加j,insert添加返回的是指向j的迭代器
  • 例如,下面的循環(huán)刪除一個(gè)list中的所有奇數(shù)元素
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; }
  • 每個(gè)循環(huán)步中,首先檢查當(dāng)前元素是否是奇數(shù)。如果是,就刪除該元素,并將it設(shè)置為我們所刪除的元素之后的元素。如果*it為偶數(shù),我們將it遞增,從而在下一步循環(huán)檢查下一個(gè)元素。

刪除多個(gè)元素

  • 接受一對(duì)迭代器的erase版本允許我們刪除一個(gè)范圍內(nèi)的元素:
  • //刪除兩個(gè)迭代器表示的范圍內(nèi)的元素
  • //返回指向最后一個(gè)被刪元素之后位置的送代器
  • elem1=slist.erase(elem1,elem2);//調(diào)用后,elem1==elem2
  • 迭代器elem1指向我們要?jiǎng)h除的第一個(gè)元素,elem2指向我們要?jiǎng)h除的最后一個(gè)元素之后的位置。
  • 為了刪除一個(gè)容器中的所有元素,我們既可以調(diào)用clear,也可以用begin和end獲得的迭代器作為參數(shù)調(diào)用erase:
  • slist.clear();//刪除容器中所有元素
  • slist.erase(slist.begin(),slist.end());//等價(jià)調(diào)用

9.3.4特殊的forwardJist操作

  • 為了理解forward_list為什么有特殊版本的添加和刪除操作,考慮當(dāng)我們從一個(gè)單向鏈表中刪除一個(gè)元素時(shí)會(huì)發(fā)生什么。如圖9.1所示,刪除一個(gè)元素會(huì)改變序列中的鏈接。
  • 在此情況下,刪除elem3也會(huì)改變elem2,elem2原來(lái)指向elem3.但刪除elem3后,elem2指向了elem4

  • 當(dāng)添加或刪除一個(gè)元素時(shí),刪除或添加的元素之前的那個(gè)元素的后繼會(huì)發(fā)生改變。為了添加或刪除一個(gè)元素,我們需要訪問其前驅(qū),以便改變前驅(qū)的鏈接。但是,forward_list是單向鏈表。在一個(gè)單向鏈表中,沒有簡(jiǎn)單的方法來(lái)獲取一個(gè)元素的前驅(qū)。出于這個(gè)原因,在一個(gè)forward_list中添加或刪除元素的操作是通過(guò)改變給定元素之后的元素來(lái)完成的。這樣,我們總是可以訪問到被添加或刪除操作所影響的元素。
  • 由于這些操作與其他容器上的操作的實(shí)現(xiàn)方式不同,forward_list并未定義insert、emplace和erase,而是定義了名為insert_after、emplace_after和erase_after的操作(參見表9.8)。例如,在我們的例子中,為了刪除elem3,應(yīng)該用指向elem2的迭代器調(diào)用erase_after。為了支持這些操作,forward_list也定義了before_begin,它返回一個(gè)首前(off-the-beginning)迭代器。這個(gè)迭代器允許我們?cè)阪湵硎自刂安⒉淮嬖诘脑?#34;之后”添加或刪除元素(亦即在鏈表首元素之前添加刪除元素)

  • 當(dāng)在forward_list中添加或刪除元素時(shí),我們必須關(guān)注兩個(gè)迭代器-----個(gè)指向我們要處理的元素,另一個(gè)指向其前驅(qū)。例如,可以改寫第312頁(yè)中從list中刪除奇數(shù)元素的循環(huán)程序,將其改為從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的第一個(gè)元素while(curr != flst.end){if(*flst % 2)curr = flst.erase_after(prev);else{prev = curr;//移動(dòng)迭代器curr 指向下一個(gè)元素 prev 指向curr之前的元素++curr;} }
  • 此例中,curr表示我們要處理的元素,prev表示curr的前驅(qū)。調(diào)用begin來(lái)初始化curr,這樣第一步循環(huán)就會(huì)檢查第一個(gè)元素是否是奇數(shù)。我們用before_begin來(lái)初始化prev,它返回指向curr之前不存在的元素的迭代器。當(dāng)找到奇數(shù)元素后,我們將prev傳遞給erase_after,此調(diào)用將prev之后的元素刪除,即,刪除curr指向的元素。然后我們將curr重置為erase_after的返回值,使得curr指向序列中下一個(gè)元素,prev保持不變,仍指向(新)curr之前的元素。如果curr指向的元素不是奇數(shù),在else中我們將兩個(gè)迭代器都向前移動(dòng)。

9.3.5改變?nèi)萜鞔笮?/span>

  • 如表9.9所描述,我們可以用resize來(lái)增大或縮小容器,與往常一樣,array不支持resize。如果當(dāng)前大小大于所要求的大小,容器后部的元素會(huì)被刪除;如果當(dāng)前大小小于新大小,會(huì)將新元素添加到容器后部:
  • list<int>ilist(10,42);//10個(gè)int:每個(gè)的值都是42
  • ilist.resize(15);//將5個(gè)值為0的元素添加到ilist的末尾
  • ilist.resize(25,-1);//將10個(gè)值為的元素添加到ilist的末尾從ilist末尾
  • ilist.resize(5);//刪除20個(gè)元素
  • resize操作接受一個(gè)可選的元素值參數(shù),用來(lái)初始化添加到容器中的元素。如果調(diào)用者未提供此參數(shù),新元素進(jìn)行值初始化(參見3.3.1節(jié),第88頁(yè))。如果容器保存的是類類型元素,且resize向容器添加新元素,則我們必須提供初始值,或者元素類型必須提供一個(gè)默認(rèn)構(gòu)造函數(shù)


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

  • 向容器中添加元素和從容器中刪除元素的操作可能會(huì)使指向容器元素的指針、引用或迭代器失效。一個(gè)失效的指針、引用或迭代器將不再表示任何元素。使用失效的指針、引用或迭代器是一種嚴(yán)重的程序設(shè)計(jì)錯(cuò)誤,很可能引起與使用未初始化指針一樣的問題(參見2.3.2節(jié),第49頁(yè))

在向容器添加元素后:

  • 如果容器是vector或string,且存儲(chǔ)空間被重新分配,則指向容器的迭代器、指針和引用都會(huì)失效(空間重新分配,比如當(dāng)前空間太小了,增大空間,但是空間不足,需要將整個(gè)容器移到別的地方重新開辟,指向先前的地址的迭代器、指針和引用就會(huì)失效。如果存儲(chǔ)空間未重新分配,指向插入位置之前的元素的迭代器、指針和引用仍有效,但指向插入位置之后元素的迭代器、指針和引用將會(huì)失效(和前面一樣,如果增大的空間不是很大,在原有的空間后面追加開辟空間,先前的仍然保留)。
  • 對(duì)于deque,插入到除首尾位置之外的任何位置都會(huì)導(dǎo)致迭代器、指針和引用失效。如果在首尾位置添加元素,迭代器會(huì)失效,但指向存在的元素的引用和指針不會(huì)失效。
  • 對(duì)于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指針和引用仍有效。
  • 當(dāng)我們從一個(gè)容器中刪除元素后,指向被刪除元素的迭代器、指針和引用會(huì)失效,這應(yīng)該不會(huì)令人驚訝。畢竟,這些元素都已經(jīng)被銷毀了。當(dāng)我們刪除一個(gè)元素后:
  • 對(duì)于list和forward_list,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指針仍有效
  • 對(duì)于deque,如果在首尾之外的任何位置刪除元素,那么指向被刪除元素外其他元素的迭代器、引用或指針也會(huì)失效。如果是刪除deque的尾元素,則尾后迭代器也會(huì)失效,但其他迭代器、引用和指針不受影響;如果是刪除首元素,這些也不會(huì)受影響。
  • 對(duì)于vector和string,指向被刪元素之前元素的迭代器、引用和指針仍有效。注意:當(dāng)我們刪除元素時(shí),尾后迭代器總是會(huì)失效
  • 因此必須保證每次改變?nèi)萜鞯牟僮髦蠖颊_地重新定位迭代器。這個(gè)建議對(duì)vector、string和deque尤為重要。

編寫改變?nèi)萜鞯难h(huán)程序

  • 添加/刪除vector、string或deque元素的循環(huán)程序必須考慮迭代器、引用和指針可能失效的問題。程序必須保證每個(gè)循環(huán)步中都更新迭代器、引用或指針。如果循環(huán)中調(diào)用的是insert或erase,那么更新迭代器很容易。這些操作都返回迭代器,我們可以用來(lái)更新:
//傻瓜循環(huán) 刪除偶數(shù)元素 復(fù)制每個(gè)奇數(shù)元素 vector<int> vi = {0,1,2,3,4,5,6,7,8,9}; auto iter = vi.begin();//使用begin,而不是cbegin,因?yàn)樾枰淖冊(cè)?while(iter != vi.end){if(*iter % 2){iter = vi.insert(iter,*iter);//復(fù)制當(dāng)前元素iter += 2;//跳過(guò)當(dāng)前元素 以及插入到他之前的元素}else{iter = vi.erase(iter);//刪除偶數(shù)元素//不需要移動(dòng)迭代器 iter刪除元素之后 指向刪除元素之后的元素} }
  • 此程序刪除vector中的偶數(shù)值元素,并復(fù)制每個(gè)奇數(shù)值元素。我們?cè)谡{(diào)用insert和 erase后都更新迭代器,因?yàn)閮烧叨紩?huì)使迭代器失效。 在調(diào)用erase后,不必遞增迭代器,因?yàn)閑rase返回的迭代器已經(jīng)指向序列中下一 個(gè)元素。調(diào)用insert后,需要遞增迭代器兩次。記住,insert在給定位置之前插入新 元素,然后返回指向新插入元素的迭代器。因此,在調(diào)用insert后,iter指向新插入 元素,位于我們正在處理的元素之前。我們將迭代器遞增兩次,恰好越過(guò)了新添加的元素和正在處理的元素,指向下一個(gè)未處理的元素

不要保存end返回的迭代器

  • 當(dāng)我們添加/刪除vector或 string的元素后,或在deque中首元素之外任何位置 添加/刪除元素后,原來(lái)end返回的迭代器總是會(huì)失效。因此,添加或刪除元素的循環(huán)程序必須反復(fù)調(diào)用end,而不能在循環(huán)之前保存end返回的迭代器,一直當(dāng)作容器末尾使用。通常C++標(biāo)準(zhǔn)庫(kù)的實(shí)現(xiàn)中end()操作都很快,部分就是因?yàn)檫@個(gè)原因。 例如,考慮這樣一個(gè)循環(huán),它處理容器中的每個(gè)元素,在其后添加一個(gè)新元素。我們希望循環(huán)能跳過(guò)新添加的元素,只處理原有元素。在每步循環(huán)之后,我們將定位迭代器,使其指向下一個(gè)原有元素。如果我們?cè)噲D“優(yōu)化”這個(gè)循環(huán),在循環(huán)之前保存end ()返回 的迭代器,一直用作容器末尾,就會(huì)導(dǎo)致一場(chǎng)災(zāi)難:
//災(zāi)難 此循環(huán)的行為是未定義的 auto begin = v.begin(); auto end = v.end(); //保存尾迭代器的數(shù)值是一個(gè)壞主意 while(begin != end){//做一些處理//傳入新值,對(duì)begin進(jìn)行重新賦值 否則他會(huì)失效++begin;// 向前移動(dòng)begin 因?yàn)槲覀兿朐谶@個(gè)元素之后插入元素begin = v.insert(begin,42);++begin;//向前移動(dòng),跳過(guò)新加入的元素 }
  • 此代碼的行為是未定義的。在很多標(biāo)準(zhǔn)庫(kù)實(shí)現(xiàn)上,此代碼會(huì)導(dǎo)致無(wú)限循環(huán)。問題在于我們將 end操作返回的迭代器保存在一個(gè)名為end的局部變量中。在循環(huán)體中,我們向容器中添加了一個(gè)元素,這個(gè)操作使保存在end中的迭代器失效了。這個(gè)迭代器不再指向v 中任何元素,或是v 中尾元素之后的位置。
  • 如果在一個(gè)循環(huán)中插入/刪除deque、string或vector中的元素,不要緩存end返回的迭代器。必須在每次插入操作后重新調(diào)用end(),而不能在循環(huán)開始前保存它返回的迭代器:
//更安全的做法是 每個(gè)循環(huán)添加/刪除元素之后 都重新計(jì)算end while(begin != end){++begin();// 向前移動(dòng)begin 因?yàn)橄朐诖嗽刂蟛迦朐豣egin = v.insert(begin,42);//插入新值++begin;//向前移動(dòng)begin 跳過(guò)新加入的元素 }

?

總結(jié)

以上是生活随笔為你收集整理的C++primer第九章 顺序容器 9.3 顺序容器操作的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。