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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

《C++ Primer 5th》笔记(9 / 19):顺序容器

發布時間:2023/12/13 c/c++ 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《C++ Primer 5th》笔记(9 / 19):顺序容器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

    • 順序容器概述
      • 確定使用哪種順序容器
    • 容器庫概覽
      • 迭代器
        • 迭代器范圍
        • 使用左閉合范圍蘊含的編程假定
      • 容器類型成員
      • begin和end成員
      • 容器定義和初始化
        • 將一個容器初始化為另一個容器的拷貝
        • 列表初始化
        • 與順序容器大小相關的構造函數
        • 標準庫array具有固定大小
      • 賦值和swap
        • 使用assign(僅順序容器)
        • 使用swap
      • 容器大小操作
      • 關系運算符
        • 容器的關系運算符使用元素的關系運算符完成比較
    • 順序容器操作
      • 向順序容器添加元素
        • 使用push_back
        • 關鍵概念:容器元素是拷貝
        • 使用push_front
        • 在容器中的特定位置添加元素
        • 插入范圍內元素
        • 使用insert的返回值
        • 使用emplace 操作
      • 訪問元素
        • 訪問成員函數返回的是引用
        • 下標操作和安全的隨機訪問
      • 刪除元素
        • pop_front和pop_back成員函數
        • 從容器內部刪除一個元素
        • 刪除多個元素
      • 特殊的forward_list操作
      • 改變容器大小
      • 容器操作可能使迭代器失效
        • 建議:管理迭代器
        • 編寫改變容器的循環程序
        • 不要保存end返回的迭代器
    • vector對象是如何增長
      • 管理容量的成員函數
      • capacity和size
    • 額外的string操作
      • 構造string的其他方法
        • substr操作
      • 改變string的其他方法
        • append和replace函數
        • 改變string 的多種重載函數
      • string搜索操作
        • 指定在哪里開始搜索
        • 逆向搜索
      • compare函數
      • 數值轉換
    • 容器適配器
      • 概述
        • 定義一個適配器
        • 棧適配器
        • 隊列適配器
    • 小結

一個容器就是一些特定類型對象的集合。順序容器(sequential container)為程序員提供了控制元素存儲和訪問順序的能力。這種順序不依賴于元素的值,而是與元素加入容器時的位置相對應。與之相對的,將在第11章介紹的有序和無序關聯容器,則根據關鍵字的值來存儲元素。

標準庫還提供了三種容器適配器,分別為容器操作定義了不同的接口,來與容器類型適配。我們將在本章末尾介紹適配器。

順序容器概述

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

  • 向容器添加或從容器中刪除元素的代價
  • 非順序訪問容器中元素的代價
名稱說明
vector可變大小數組。支持快速隨機訪問。在尾部之外的位置插入或刪除元素可能很慢
deque雙端隊列。支持快速隨機訪問。在頭尾位置插入/刪除速度很快
list雙向鏈表。只支持雙向順序訪問。在 list中任何位置進行插入/刪除操作速度都很快
forward_list單向鏈表。只支持單向順序訪問。在鏈表任何位置進行插入/刪除操作速度都很快
array固定大小數組。支持快速隨機訪問。不能添加或刪除元素
string與vector相似的容器,但專門用于保存字符。隨機訪問快。在尾部插入/刪除速度快

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

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

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

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

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

Note:新標準庫的容器比舊版本快得多,原因我們將在第13章解釋。新標準庫容器的性能幾乎肯定與最精心優化過的同類數據結構一樣好(通常會更好)。現代C++程序應該使用標準庫容器,而不是更原始的數據結構,如內置數組。

確定使用哪種順序容器

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

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

  • 除非你有很好的理由選擇其他容器,否則應使用vector。
  • 如果你的程序有很多小的元素,且空間的額外開銷很重要,則不要使用list或forward_list。
  • 如果程序要求隨機訪問元素,應使用vector或deque。
  • 如果程序要求在容器的中間插入或刪除元素,應使用list或forward_list。如果程序需要在頭尾位置插入或刪除元素,但不會在中間位置進行插入或刪除操作,則使用deque。(鏈表)
  • 如果程序只有在讀取輸入時才需要在容器中間位置插入元素,隨后需要隨機訪問元素,則
    • 首先,確定是否真的需要在容器中間位置添加元素。當處理輸入數據時,通常可以很容易地向vector追加數據,然后再調用標準庫的sort函數(第10章內容)來重排容器中的元素,從而避免在中間位置添加元素。
    • 如果必須在中間位置插入元素,考慮在輸入階段使用list,一旦輸入完成,將list中的內容拷貝到一個vector中。

如果程序既需要隨機訪問元素,又需要在容器中間位置插入元素,那該怎么辦?答案取決于在list或forward_list 中訪問元素與vector或deque中插入/刪除元素的相對性能。一般來說,應用中占主導地位的操作(執行的訪問操作更多還是插入/刪除更多)決定了容器類型的選擇。在此情況下,對兩種容器分別測試應用的性能可能就是必要的了。

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

容器庫概覽

容器類型上的操作形成了一種層次:

  • 某些操作是所有容器類型都提供的(參見下表)。
  • 另外一些操作僅針對順序容器(本章內容)、關聯容器(第11章)或無序容器(第11章)。
  • 還有一些操作只適用于一小部分容器。
操作說明
類型別名
iterator此容器類型的迭代器類型
const_iterator可以讀取元素,但不能修改元素的迭代器類型
size_type無符號整數類型,足夠保存此種容器類型最大可能容器的大小
difference_type帶符號整數類型,足夠保存兩個迭代器之間的距離
value_type元素類型
reference元素的左值類型;與value_type&含義相同
const_reference元素的const左值類型(即,const value_type&)
構造函數
C c;默認構造函數,構造空容器(array)
C c1(c2);構造c2的拷貝c1
C c(b,e);構造c,將迭代器b和e指定的范圍內的元素拷貝到c(array不支持)
C c{a, b, c…};列表初始化c
賦值與swap
cl = c2將c1中的元素替換為c2中元素
c1 = {a, b, c…}將c1中的元素替換為列表中元素(不適用于array)
a.swap(b)交換a和b的元素
swap(a, b)與a.swap(b)等價
大小
c.size()c中元素的數目(不支持forward_list)
c.max_size()c可保存的最大元素數目
c.empty()若c中存儲了元素,返回false,否則返回true
添加/刪除元素(不適用于array)
注:在不同容器中,這些操作的接口都不同
c.insert(args)將args中的元素拷貝進c
c.emplace(inits)使用inits構造c中的一個元素
c.erase(args)刪除args指定的元素
c.clear()刪除c中的所有元素,返回void
關系運算符
==,!=所有容器都支持相等(不等)運算符
<,<=,>,>=關系運算符(無序關聯容器不支持)
獲取迭代器
c.begin(),c.end()返回指向c的首元素和尾元素之后位置的迭代器
c.cbegin(),c.cend()返回const_iterator
反向容器的額外成員(不支持forward_list)
reverse_iterator按逆序尋址元素的迭代器
const_reverse_iterator不能修改元素的逆序迭代器
c.rbegin(),c.rend()返回指向c的尾元素和首元素之前位置的迭代器
c.crbegin(),c.crend()返回const_reverse_iterator

在本節中,我們將介紹對所有容器都適用的操作。本章剩余部分將聚焦于僅適用于順序容器的操作。關聯容器特有的操作將在第11章介紹。

一般來說,每個容器都定義在一個頭文件中,文件名與類型名相同。即,deque定義在頭文件deque 中,list定義在頭文件list中,以此類推。容器均定義為模板類。例如對vector,我們必須提供額外信息來生成特定的容器類型。對大多數,但不是所有容器,我們還需要額外提供元素類型信息:

list<Sales_data> //保存sales_data對象的list deque<double> //保存double的deque

對容器可以保存的元素類型的限制

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

vector<vector<string>> lines; //vector的vector

此處lines是一個vector,其元素類型是string的vector。

Note:較舊的編譯器可能需要在兩個尖括號之間鍵入空格,例如,vector<vector<string> >。

雖然我們可以在容器中保存幾乎任何類型,但某些容器操作對元素類型有其自己的特殊要求。我們可以為不支持特定操作需求的類型定義容器,但這種情況下就只能使用那些沒有特殊要求的容器操作了。

例如,順序容器構造函數的一個版本接受容器大小參數,它使用了元素類型的默認構造函數。但某些類沒有默認構造函數。我們可以定義一個保存這種類型對象的容器,但我們在構造這種容器時不能只傳遞給它一個元素數目參數:

// assume noDefault is a type without a default constructor vector<noDefault> v1(10, init); // ok: element initializer supplied vector<noDefault> v2(10); // error: must supply an element initializer

迭代器

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

例如,標準容器類型上的所有迭代器都允許我們訪問容器中的元素,而所有迭代器都是通過解引用運算符來實現這個操作的。類似的,標準庫容器的所有迭代器都定義了遞增運算符,從當前元素移動到下一個元素。

下表關于容器迭代器支持的所有操作,其中有一個例外不符合公共接口特點——forward_list迭代器不支持遞減運算符(–)。(forward_list是單向鏈表)

..
*iter返回迭代器iter所指元素的引用
iter->mem解引用iter并獲取該元素的名為mem的成員,等價于(*iter).mem
++iter令iter指示容器中的下一個元素
–iter令iter指示容器中的上一個元素
iter1 == iter2判斷兩個迭代器是否相等,
如果兩個迭代器指示的是同一個元素
或者它們是同一個容器的尾后迭代器,則相等;反之,不相等
iter1 != iter2判斷兩個迭代器是否不相等

下表關于迭代器支持的算術運算,這些運算只能應用于string、vector、deque和 array的迭代器。我們不能將它們用于其他任何容器類型的迭代器。

..
iter + n迭代器加上一個整數值仍得一個迭代器,
迭代器指示的新位置與原來相比向前移動了若干個元素。
結果迭代器或者指示容器內的一個元素,或者指示容器尾元素的下一位置
iter - n迭代器減去一個整數值仍得一個迭代器,
迭代器指示的新位置與原來相比向后移動了若干個元素。
結果迭代器或者指示容器內的一個元素,或者指示容器尾元素的下一位置
iter1 += n迭代器加法的復合賦值語句,將iter1加n的結果賦給iter1
iter1 -= n迭代器減法的復合賦值語句,將iter1減n的結果賦給iter1
iter1 - iter2兩個迭代器相減的結果是它們之間的距離,也就是說,
將運算符右側的迭代器向前移動差值個元素后將得到左側的迭代器。
參與運算的兩個迭代器必須指向的是同一個容器中的元素或者尾元素的下一位置
>、>=、<、<=迭代器的關系運算符,如果某迭代器指向的容器位置在另一個迭代器所指位置之前,
則說前者小于后者。參與運算的兩個迭代器必須指向的是同一個容器中的元素或者尾元素的下一位置

迭代器范圍

Note:迭代器范圍的概念是標準庫的基礎。

一個迭代器范圍(iterator range)由一對迭代器表示,兩個迭代器分別指向同一個容器中的元素或者是尾元素之后的位置(one past the last element)。這兩個迭代器通常被稱為begin和end,或者是first和last(可能有些誤導),它們標記了容器中元素的一個范圍。

雖然第二個迭代器常常被稱為last,但這種叫法有些誤導,因為第二個迭代器從來都不會指向范圍中的最后一個元素,而是指向尾元素之后的位置。迭代器范圍中的元素包含first所表示的元素以及從first開始直至last(但不包含last)之間的所有元素。

這種元素范圍被稱為左閉合區間(left-inclusive interval),其標準數學描述為[begin,end)。

表示范圍自begin開始,于end之前結束。迭代器begin和end必須指向相同的容器。end可以與begin指向相同的位置,但不能指向begin之前的位置。

對構成范圍的迭代器的要求

如果同時滿足如下條件,兩個迭代器begin和end構成一個迭代器范圍:

  • 它們指向同一個容器中的元素,或者是容器最后一個元素之后的位置,
  • 我們可以通過反復遞增begin來到達end。換句話說,end不在begin之前。

WARNING:編譯器不會強制這些要求。確保程序符合這些約定是程序員的責任。

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

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

  • 如果begin與end相等,則范圍為空。

  • 如果begin與end不等,則范圍至少包含一個元素,且 begin指向該范圍中的第一個元素。

  • 我們可以對begin遞增若干次,使得begin==end

這些性質意味著我們可以像下面的代碼一樣用一個循環來處理一個元素范圍,而這是安全的:

while (begin != end){*begin = val;//正確:范圍非空,因此begin指向一個元素++begin;//移動迭代器,獲取下一個元素 }

容器類型成員

每個容器都定義了多個類型,如在容器庫概覽的表格所示。在第3章,已經使用過其中三種:

  • size_type
  • iterator
  • const_iterator
  • 除了已經使用過的迭代器類型,大多數容器還提供反向迭代器。簡單地說,反向迭代器就是一種反向遍歷容器的迭代器,與正向迭代器相比,各種操作的含義也都發生了顛倒。例如,對一個反向迭代器執行++操作,會得到上一個元素。我們將在第10章介紹更多關于反向迭代器的內容。

    剩下的就是類型別名了,通過類型別名,我們可以在不了解容器中元素類型的情況下使用它。如果需要元素類型,可以使用容器的value_type。如果需要元素類型的一個引用,可以使用reference或const_reference。這些元素相關的類型別名在泛型編程中非常有用,我們將在16章中介紹相關內容。

    為了使用這些類型,我們必須顯式使用其類名:

    // iter是通過list<string>定義的一個迭代器類型 list<string>::iterator iter;// count是通過vector<int>定義的一個difference_type類型 vector<int>::difference_type count;//帶符號整數類型,足夠保存兩個迭代器之間的距離

    這些聲明語句使用了作用域運算符來說明我們希望使用list<string>類的iterator成員及vector<int>類定義的difference_type。

    begin和end成員

    begin和end操作生成指向容器中第一個元素和尾元素之后位置的迭代器。這兩個迭代器最常見的用途是形成一個包含容器中所有元素的迭代器范圍。

    如在容器庫概覽的表格所示,begin和end有多個版本:帶r的版本返回反向迭代器(見第10章);以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

    不以c開頭的函數都是被重載過的。

    也就是說,實際上有兩個名為begin的成員。一個是const成員,返回容器的const_iterator類型。另一個是非常量成員,返回容器的iterator類型。rbegin、end和rend的情況類似。

    當我們對一個非常量對象調用這些成員時,得到的是返回 iterator 的版本。只有在對一個const對象調用這些函數時,才會得到一個const版本。與const指針和引用類似,可以將一個普通的iterator轉換為對應的const_iterator,但反之不行。

    以c開頭的版本是C++新標準引入的,用以支持auto與begin和end函數結合使用。過去,沒有其他選擇,只能顯式聲明希望使用哪種類型的迭代器:

    // type is explicitly specified list<string>::iterator it5 = a.begin(); list<string>::const_iterator it6 = a.begin(); // iterator or const_iterator depending on a's type of a auto it7 = a.begin(); // const_iterator only if a is const auto it8 = a.cbegin(); // it8 is const_iterator

    當auto與begin或end結合使用時,獲得的迭代器類型依賴于容器類型,與我們想要如何使用迭代器毫不相干。但以c開頭的版本還是可以獲得const_iterator的,而不管容器的類型是什么。

    Best Practies:當不需要寫訪問時,應使用 cbegin和cend。

    容器定義和初始化

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

    構造器說明
    C c;默認構造函數。如果c是一個array,則c中元素按默認方式初始化;否則c為空
    C c1(c2)c1初始化為c2的拷貝。c1和c2必須是相同類型(即,它們必須是相同的容器類型,且保存的是相同的元素類型;對于array類型,兩者還必須具有相同大小)
    C c1=c2同上條
    C c{a,b,c…}c初始化為初始化列表中元素的拷貝。列表中元素的類型必須與c的元素類型相容。對于array類型,列表中元素數目必須等于或小于array的大小,任何遺漏的元素都進行值初始化(參見第3章)
    C c={a,b,c…}同上條
    C c(b, e)c初始化為迭代器b和e指定范圍中的元素的拷貝。范圍中元素的類型必須與c的元素類型相容(array不適用)
    只有順序容器(不包括array)的構造函數才能接受大小參數
    C seq(n)seq包含n個元素,這些元素進行了值初始化;此構造函數是explicit的。(string不適用)
    C seq(n,t)seq包含n個初始化為值t的元素

    將一個容器初始化為另一個容器的拷貝

    將一個新容器創建為另一個容器的拷貝的方法有兩種:可以直接拷貝整個容器,或者
    (array除外)拷貝由一個迭代器對指定的元素范圍。

    為了創建一個容器為另一個容器的拷貝,兩個容器的類型及其元素類型必須匹配。不過,當傳遞迭代器參數來拷貝一個范圍時,就不要求容器類型是相同的了。而且,新容器和原容器中的元素類型也可以不同,只要能將要拷貝的元素轉換為要初始化的容器的元素類型即可。

    // each container has three elements, initialized from the given initializers list<string> authors = {"Milton", "Shakespeare", "Austen"}; vector<const char*> articles = {"a", "an", "the"}; list<string> list2(authors); // ok: types match deque<string> authList(authors); // error: container types don't match vector<string> words(articles); // error: element types must match // ok: converts const char* elements to string forward_list<string> words(articles.begin(), articles.end());

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

    接受兩個迭代器參數的構造函數用這兩個迭代器表示我們想要拷貝的一個元素范圍。與以往一樣,兩個迭代器分別標記想要拷貝的第一個元素和尾元素之后的位置。新容器的大小與范圍中元素的數目相同。新容器中的每個元素都用范圍中對應元素的值進行初始化。

    由于兩個迭代器表示一個范圍,因此可以使用這種構造函數來拷貝一個容器中的子序列。例如,我們可以編寫如下代碼:

    //拷貝元素,直到(但不包括)it指向的元素 deque<string> authList(authors.begin(), it);//假定迭代器it表示authors中的一個元素

    列表初始化

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

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

    當這樣做時,我們就顯式地指定了容器中每個元素的值。對于除array之外的容器類型,初始化列表還隱含地指定了容器的大小:容器將包含與初始值一樣多的元素。

    與順序容器大小相關的構造函數

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

    vector<int> ivec(10, -1); // ten int elements, each initialized to -1 list<string> svec(10, "hi!"); // ten strings; each element is "hi!" forward_list<int> ivec(10); // ten elements, each initialized to 0 deque<string> svec(10); // ten elements, each an empty string

    如果元素類型是內置類型或者是具有默認構造函數的類類型,可以只為構造函數提供一個容器大小參數。如果元素類型沒有默認構造函數,除了大小參數外,還必須指定一個顯式的元素初始值。

    Note:只有順序容器的構造函數才接受大小參數,關聯容器并不支持。

    標準庫array具有固定大小

    與內置數組一樣,標準庫array的大小也是類型的一部分。當定義一個array時,除了指定元素類型,還要指定容器大小:

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

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

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

    由于大小是array類型的一部分,array不支持普通的容器構造函數。這些構造函數都會確定容器的大小,要么隱式地,要么顯式地。而允許用戶向一個 array構造函數傳遞大小參數,最好情況下也是多余的,而且容易出錯。

    array大小固定的特性也影響了它所定義的構造函數的行為。與其他容器不同,一個默認構造的array是非空的:它包含了與其大小一樣多的元素。這些元素都被默認初始化,就像一個內置數組中的元素那樣。

    如果我們對array進行列表初始化,初始值的數目必須等于或小于array的大小。如果初始值數目小于array的大小,則它們被用來初始化array中靠前的元素,所有剩余元素都會進行值初始化。在這兩種情況下,如果元素類型是一個類類型,那么該類必須有一個默認構造函數,以使值初始化能夠進行:

    array<int, 10> ial;//10個默認初始化的int array<int, 10> ia2 = {0,1,2,3,4,5,6,7,8,9};//列表初始化 array<int,10> ia3 ={42}; //ia3 [0]為42,剩余元素為0

    值得注意的是,雖然我們不能對內置數組類型進行拷貝或對象賦值操作,但array并無此限制:

    int digs[10] = {0,1,2,3,4,5,6,7,8,9}; int cpy[10] = digs;//錯誤:內置數組不支持拷貝或賦值 array<int,10> digits = {0,1,2,3,4,5,6,7,8,9 }; array<int,10> copy = digits; //正確:只要數組類型匹配即合法

    與其他容器一樣,array也要求初始值的類型必須與要創建的容器類型相同。此外,array還要求元素類型和大小也都一樣,因為大小是array類型的一部分。

    賦值和swap

    容器賦值運算說明
    c1=c2將c1中的元素替換為c2中元素的拷貝。c1和c2必須具有相同的類型
    c={a,b,c…}將c1中元素替換為初始化列表中元素的拷貝(array不適用)
    swap(c1, c2)交換c1和c2中的元素。c1和c2必須具有相同的類型。swap通常比從c2向c1拷貝元素快得多
    cl.swap(c2)同上條
    assign操作不適用于關聯容器和array
    seq.assign(b, e)將seq中的元素替換為迭代器b和e所表示的范圍中的元素。迭代器b和e不能指向seq中的元素
    seq.assign(il)將seq中的元素替換為初始化列表il中的元素
    seq.assign(n, t)將seq中的元素替換為n個值為t的元素

    WARNING:賦值相關運算會導致指向左邊容器內部的迭代器、引用和指針失效。而swap操作將容器內容交換不會導致指向容器的迭代器、引用和指針失效(容器類型為array和string的情況除外)。

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

    c1 = c2;//將c1的內容替換為c2中元素的拷貝 c1 = {a,b,c};//賦值后,c1大小為3

    第一個賦值運算后,左邊容器將與右邊容器相等。如果兩個容器原來大小不同,賦值運算后兩者的大小都與右邊容器的原大小相同。第二個賦值運算后,c1的size變為3,即花括號列表中值的數目。

    與內置數組不同,標準庫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操作用參數所指定的元素(的拷貝)替換左邊容器中的所有元素。例如,我們可以用assgin實現將一個vector中的一段char*值賦予一個list中的string:

    list<string> names; vector<const char*> oldstyle; names = oldstyle; // error: container types don't match // ok: can convert from const char*to string names.assign(oldstyle.cbegin(), oldstyle.cend());

    這段代碼中對assign的調用將names中的元素替換為迭代器指定的范圍中的元素的拷貝。assign的參數決定了容器中將有多少個元素以及它們的值都是什么。

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

    assign的第二個版本接受一個整型值和一個元素值。它用指定數目且具有相同給定值的元素替換容器中原有的元素:

    // equivalent to slist1.clear(); // followed by slist1.insert(slist1.begin(), 10, "Hiya!"); list<string> slist1(1); // one element, which is the empty string slist1.assign(10, "Hiya!"); // ten elements; each one is Hiya !

    使用swap

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

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

    調用swap后,svecl將包含24個string元素,svec2將包含10個string.除array外,交換兩個容器內容的操作保證會很快——-元素本身并未交換,swap只是交換了兩個容器的內部數據結構。

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

    元素不會被移動的事實意味著,除string外,指向容器的迭代器、引用和指針在swap操作之后都不會失效。它們仍指向swap操作之前所指向的那些元素。但是,在swap之后,這些元素已經屬于不同的容器了。例如,假定iter在swap之前指向svec1[3]的string,那么在swap之后它指向svec2[3]的元素。與其他容器不同,對一個string調用swap會導致迭代器、引用和指針失效。

    與其他容器不同,swap兩個array會真正交換它們的元素。因此,交換兩個array所需的時間與array中元素的數目成正比。

    因此,對于array,在swap操作之后,指針、引用和迭代器所綁定的元素保持不變,但元素值已經與另一個array 中對應元素的值進行了交換。

    在新標準庫中,容器既提供成員函數版本的swap,也提供非成員版本的swap。而早期標準庫版本只提供成員函數版本的swap。非成員版本的swap在泛型編程中是非常重要的。統一使用非成員版本的swap是一個好習慣。

    容器大小操作

    除了一個例外,每個容器類型都有三個與大小相關的操作。

  • 成員函數size(參見第3章)返回容器中元素的數目;
  • empty當size為0時返回布爾值true,否則返回 false;
  • max_size返回一個大于或等于該類型容器所能容納的最大元素數的值。
  • forward_list支持max_size和empty,但不支持size,原因我們將在下一節解釋。

    關系運算符

    每個容器類型都支持相等運算符(==和!=);除了無序關聯容器外的所有容器都支持關系運算符(>、>=、<、<=)。關系運算符左右兩邊的運算對象必須是相同類型的容器,且必須保存相同類型的元素。即,我們只能將一個vector<int>與另一個vector<int>進行比較,而不能將一個vector<int>與一個list<int>或一個vector<double>進行比較。

    比較兩個容器實際上是進行元素的逐對比較。這些運算符的工作方式與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 and v2 differ at element [2]: v1[2] is less than v2[2] v1 < v3 // false; all elements are equal, but v3 has fewer of them; v1 == v4 // true; each element is equal and v1 and v4 have the same size() v1 == v2 // false; v2 has fewer elements than v1

    容器的關系運算符使用元素的關系運算符完成比較

    Note:只有當其元素類型也定義了相應的比較運算符時,我們才可以使用關系運算符來比較兩個容器。

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

    例如,我們在第7章中定義的Sales_data類型并未定義==和<運算。因此,就不能比較兩個保存Sales_data元素的容器:

    vector<Sales_data> storeA, storeB; if (storeA < storeB) // error: Sales_data has no less-than operator

    順序容器操作

    順序容器和關聯容器的不同之處在于兩者組織元素的方式。這些不同之處直接關系到了元素如何存儲、訪問、添加以及刪除。上一節介紹了所有容器都支持的操作。本章剩余部分將介紹順序容器所特有的操作。

    向順序容器添加元素

    除array外,所有標準庫容器都提供靈活的內存管理。在運行時可以動態添加或刪除元素來改變容器大小。下表列出了向順序容器(非array)添加元素的操作。

    注意:

    • 這些操作會改變容器的大小; array不支持這些操作。
    • forward_list有自己專有版本的insert 和emplace;
    • forward_list不支持push_back 和emplace_back。
    • vector和string不支持push_front和emplace_front。
    • 向一個vector、string或deque插入元素會使所有指向容器的迭代器、引用和指針失效。

    向順序容器添加元素的操作

    操作說明
    c.push_back(t)在c的尾部創建一個值為t或由args 創建的元素。返回void
    c.emplace_back(args)同上一條
    c.push_front(it)在c的頭部創建一個值為t或由args 創建的元素。返回void
    c.emplace_front(args)同上一條
    c.insert(p,t)在迭代器p指向的元素之前創建一個值為t或由args創建的元素。返回指向新添加的元素的迭代器
    c.emplace(p, args)同上一條
    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的首尾之外的任何位置添加元素,都需要移動元素。而且,向一個vector或string添加元素可能引起整個對象存儲空間的重新分配。重新分配一個對象的存儲空間需要分配新的內存,并將元素從舊的空間移動到新的空間中。

    (MyNote:盲猜vector和deque底層是用數組實現。)

    使用push_back

    在第3章,看到push_back將一個元素追加到一個vector的尾部。除 array和forward_list之外,每個順序容器(包括string類型)都支持push_back。

    例如,下面的循環每次讀取一個string 到 word中,然后追加到容器尾部:

    //從標準輸入讀取數據,將每個單詞放到容器末尾 string word; while (cin >> word)container.push_back (word);

    對push_back的調用在container尾部創建了一個新的元素,將container的size增大了1。該元素的值為word的一個拷貝。container的類型可以是list、vector或deque。
    由于string是一個字符容器,我們也可以用push_back在string末尾添加字符:

    void pluralize(size_t cnt, string &word){ if (cnt > 1)word.push_back ('s'); //等價于word += 's' }

    關鍵概念:容器元素是拷貝

    當我們用一個對象來初始化容器時,或將一個對象插入到容器中時,實際上放入到容器中的是對象值的一個拷貝,而不是對象本身。就像我們將一個對象傳遞給非引用參數一樣,容器中的元素與提供值的對象之間沒有任何關聯。隨后對容器中元素的任何改變都不會影響到原始對象,反之亦然。

    使用push_front

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

    list<int> ilist; //將元素添加到ilist開頭 for (size_t ix = 0; ix != 4; ++ix)ilist.push_front (ix) ;

    此循環將元素0、1、2、3添加到ilist頭部。每個元素都插入到list的新的開始位置(new beginning)。即,當我們插入1時,它會被放置在0之前,2被放置在1之前,依此類推。因此,在循環中以這種方式將元素添加到容器中,最終會形成逆序。在循環執行完畢后,ilist保存序列3、2、1、0。

    注意,deque像vector一樣提供了隨機訪問元素的能力,但它提供了vector所
    不支持的push_front。deque 保證在容器首尾進行插入和刪除元素的操作都只花費常數時間。與vector一樣,在deque首尾之外的位置插入元素會很耗時。

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

    push_back和push_front操作提供了一種方便地在順序容器尾部或頭部插入單個元素的方法。insert成員提供了更一般的添加功能,它允許我們在容器中任意位置插入0個或多個元素。vector、deque、list和string都支持insert成員。forward_list提供了特殊版本的insert成員,隨后介紹。

    每個insert函數都接受一個迭代器作為其第一個參數。迭代器指出了在容器中什么位置放置新元素。它可以指向容器中任何位置,包括容器尾部之后的下一個位置。由于迭代器可能指向容器尾部之后不存在的元素的位置,而且在容器開始位置插入元素是很有用的功能,所以insert函數將元素插入到迭代器所指定的位置之前。

    例如,下面的語句

    slist.insert(iter,"Hello!");//將"Hello! "添加到iter之前的位置

    將一個值為"Hello"的string插入到iter指向的元素之前的位置。

    雖然某些容器不支持 push_front操作,但它們對于insert操作并無類似的限制(插入開始位置)。因此我們可以將元素插入到容器的開始位置,而不必擔心容器是否支持push_front:

    vector<string> svec; list<string> slist; //等價于調用slist.push_front("Hello! "); slist.insert(slist.begin(), "Hello! ");//vector不支持push_front,但我們可以插入到begin()之前 //警告:插入到vector末尾之外的任何位置都可能很慢 svec.insert(svec.begin() , "Hello ! " );

    WARNING:將元素插入到vector、deque和string中的任何位置都是合法的。然而,這樣做可能很耗時。

    插入范圍內元素

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

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

    這行代碼將10個元素插入到svec的末尾,并將所有元素都初始化為string “Anna”。

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

    vector<string> v = {"quasi", "simba", "frollo", "scar"}; //將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一對迭代器,它們不能指向添加元素的目標容器。

    在新標準下,接受元素個數或范圍的insert版本返回指向第一個新加入元素的迭代器。(在舊版本的標準庫中,這些操作返回void。)如果范圍為空,不插入任何元素,insert操作會將第一個參數返回。

    使用insert的返回值

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

    list<string> lst; auto iter = lst.begin();while (cin >> word)//insert返回的迭代器恰好指向新元素。iter = lst.insert(iter, word); //等價于調用push_front

    Note:理解這個循環是如何工作的非常重要,特別是理解這個循環為什么等價于調用push_front尤為重要。

    使用emplace 操作

    新標準引入了三個新成員:

  • emplace_front、
  • emplace
  • emplace_back,
  • 這些操作構造而不是拷貝元素。

    這些操作分別對應

  • push_front、
  • insert
  • push_back,
  • 允許我們將元素放置在容器頭部、一個指定位置之前或容器尾部。

    當調用push或insert成員函數時,我們將元素類型的對象傳遞給它們,這些對象被拷貝到容器中。而當我們調用一個emplace成員函數時,則是將參數傳遞給元素類型的構造函數。emplace 成員使用這些參數在容器管理的內存空間中直接構造元素。例如,假定c保存Sales_data(參見第7章)元素:

    //在c的末尾構造一個Sales_data對象 //使用三個參數的sales_data構造函數 c.emplace_back("978-0590353403", 25, 15.99);//錯誤:沒有接受三個參數的push_back版本 c.push_back("978-0590353403", 25, 15.99);//正確:創建一個臨時的sales_data對象傳遞給push_back c.push_back(Sales_data("978-0590353403"2515.99));

    其中對emplace_back的調用和第二個push_back調用都會創建新的Sales_data對象。在調用emplace_back 時,會在容器管理的內存空間中直接創建對象。而調用push_back則會創建一個局部臨時對象,并將其壓入容器中。

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

    //iter指向c中一個元素,其中保存了sales_data元素 c.emplace_back() ; //使用sales_data的默認構造函數 c.emplace(iter,"999-999999999");//使用sales_data (string)//使用sales_data的接受一個工SBN、一個count和一個price的構造函數 c.emplace_front("978-0590353403"2515.99);

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

    訪問元素

    下表列出了我們可以用來在順序容器中訪問元素的操作。如果容器中沒有元素,訪問操作的結果是未定義的。

    注意:

    • at和下標操作只適用于string、vector、 deque和array。
    • back不適用于forward_list。
    • 對一個空容器調用 front和back,就像使用一個越界的下標一樣,是一種嚴重的程序設計錯誤。
    操作說明
    c.back()返回c中尾元素的引用。若c為空,函數行為未定義
    c.front()返回c中首元素的引用。若c為空,函數行為未定義
    c[n]返回c中下標為n的元素的引用,n是一個無符號整數。若n>=c.size(),則函數行為未定義
    c.at(n)返回下標為n的元素的引用。如果下標越界,則拋出一out_of_range異常

    包括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不支持 }

    此程序用兩種不同方式來獲取c中的首元素和尾元素的引用。直接的方法是調用front和 back。而間接的方法是通過解引用begin返回的迭代器來獲得首元素的引用,以及通過遞減然后解引用end返回的迭代器來獲得尾元素的引用。

    這個程序有兩點值得注意:

  • 迭代器end指向的是容器尾元素之后的(不存在的)元素。為了獲取尾元素,必須首先遞減此迭代器。
  • 另一個重要之處是在調用front和back(或解引用begin和 end返回的迭代器)之前,要確保c非空。如果容器為空,if中操作的行為將是未定義的。
  • 訪問成員函數返回的是引用

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

    if (!c.empty()){c.front () = 42;//將42賦予c中的第一個元素auto &v = c.back ();//獲得指向最后一個元素的引用v= 1024;//改變c中的元素auto v2 = c.back () ;// v2不是一個引用,它是c.back ()的一個拷貝v2 = 0;//未改變c中的元素 }

    與往常一樣,如果我們使用auto變量來保存這些函數的返回值,并且希望使用此變量來改變元素的值,必須記得將變量定義為引用類型

    下標操作和安全的隨機訪問

    提供快速隨機訪問的容器(string、vector、deque和 array)也都提供下標運算符。就像我們已經看到的那樣,下標運算符接受一個下標參數,返回容器中該位置的元素的引用。給定下標必須“在范圍內”(即,大于等于0,且小于容器的大小)。

    保證下標有效是程序員的責任,下標運算符并不檢查下標是否在合法范圍內。使用越界的下標是一種嚴重的程序設計錯誤,而且編譯器并不檢查這種錯誤。

    如果我們希望確保下標是合法的,可以使用at成員函數。at成員函數類似下標運算符,但如果下標越界,at會拋出一個out_of_range異常:

    vector<string> svec;//空vector cout << svec[0];//運行時錯誤:svec中沒有元素! cout << svec.at(0);//拋出一個out_of_range異常

    刪除元素

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

    注意:

    • 這些操作會改變容器的大小,所以不適用于array。
    • forward_list有特殊版本的erase,隨后介紹。
    • forward_list不支持pop_back; vector和string不支持pop_front。
    • 刪除deque中除首尾位置之外的任何元素都會使所有迭代器、引用和指針失效。指向vector或string中刪除點之后位置的迭代器、引用和指針ING都會失效。
    • 刪除元素的成員函數并不檢查其參數。在刪除元素之前,程序員必須確保它(們)是存在的。
    操作說明
    c.pop_back()刪除c中尾元素。若c為空,則函數行為未定義。函數返回void
    c.pop_front()刪除c中首元素。若c為空,則函數行為未定義。函數返回void
    c.erase§刪除迭代器p所指定的元素,返回一個指向被刪元素之后元素的迭代器,若p指向尾元素,則返回尾后(off-the-end)迭代器。若p是尾后迭代器,則函數行為未定義
    c.erase(b,e)刪除deque中除首尾位置之外的任何元素都會使所有迭代器、引用和指針失效。指向vector或string中刪除點之后位置的迭代器、引用和指針都會失效。
    c.clear()刪除元素的成員函數并不檢查其參數。在刪除元素之前,程序員必須確保它(們)是存在的。

    pop_front和pop_back成員函數

    pop_front和pop_back成員函數分別刪除首元素和尾元素。與vector和string不支持 push_front一樣,這些類型也不支持pop_front。類似的,forward_list不支持pop_back。與元素訪問成員函數類似,不能對一個空容器執行彈出操作。

    這些操作返回void。如果你需要彈出的元素的值,就必須在執行彈出操作之前保存它:

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

    從容器內部刪除一個元素

    成員函數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;

    每個循環步中,首先檢查當前元素是否是奇數。如果是,就刪除該元素,并將it設置為我們所刪除的元素之后的元素。如果*it為偶數,我們將it遞增,從而在下一步循環檢查下一個元素。

    刪除多個元素

    接受一對迭代器的erase版本允許我們刪除一個范圍內的元素:

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

    迭代器elem1指向我們要刪除的第一個元素,elem2指向我們要刪除的最后一個元素之后的位置。

    為了刪除一個容器中的所有元素,我們既可以調用clear,也可以用begin和end獲得的迭代器作為參數調用erase:

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

    特殊的forward_list操作

    為了理解forward_list為什么有特殊版本的添加和刪除操作,考慮當我們從一個單向鏈表中刪除一個元素時會發生什么。如下圖所示,刪除一個元素會改變序列中的鏈接。在此情況下,刪除elem3會改變elem2,elem2原來指向elem3,但刪除elem3后,elem2指向了elem4。

    當添加或刪除一個元素時,刪除或添加的元素之前的那個元素的后繼會發生改變。為了添加或刪除一個元素,我們需要訪問其前驅,以便改變前驅的鏈接。但是,forward_list 是單向鏈表。在一個單向鏈表中,沒有簡單的方法來獲取一個元素的前驅。出于這個原因,在一個forward_list中添加或刪除元素的操作是通過改變給定元素之后的元素來完成的。這樣,我們總是可以訪問到被添加或刪除操作所影響的元素。

    (MyNote:forward_list 是數據結構種的單向鏈表。)

    由于這些操作與其他容器上的操作的實現方式不同,forward_list并未定義insert、emplace和erase,而是定義了名為insert_after、emplace_after和erase_after的操作。例如,在我們的例子中,為了刪除elem3,應該用指向elem2的迭代器調用erase_after。為了支持這些操作,forward_list也定義了before_begin,它返回一個首前( off-the-beginning)迭代器。這個迭代器允許我們在鏈表首元素之前并不存在的元素“之后”添加或刪除元素(亦即在鏈表首元素之前添加刪除元素)。

    在forward list中插入或刪除元素的操作

    操作說明
    lst.before_begin()返回指向鏈表首元素之前不存在的元素的迭代器。
    此迭代器不能解引用。cbefore_begin()返回一個const_iterator
    lst.cbefore_begin()同上一條
    lst.insert_after(p, t)在迭代器p之后的位置插入元素。t是一個對象,n是數量,
    b和e是表示范圍的一對迭代器(b和e不能指向lst內),
    il是一個花括號列表。返回一個指向最后一個插入元素的迭代器。
    如果范圍為空,則返回p。若p為尾后迭代器,則函數行為未定義
    lst.insert_after(p, n, t)同上一條
    lst.insert_after(p, b, e)同上一條
    lst.insert_after(p, il)同上一條
    emplace_after(p, args)使用args在p指定的位置之后創建一個元素。返回一個指向這個新元素的迭代器。
    若p為尾后迭代器,則函數行為未定義
    lst.erase_after§刪除p指向的位置之后的元素,或刪除從b之后直到(但不包含)e之間的元素。
    返回一個指向被刪元素之后元素的迭代器,若不存在這樣的元素,則返回尾后迭代器。
    如果p 指向lst的尾元素或者是一個尾后迭代器,則函數行為未定義
    lst.erase_after(b, e)同上一條

    當在forward_list 中添加或刪除元素時,我們必須關注兩個迭代器:

  • 一個指向我們要處理的元素,
  • 另一個指向其前驅。
  • 例如,從list中刪除奇數元素的循環程序,將其改為從forward_list中刪除元素:

    forward_list<int> flst = {0,1,2,3,4,5,6,7,8,9}; auto prev = flst.before_begin(); // denotes element "off the start" of flst auto curr = flst.begin(); // denotes the first element in flst while (curr != flst.end()) { // while there are still elements to processif (*curr % 2) // if the element is oddcurr = flst.erase_after(prev); // erase it and move currelse {prev = curr; // move the iterators to denote the next++curr; // element and one before the nextelement} }

    改變容器大小

    如下表所描述,我們可以用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向容器添加新元素,則我們必須提供初始值,或者元素類型必須提供一個默認構造函數。

    注意:

    • resize不使用于array。
    • 如果resize縮小容器,則指向被刪除元素的迭代器、引用和指針都會失效;對vector、string或deque進行resize可能導致迭代器、指針和引用失效。

    順序容器大小操作

    操作說明
    c.resize(n)調整c的大小為n個元素。若n<c.size(),則多出的元素被丟棄。若必須添加新元素,對新元素進行值初始化
    c.resize(n,t)調整c的大小為n個元素。任何新添加的元素都初始化為值t

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

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

    在向容器添加元素后:

    • 如果容器是vector或string,且存儲空間被重新分配,則指向容器的迭代器、指針和引用都會失效。如果存儲空間未重新分配,指向插入位置之前的元素的迭代器、指針和引用仍有效,但指向插入位置之后元素的迭代器、指針和引用將會失效。(MyNote:底層是用連續空間的數組實現。)
    • 對于 deque,插入到除首尾位置之外的任何位置都會導致迭代器、指針和引用失效。如果在首尾位置添加元素,迭代器會失效,但指向存在的元素的引用和指針不會失效。
    • 對于list和forward_list,指向容器的迭代器(包括尾后迭代器和首前迭代器)、指針和引用仍有效。

    當我們從一個容器中刪除元素后,指向被刪除元素的迭代器、指針和引用會失效,這應該不會令人驚訝。畢竟,這些元素都已經被銷毀了。

    當我們刪除一個元素后:

    • 對于list和forward_list,指向容器其他位置的迭代器(包括尾后迭代器和首前迭代器)、引用和指針仍有效。

    • 對于deque,如果在首尾之外的任何位置刪除元素,那么指向被刪除元素外其他元素的迭代器、引用或指針也會失效。如果是刪除 deque的尾元素,則尾后迭代器也會失效,但其他迭代器、引用和指針不受影響;如果是刪除首元素,這些也不會受影響。

    • 對于vector和string,指向被刪元素之前元素的迭代器、引用和指針仍有效。注意:當我們刪除元素時,尾后迭代器總是會失效。

    WARNING:使用失效的迭代器、指針或引用是嚴重的運行時錯誤。

    建議:管理迭代器

    當你使用迭代器(或指向容器元素的引用或指針)時,最小化要求迭代器必須保持有效的程序片段是一個好的方法。

    由于向迭代器添加元素和從迭代器刪除元素的代碼可能會使迭代器失效,因此必須保證每次改變容器的操作之后都正確地重新定位迭代器。這個建議對vector、string和deque尤為重要。

    編寫改變容器的循環程序

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

    // silly loop to remove even-valued elements and insert a duplicate of odd-valued elements//這silly并不意味error, vector<int> vi = {0,1,2,3,4,5,6,7,8,9}; auto iter = vi.begin(); // call begin, not cbegin because we're changing vi while (iter != vi.end()) {if (*iter % 2) {//奇數iter = vi.insert(iter, *iter); // duplicate the current elementiter += 2; // advance past this element and the one inserted before it} else {//偶數iter = vi.erase(iter); // remove even elements// don't advance the iterator; iter denotes the element after the one we erased} }

    此程序刪除vector中的偶數值元素,并復制每個奇數值元素。我們在調用insert和erase后都更新迭代器,因為兩者都會使迭代器失效

    在調用erase后,不必遞增迭代器,因為 erase返回的迭代器已經指向序列中下一個元素。調用insert后,需要遞增迭代器兩次。記住,insert在給定位置之前插入新元素,然后返回指向新插入元素的迭代器。因此,在調用insert后,iter指向新插入元素,位于我們正在處理的元素之前。我們將迭代器遞增兩次,恰好越過了新添加的元素和正在處理的元素,指向下一個未處理的元素。

    不要保存end返回的迭代器

    當我們添加/刪除vector或string 的元素后,或在 deque 中首元素之外任何位置添加/刪除元素后,原來end返回的迭代器總是會失效。因此,添加或刪除元素的循環程序必須反復調用end,而不能在循環之前保存end返回的迭代器,一直當作容器末尾使用。通常C++標準庫的實現中end()操作都很快,部分就是因為這個原因。

    例如,考慮這樣一個循環,它處理容器中的每個元素,在其后添加一個新元素。我們希望循環能跳過新添加的元素,只處理原有元素。在每步循環之后,我們將定位迭代器,使其指向下一個原有元素。如果我們試圖“優化”這個循環,在循環之前保存end()返回的迭代器,一直用作容器末尾,就會導致一場災難:

    //災難:此循環的行為是未定義的 auto begin = v.begin (),end = v.end(); //保存尾迭代器的值是一個壞主意 while (begin != end) {//做一些處理//插入新值,對begin重新賦值,否則的話它就會失效++begin; //向前移動begin,因為我們想在此元素之后插入元素begin = v.insert(begin, 42); //插入新值++begin; //向前移動begin跳過我們剛剛加入的元素 }

    此代碼的行為是未定義的。在很多標準庫實現上,此代碼會導致無限循環。問題在于我們將end操作返回的迭代器保存在一個名為end 的局部變量中。在循環體中,我們向容器中添加了一個元素,這個操作使保存在end中的迭代器失效了。這個迭代器不再指向v中任何元素,或是v中尾元素之后的位置。

    Tip:如果在一個循環中插入/刪除 deque、string或 vector 中的元素,不要緩存end返回的迭代器。

    必須在每次插入操作后重新調用end(),而不能在循環開始前保存它返回的迭代器:

    // safer: recalculate end on each trip whenever the loop adds/erases elements while (begin != v.end()) {// do some processing++begin; // advance begin because we want to insert after this elementbegin = v.insert(begin, 42); // insert the new value++begin; // advance begin past the element we just added }

    vector對象是如何增長

    為了支持快速隨機訪問,vector將元素連續存儲,也就是說每個元素緊挨著前一個元素存儲。通常情況下,我們不必關心一個標準庫類型是如何實現的,而只需關心它如何使用。然而,對于vector和 string,其部分實現滲透到了接口中。

    (MyNote:C++的vector與Java的ArrayList類似。)

    假定容器中元素是連續存儲的,且容器的大小是可變的,考慮向vector或string中添加元素會發生什么:如果沒有空間容納新元素,容器不可能簡單地將它添加到內存中其他位置——因為元素必須連續存儲。容器必須分配新的內存空間來保存已有元素和新元素,將已有元素從舊位置移動到新空間中,然后添加新元素,釋放舊存儲空間。(MyNote:擴容)如果我們每添加一個新元素,vector就執行一次這樣的內存分配和釋放操作,性能會慢到不可接受。

    為了避免這種代價,標準庫實現者采用了可以減少容器空間重新分配次數的策略。當不得不獲取新的內存空間時,vector和string 的實現通常會分配比新的空間需求更大的內存空間。容器預留這些空間作為備用,可用來保存更多的新元素。這樣,就不需要每次添加新元素都重新分配容器的內存空間了。

    這種分配策略比每次添加新元素時都重新分配容器內存空間的策略要高效得多。其實際性能也表現得足夠好。雖然vector在每次重新分配內存空間時都要移動所有元素,但使用此策略后,其擴張操作通常比list和deque還要快。

    管理容量的成員函數

    如下表所示,vector和string類型提供了一些成員函數,允許我們與它的實現中內存分配部分互動。

    • capacity操作告訴我們容器在不擴張內存空間的情況下可以容納多少個元素。
    • reserve操作允許我們通知容器它應該準備保存多少個元素。

    reserve
    英 [r??z??v] 美 [r??z??rv]
    v. 預訂,預約(座位、席位、房間等);保留;貯備;擁有,保持,保留(某種權利)
    n. 儲備(量);儲藏(量);(動植物)保護區;自然保護區;內向;寡言少語;矜持

    reverse
    英 [r??v??s] 美 [r??v??rs]
    v. 顛倒;徹底轉變;使完全相反;撤銷,廢除(決定、法律等);使反轉;使次序顛倒
    n. 相反的情況(或事物);后面;背面;反面;倒擋
    adj. 相反的;反面的;反向的;背面的;后面的

    (MyNote:reserve和reverse傻傻分不清楚。)

    注意:

    • shrink_to_fit只適用于vector、 string和deque。
    • capacity和reserve只適用于vector和string。

    容器大小管理操作

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

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

    只有當需要的內存空間超過當前容量時,reserve調用才會改變vector的容量。如果需求大小大于(>)當前容量,reserve至少分配與需求一樣大的內存空間(可能更大)。

    如果需求大小小于或等于當前容量,reserve什么也不做。特別是,當需求大小小于(<)當前容量時,容器不會退回內存空間。因此,在調用reserve之后,capacity將會大于或等于傳遞給reserve的參數。

    這樣,調用reserve永遠也不會減少容器占用的內存空間。類似的,resize成員函數只改變容器中元素的數目,而不是容器的容量。我們同樣不能使用resize來減少容器預留的內存空間。(MyNote:注意reserve與最大容量有關,resize與實際容量有關。)

    在新標準庫中,我們可以調用shrink_to_fit來要求deque、vector或string退回不需要的內存空間。此函數指出我們不再需要任何多余的內存空間。但是,具體的實現可以選擇忽略此請求。也就是說,調用shrink_to_fit也并不保證一定退回內存空間。(MyNote:shrink_to_fit會盡力了。)

    capacity和size

    理解capacity和size的區別非常重要。容器的size是指它已經保存的元素的數目;而capacity則是在不分配新的內存空間的前提下它最多可以保存多少元素。(MyNote:注意capacity與最大容量有關,size與實際容量有關。)

    下面的代碼展示了size和capacity之間的相互作用:

    vector<int> ivec; // size should be zero; capacity is implementation defined cout << "ivec: size: " << ivec.size()<< " capacity: " << ivec.capacity() << endl;// give ivec 24 elements for (vector<int>::size_type ix = 0; ix != 24; ++ix)ivec.push_back(ix);// size should be 24; capacity will be >= 24 and is implementation defined cout << "ivec: size: " << ivec.size()<< " capacity: " << ivec.capacity() << endl;

    當在我們的系統上運行時,這段程序得到如下輸出:

    ivec: size: 0 capacity: 0 ivec: size: 24 capacity: 32

    我們知道一個空vector的size為0,顯然在我們的標準庫實現中一個空vector的capacity也為0。當向vector中添加元素時,我們知道size與添加的元素數目相等。而capacity至少與size一樣大,具體會分配多少額外空間則視標準庫具體實現而定。在我們的標準庫實現中,每次添加1個元素,共添加24個元素,會使capacity變為32。

    可以想象ivec的當前狀態如下圖所示:

    現在可以預分配一些額外空間:

    ivec.reserve(50); //將capacity至少設定為50,可能會更大//size應該為24; capacity應該大于等于50,具體值依賴于標準庫實現 cout << "ivec: size: " << ivec.size()<<" capacity: " << ivec.capacity() <<endl;

    程序的輸出表明reserve嚴格按照我們需求的大小分配了新的空間:

    ivec: size: 24 capacity: 50

    接下來可以用光這些預留空間:

    //添加元素用光多余容量 while (ivec.size() != ivec.capacity())ivec.push_back(O);//capacity應該未改變,size和capacity不相等 cout << "ivec: size: " << ivec.size ()<<" capacity: "<< ivec.capacity () << endl;

    程序輸出表明此時我們確實用光了預留空間,size和 capacity相等:

    ivec: size: 50 capacity: 50

    由于我們只使用了預留空間,因此沒有必要為vector分配新的空間。實際上,只要沒有操作需求超出vector的容量,vector就不能重新分配內存空間。

    如果我們現在再添加一個新元素,vector就不得不重新分配空間:

    ivec.push_back(42);//再添加一個元素 // size應該為51; capacity應該大于等于51,具體值依賴于標準庫實現cout<<"ivec: size: " << ivec.size()<<" capacity: "<< ivec.capacity()<< endl;

    這段程序的輸出為

    ivec: size: 51 capacity: 100

    這表明vector的實現采用的策略似乎是在每次需要分配新內存空間時將當前容量翻倍。

    (MyNote:真的不夠用,才去擴容。)

    可以調用shrink_to_fit來要求vector將超出當前大小的多余內存退回給系統:

    ivec.shrink_to_fit(); //要求歸還內存 // size應該未改變;capacity的值依賴于具體實現 cout <<"ivec: size: " << ivec.size()<<" capacity: "<< ivec.capacity() <<endl;

    調用shrink_to_fit只是一個請求,標準庫并不保證退還內存。

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

    只有在執行insert操作時size與capacity相等,或者調用resize或reserve時給定的大小超過當前capacity,vector才可能重新分配內存空間。會分配多少超過給定容量的額外空間,取決于具體實現。

    雖然不同的實現可以采用不同的分配策略,但所有實現都應遵循一個原則:確保用push_back向vector添加元素的操作有高效率。從技術角度說,就是通過在一個初始為空的vector上調用n次push_back來創建一個n個元素的vector,所花費的時間不能超過n的常數倍。

    額外的string操作

    除了順序容器共同的操作之外,string類型還提供了一些額外的操作。這些操作中的大部分要么是提供string類和C風格字符數組之間的相互轉換,要么是增加了允許我們用下標代替迭代器的版本。

    標準庫string類型定義了大量函數。幸運的是,這些函數使用了重復的模式。可按需查閱。

    構造string的其他方法

    第3章介紹過string部分構造函數,以及其他順序容器相同的構造函數外,string類型還支持另外三個構造函數,如表所示:

    注釋:

    • n、len2和pos2都是無符號值
    操作說明
    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*參數,還接受(可選的)指定拷貝多少個字符的參數。當我們傳遞給它們的是一個string 時,還可以給定一個下標來指出從哪里開始拷貝:

    const char *cp = "Hello world!!!";//以空字符結束的數組 char noNull[]= {"H", "i"};//不是以空字符結束 string s1(cp);//拷貝cp中的字符直到遇到空字符;s1 == "Hello world! ! !"//1. string s2(noNull, 2);//從noNull拷貝兩個字符; s2 == "Hi" string s3(noNull);//未定義:noNull不是以空字符結束//2.string s4(cp + 65);//從cp[6]開始拷貝5個字符;s4 == "wor1d"//3. string s5(s1, 65);//從s1[6]開始拷貝5個字符;s5 -= "world" string s6(s1, 6);//從s1 [6]開始拷貝,直至s1末尾;s6== "wor1d! ! !" string s7(s1, 6, 20);//正確,只拷貝到s1末尾;s7 =="world! ! ! "//4. string s8(s1, 16);//拋出一個out_of_range異常//3.
  • 通常當我們從一個const char*創建string 時,指針指向的數組必須以空字符結尾,拷貝操作遇到空字符時停止。如果我們還傳遞給構造函數一個計數值,數組就不必以空字符結尾。

  • 如果我們未傳遞計數值且數組也未以空字符結尾,或者給定計數值大于數組大小,則構造函數的行為是未定義的。

  • 當從一個string拷貝字符時,我們可以提供一個可選的開始位置和一個計數值。開始位置必須小于或等于給定的string的大小。如果位置大于size,則構造函數拋出一個out_of_range異常。

  • 如果我們傳遞了一個計數值,則從給定位置開始拷貝這么多個字符。不管我們要求拷貝多少個字符,標準庫最多拷貝到string結尾,不會更多。

  • substr操作

    操作說明
    s.substr(pos, n)返回一個string,包含s中從pos開始的n個字符的拷貝。pos的默認值為0。n的默認值為s.size () - pos,即拷貝從pos開始的所有字符

    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);//s3 = world string s5 = s.substr(12);//拋出一個out_of_range異常

    如果開始位置超過了string的大小,則substr函數拋出一個out_of_range異常。

    如果開始位置加上計數值大于string的大小,則substr會調整計數值,只拷貝到string的末尾。

    改變string的其他方法

    string類型支持順序容器的賦值運算符以及assign、insert和erase操作。除此之外,它還定義了額外的insert和erase版本。

    除了接受迭代器的insert和erase版本外,string還提供了接受下標的版本。下標指出了開始刪除的位置,或是insert到給定值之前的位置:

    s.insert(s.size(), 5, '!');//在s末尾插入5個感嘆號 s.erase(s.size() - 5, 5); //從s刪除最后5個字符

    標準庫string類型還提供了接受C風格字符數組的insert和 assign版本。例如,我們可以將以空字符結尾的字符數組insert到或assign給一個string:

    const char *cp = "Stately, plump Buck"; s.assign(cp, 7); // s == "Stately" s.insert(s.size(), cp + 7); // s == "Stately, plump Buck"

    此處我們首先通過調用assign替換s的內容。我們賦予s的是從cp指向的地址開始的7個字符。要求賦值的字符數必須小于或等于cp 指向的數組中的字符數(不包括結尾的空字符)。

    接下來在s上調用insert,我們的意圖是將字符插入到s[size()]處(不存在的)元素之前的位置。在此例中,我們將cp開始的7個字符(至多到結尾空字符之前)拷貝到s中。

    我們也可以指定將來自其他string或子字符串的字符插入到當前string中或賦予當前string:

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

    append和replace函數

    string類定義了兩個額外的成員函數:append和replace,這兩個函數可以改變string的內容。下表描述了這兩個函數的功能。

    append操作是在string末尾進行插入操作的一種簡寫形式:

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

    此例中調用replace時,插入的文本恰好與刪除的文本一樣長。這不是必須的,可以插入一個更長或更短的string:

    s.replace(11, 3, "Fifth");// s == "C++ Primer Fifth Ed."

    在此調用中,刪除了3個字符,但在其位置插入了5個新字符。

    修改string的操作

    操作說明
    s.insert(pos, args)在pos之前插入args指定的字符。pos可以是一個下標或一個迭代器。
    接受下標的版本返回一個指向s的引用;
    接受迭代器的版本返回指向第一個插入字符的迭代器
    s.erase(pos, len)刪除從位置pos開始的len個字符。如果len被省略,
    則刪除從pos開始直至s末尾的所有字符。返回一個指向s的引用
    s.assign(args)將s中的字符替換為args指定的字符。返回一個指向s的引用
    s.append(args)將args追加到s。返回一個指向s的引用
    s.replace(range, args)刪除s中范圍range內的字符,替換為args指定的字符。
    range或者是一個下標和一個長度,或者是一對指向s 的迭代器。
    返回一個指向s的引用

    上表的args可以是下列形式之一; append和assign可以使用所有形式。

    str不能與s相同(MyNote:str與s互不wweiei),迭代器b和e不能指向s。

    ..
    str字符串str
    str, pos, lenstr中從pos開始最多len個字
    cp, len從cp指向的字符數組的前(最多) len個字符
    cpcp指向的以空字符結尾的字符數組
    n, cn個字符c
    b, e迭代器b和e指定的范圍內的字符
    初始化列表花括號包圍的,以逗號分隔的字符列表

    replace和insert所允許的args形式依賴于range和pos是如何指定的。

    replace
    (pos, len, args)replace
    (b, e, args)insert
    (pos, args)insert
    (iter, args)args是否可以是
    TTTstr
    TTstr, pos, len
    TTTcp, len
    TTcp
    TTTTn, c
    TTb, e
    TT初始化列表

    說明:空的表示F。

    改變string 的多種重載函數

    上面表列出的append、assign、insert和replace函數有多個重載版本。根據我們如何指定要添加的字符和string 中被替換的部分,這些函數的參數有不同版本。幸運的是,這些函數有共同的接口。

    assign 和 append 函數無須指定要替換string中哪個部分:

    • assign總是替換string中的所有內容,
    • append總是將新字符追加到string末尾。

    replace函數提供了兩種指定刪除元素范圍的方式。

  • 可以通過一個位置和一個長度來指定范圍,
  • 也可以通過一個迭代器范圍來指定。
  • insert函數允許我們用兩種方式指定插入點:

  • 用一個下標
  • 或一個迭代器。
  • 在兩種情況下,新元素都會插入到給定下標(或迭代器)之前的位置。

    可以用好幾種方式來指定要添加到string中的字符。新字符可以

    • 來自于另一個string,
    • 來自于一個字符指針(指向的字符數組),
    • 來自于一個花括號包圍的字符列表,或者是一個字符和一個計數值。

    當字符來自于一個string或一個字符指針時,我們可以傳遞一個額外的參數來控制是拷貝部分還是全部字符。

    并不是每個函數都支持所有形式的參數。例如,insert就不支持下標和初始化列表參數。類似的,如果我們希望用迭代器指定插入點,就不能用字符指針指定新字符的來源。

    string搜索操作

    string類提供了6個不同的搜索函數,每個函數都有4個重載版本。

    下表描述了這些搜索成員函數及其參數。每個搜索操作都返回一個string:size_type值,表示匹配發生位置的下標。

    如果搜索失敗,則返回一個名為string:npos的static成員。標準庫將npos定義為一個const string:size_type類型,并初始化為值-1。由于npos是一個unsigned類型,此初始值意味著npos等于任何string最大的可能大小。

    WARNING:string搜索函數返回string::size_type值,該類型是一個unsigned類型。因此,用一個 int或其他帶符號類型來保存這些函數的返回值不是一個好主意

    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中的字符

    args必須是以下形式之一

    ..
    c, pos從s中位置pos開始查找字符c。pos默認為О
    s2, pos從s中位置pos開始查找字符串s2。pos默認為О
    cp, pos從s中位置pos開始查找指針cp指向的以空字符結尾的C風格字符串。pos默認為0
    cp, pos, n從s中位置pos開始查找指針cp指向的數組的前n個字符。pos和 n無默認值

    find函數完成最簡單的搜索。它查找參數指定的字符串,若找到,則返回第一個匹配位置的下標,否則返回npos:

    string name ( "AnnaBelle" ) ; auto pos1 = name.find ( "Anna"); //pos1 == 0

    這段程序返回0,即子字符串"Anna"在"AnnaBelle"中第一次出現的下標。

    搜索(以及其他string操作)是大小寫敏感的。當在string中查找子字符串時,要注意大小寫:

    string lowercase("annabelle"); pos1 = lowercase.find("Anna"); // posl == npos

    這段代碼會將pos1置為npos,因為Anna與anna不匹配。

    一個更復雜一些的問題是查找與給定字符串中任何一個字符匹配的位置。例如,下面代碼定位name中的第一個數字:

    string numbers("0123456789"), name("r2d2");//返回1,即,name中第一個數字的下標 auto pos = name.find_first_of(numbers) ;

    如果是要搜索第一個不在參數中的字符,我們應該調用find_first_not_of。例如,為了搜索一個string中第一個非數字字符,可以這樣做:

    string dept("03714p3"); //返回5——字符'p'的下標 auto pos = dept.find_first_not_of(numbers);

    指定在哪里開始搜索

    我們可以傳遞給find操作一個可選的開始位置。這個可選的參數指出從哪個位置開始進行搜索。默認情況下,此位置被置為0。一種常見的程序設計模式是用這個可選參數在字符串中循環地搜索子字符串出現的所有位置:

    string::size_type pos = 0; //每步循環查找name中下一個數 while((pos = name.find_first_of(numbers, pos))!= string::npos){cout << "found number at index : " << pos<< " element is " << name[pos] << endl;++pos;//移動到下一個字符 }

    while的循環條件將pos重置為從 pos開始遇到的第一個數字的下標。只要find_first_of返回一個合法下標,我們就打印當前結果并遞增pos。

    如果我們忽略了遞增pos,循環就永遠也不會終止。為了搞清楚原因,考慮如果不做遞增運算會發生什么。在第二步循環中,我們從 pos 指向的字符開始搜索。這個字符是一個數字,因此find_first_of會(重復地)返回pos!

    逆向搜索

    到現在為止,我們已經用過的find操作都是由左至右搜索。標準庫還提供了類似的,但由右至左搜索的操作。rfind成員函數搜索最后一個匹配,即子字符串最靠右的出現位置:

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

    find返回下標1,表示第一個"is"的位置,而rfind返回下標4,表示最后一個"is"的位置。

    類似的,find_last函數的功能與find_first函數相似,只是它們返回最后一個而不是第一個匹配:

    • find_last_of搜索與給定string 中任何一個字符匹配的最后一個字符。
    • find_last_not_of搜索最后一個不出現在給定string中的字符。

    每個操作都接受一個可選的第二參數,可用來指出從什么位置開始搜索。

    compare函數

    除了關系運算符外,標準庫string類型還提供了一組compare函數,這些函數與C標準庫的strcmp函數很相似。類似strcmp,根據s是等于、大于還是小于參數指定的字符串,s.compare返回0、正數或負數。

    compare有6個版本。根據我們是要比較兩個string還是一個string與一個字符數組,參數各有不同。在這兩種情況下,都可以比較整個或一部分字符串。

    s.compare的幾種參數形式

    操作說明
    s2比較s和s2
    pos1, n1, s2將s中從pos1開始的n1個字符與s2進行比較
    pos1, n1, s2, pos2, n2將s中從pos1開始的n1個字符與s2中從pos2開始的n2個字符進行比較
    cp比較s與cp指向的以空字符結尾的字符數組
    pos1, n1, cp將s中從pos1開始的n1個字符與cp指向的以空字符結尾的字符數組進行比較
    pos1, n1, cp, n2將s中從pos1開始的n1個字符與指針cp指向的地址開始的n2個字符進行比較

    數值轉換

    string和數值之間的轉換:

    操作說明
    to_string(val)一組重載函數,返回數值val的string表示。val可以是任何算術類型。對每個浮點類型和int或更大的整型,都有相應版本的to_string。與往常一樣,小整型會被提升
    stoi(s, p, b)返回s的起始子串(表示整數內容)的數值,返回值類型分別是int、long、unsigned long、long long、unsigned long long。b表示轉換所用的基數,默認值為10。p是size_t指針,用來保存s中第一個非數值字符的下標,p默認為0,即,函數不保存下標
    stol(s, p, b)同上一條
    stoul(s, p, b)同上一條
    stoll(s, p, b)同上一條
    stoull(s, p,b)同上一條
    stof(s, p)返回s的起始子串(表示浮點數內容)的數值,返回值類型分別是float、 double 或 long double。參數p的作用與整數轉換函數中一樣
    stod(s, p)同上一條
    stold(s, p)同上一條

    字符串中常常包含表示數值的字符。例如,我們用兩個字符的string表示數值15——字符‘1’后跟字符’5’。一般情況,一個數的字符表示不同于其數值。數值15如果保存為16位的 short類型,則其二進制位模式為0000 0000 0000 1111,而字符串"15"存為兩個Latin-1編碼的char,二進制位模式為0011 0001 0011 0101。第一個字節表示字符’1’,其八進制值為061,第二個字節表示’5’,其 Latin-1編碼為八進制值065。

    新標準引入了多個函數,可以實現數值數據與標準庫 string之間的轉換;

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

    此例中我們調用to_string 將42轉換為其對應的string表示,然后調用stod將此string轉換為浮點值。

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

    string s2 = "pi = 3.14"; //轉換s中以數字開始的第一個子串,結果d = 3.14 d = stod (s2.substr(s2.find_first_of("+-.0123456789")));

    在這個stod調用中,我們調用了find_first_of來獲得s中第一個可能是數值的一部分的字符的位置。我們將s中從此位置開始的子串傳遞給stod。stod函數讀取此參數,處理其中的字符,直至遇到不可能是數值的一部分的字符。然后它就將找到的這個數值的字符串表示形式轉換為對應的雙精度浮點值。

    string參數中第一個非空白符必須是符號(+ 或 -)或數字。它可以以0x或0x開頭來表示十六進制數。對那些將字符串轉換為浮點值的函數,string參數也可以以小數點(.)開頭,并可以包含e或E來表示指數部分。對于那些將字符串轉換為整型值的函數,根據基數不同,string參數可以包含字母字符,對應大于數字9的數。

    Note:如果string不能轉換為一個數值,這些函數拋出一個invalid_argument異常。如果轉換得到的數值無法用任何類型來表示,則拋出一個out_of_range異常。

    容器適配器

    概述

    除了順序容器外,標準庫還定義了三個順序容器適配器:

  • stack
  • queue
  • priority_queue
  • 適配器(adaptor)是標準庫中的一個通用概念。容器、迭代器和函數都有適配器。本質上,一個適配器是一種機制,能使某種事物的行為看起來像另外一種事物一樣。一個容器適配器接受一種已有的容器類型,使其行為看起來像一種不同的類型。例如,stack適配器接受一個順序容器(除array或forward_list外),并使其操作起來像一個stack一樣。下表列出了所有容器適配器都支持的操作和類型。

    所有容器適配器都支持的操作和類型

    操作說明
    size_type一種類型,足以保存當前類型的最大對象的大小
    value_type元素類型
    container_type實現適配器的底層容器類型
    A a;創建一個名為a的空適配器
    A a?;創建一個名為a的適配器,帶有容器c的一個拷貝
    關系運算符每個適配器都支持所有關系運算符:==、!=、<、<=、>和>=這些運算符返回底層容器的比較結果
    a.empty()若a包含任何元素,返回false,否則返回true
    a.size()返回a中的元素數目
    swap(a,b)交換a和b的內容,a和b必須有相同類型,包括底層容器類型也必須相同
    a.swap(b)同上一條

    定義一個適配器

    每個適配器都定義兩個構造函數:默認構造函數創建一個空對象,接受一個容器的構造函數拷貝該容器來初始化適配器。例如,假定deq是一個deque<int>,我們可以用deq來初始化一個新的stack,如下所示:

    stack<int> stk(deq);//從deq拷貝元素到stk

    默認情況下,stack和queue是基于deque實現的,priority_queue是在vector之上實現的。我們可以在創建一個適配器時將一個命名的順序容器作為第二個類型參數,來重載默認容器類型。

    //在vector上實現的空棧 stack<string,vector<string>> str_stk; // str_stk2在vector上實現,初始化時保存svec的拷貝 stack<string,vector<string>> str_stk2(svec);

    對于一個給定的適配器,可以使用哪些容器是有限制的。所有適配器都要求容器具有添加和刪除元素的能力。因此,適配器不能構造在array 之上。類似的,我們也不能用forward_list來構造適配器,因為所有適配器都要求容器具有添加、刪除以及訪問尾元素的能力。

    • stack只要求push_back、pop_back和back操作因此可以使用除array和forward_list之外的任何容器類型來構造stack。
    • queue適配器要求back、push_back、front和push_front,因此它可以構造于list或deque之上,但不能基于vector構造。
    • priority_queue除了front、push_back和pop_back操作之外還要求隨機訪問能力,因此它可以構造于vector或deque 之上,但不能基于list構造。

    棧適配器

    stack類型定義在stack頭文件中。下表列出了stack所支持的操作。

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

    棧特有操作

    操作說明
    s.pop()刪除棧頂元素,但不返回該元素值
    s.push(item)創建一個新元素壓入棧頂,該元素通過拷貝或移動item而來,或者由args構造
    s.emplace(args)同上一條
    s.top()返回棧頂元素,但不將元素彈出棧

    下面的程序展示了如何使用stack:

    stack<int> intstack; //空棧//填滿棧 for (size_t ix = 0; ix != 10; ++ix)intstack.push(ix); //intstack 保存0到9十個數while (!intstack.empty()){//intStack 中有值就繼續循環int value = intstack.top();//使用棧頂值的代碼intstack.pop();//彈出棧頂元素,繼續循環 }

    其中,聲明語句

    stack<int> intstack; //空棧

    定義了一個保存整型元素的棧intstack,初始時為空。for循環將10個元素添加到棧中,這些元素被初始化為從О開始連續的整數。while循環遍歷整個stack,獲取top值,將其從棧中彈出,直至棧空。

    每個容器適配器都基于底層容器類型的操作定義了自己的特殊操作。我們只可以使用適配器操作,而不能使用底層容器類型的操作。例如,

    intstack.push(ix); //intstack保存0到9十個數

    此語句試圖在intstack的底層deque對象上調用push_back。雖然stack是基于deque實現的,但我們不能直接使用deque操作。不能在一個stack上調用push_back,而必須使用stack自己的操作——push。

    隊列適配器

    queue和priority_queue適配器定義在 queue頭文件中。下表列出了它們所支持的操作。

    queue默認基于deque實現,priority_queue 默認基于vector實現;

    queue也可以用list或vector實現,priority_queue也可以用deque實現。

    隊列特有的操作

    操作說明
    q.pop()返回queue的首元素或priority_queue的最高優先級的元素,但不刪除此元素
    q.front()(只適用于queue)返回首元素,但不刪除此元素
    q.back()(只適用于queue)返回尾元素,但不刪除此元素
    q.top()(只適用于priority_queue)返回首元素或尾元素,但不刪除此元素
    q.push(item)在queue末尾或priority_queue 中恰當的位置創建一個元素,其值為item
    q.emplace(args)在queue末尾或priority_queue 中恰當的位置創建一個元素,或者由args構造

    標準庫queue使用一種先進先出(first-in,first-out,FIFO)的存儲和訪問策略。進入隊列的對象被放置到隊尾,而離開隊列的對象則從隊首刪除。正如飯店按客人到達的順序來為他們安排座位,就是一個先進先出隊列的例子。

    priority_queue允許我們為隊列中的元素建立優先級。新加入的元素會排在所有優先級比它低的已有元素之前。正如飯店按照客人預定時間而不是到來時間的早晚來為他們安排座位,就是一個優先隊列的例子。默認情況下,標準庫在元素類型上使用 < 運算符來確定相對優先級。我們將在第11章學習如何重載這個默認設置。

    小結

    本章順序容器與Java神同形似的容器

    C++Java
    vectorVector、ArrayList
    dequeLinkedList、ArrayDeque
    listLinkedList
    forward_list-
    array(容量固定,不能增刪元素)ArrayList(可動態擴容,能增刪元素)
    stringString(在Java,它不是容器,另外,
    它一旦初始化,內容就不能改變)
    stackStack、LinkedList
    queueLinkedList
    priority_queuePriorityQueue

    總結

    以上是生活随笔為你收集整理的《C++ Primer 5th》笔记(9 / 19):顺序容器的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。