C++ Primer 5th學習筆記8 順序容器

順序容器

1 順序容器概述

順序容器類型
名稱 描述
vector 可變大小數組,支持快速隨機訪問。在尾部之外的位置插入元素或刪除元素可能很慢
deque 雙端隊列,支持快速隨機訪問。從頭尾位置插入/刪除速度很快
list 雙向鏈表,只支持雙向順序訪問。在list中任何位置進行插入和刪除操作速度都很快
forward_list 單向鏈表,只支持單向順序訪問。在任何位置進行插入和刪除操作都很快
array 給定大小數組。支持快速隨機訪問。不能添加或刪除元素
string vector相似的容器,隨機訪問快,在尾部插入/刪除速度快

Tips:

  • string和vector均是將元素保存在連續的內存空間中,由於儲存空間的連續,因此利用下標計算其地址非常快。但在容器的中間位置添加或刪除元素會非常耗時:在一次插入操作後,需要移動插入或刪除位置之後的所有元素,而且添加一個元素後可能還需要分配額外的儲存空間。
  • list和forward_list優點是在容器如何位置添加和刪除操作都很快。但這兩類容器不支持隨機訪問。訪問一個元素時,只能遍歷整個容器。額外內存開銷比其他容器較大。
  • deque支持快速隨機訪問,在中間位置添加和刪除元素的代價搞,但是在兩端添加或刪除元素都很快

  選擇容器的基本原則:

  • 一般情況下,優先選擇vector
  • 若程序有很多小的元素,且空間的額外開銷很重要,則不要使用listforward_list
  • 若程序要求隨機訪問元素,應使用vectordeque
  • 若程序要求在容器的中間插入或刪除元素,應使用listforward_list
  • 若程序要求在容器的頭尾位置插入或刪除元素,但不會在中間位置進行插入或刪除,則使用deque
  • 若程序只有在讀取輸入時才需要在容器中間位置插入元素,隨後需要隨機訪問元素。如果必須在中間位置插入元素,則在輸入階段使用list,輸入完成之後,將list的內容拷貝到一個vector中。

2 容器庫概覽

容器操作
類型別名 描述
iterator 該容器的的迭代器類型
const_iterator 可以讀取元素,但不能修改元素的迭代器類型
size_type 無符號整數類型,足夠保存此容器類型最大可能容器的大小
difference_type 有符號整數類型,足夠保存兩個迭代器之間的距離
value_type 元素類型
reference 元素的左值類型,與value_type&含義相同
const_reference 元素的const左值類型(即,const value_type&

構造函數

名稱 描述
C c; 默認構造函數,構造空函數
C c1(c2); 構造c2的拷貝c1
C c(b, e); 構造c,將迭代器b和e指定範圍內的元素拷貝到c(array 不支持)
C c{a, b, c ...}; 列表初始化c

大小

名稱 描述
c.size() c中元素的數目(不支持forward_list)
c.max_size() c可保存的最大元素數目
c.empty() 若c中儲存了元素,返回false,否則返回true

添加/刪除元素(不適用與array)

名稱 描述
c.insert(args) args中的元素拷貝進c
c.emplace(args) 使用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

2.1 容器類型成員

示例如下:

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

2.2 容器定義和初始化

  將一個新容器創建爲另外一個容器的拷貝的方法有兩種:可以直接拷貝整個容器,或者(array除外)拷貝由一個迭代器對指定的元素範圍。當將一個容器初始化爲另外一個容器的拷貝時,兩個容器的容器類型和元素類型都必須相同。
示例:

//拷貝元素,直到(但不包括)it指向的元素
deque<string> authList(authors.begin(), it);

2.3 賦值和swap

容器賦值運算
操作 描述
c1 = c2 c1中的元素替換爲c2中元素的拷貝
c = {a,b,c...} c1中的元素替換爲初始化列表中元素的拷貝(array不適用)
swap(c1, c2) 交換c1c2中的元素,兩者必須具有相同類型,比拷貝快
c1.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的元素

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

  賦值運算要求左邊和右邊的運算對象具有相同的類型,assign操作用參數指定的元素(的拷貝)替換左邊容器中的所有元素。例如可以將一個vector中的一段char*值賦給一個list中的string:

list<string> names;
vector<const char*> oldstyle;
names.assign(oldstyle.cbegin(),oldstyle.cbegin());

使用swap操作用來交換兩個相同類型容器的內容。除array外,交換兩個容器內容的操作中,元素本身未交換,swap只是交換了兩個容器的內部數據結構。這意味着元素不會被移動,因此除string外,指向容器的迭代器、引用和指針在swap操作之後都不會失效。而swap兩個array會真正交換兩者的元素,因此對於array,在swap操作之後,指針、引用和迭代器所綁定的元素保持不變,但元素值已經和另外一個array中對應的元素的值進行了交換。

2.3 容器大小操作

  成員函數size返回容器中元素的數目;empty當size爲0時返回布爾值true,否則返回false;max_size返回一個大於或等於改類型容器所能容納的最大元素的值。

2.4 關係運算符

  關係運算符左右兩邊的運算對象必須是相同類型的容器,且必須保存相同類型的元素。比較方式如下:

  • 如果兩個容器具有相同的大小且所有的容器都兩兩對應相等,則這兩個容器相等;否則兩個容器不等。
  • 如果兩個容器大小不同,但較小的容器中每個元素都等於較大容器中的對應元素,則較小容器小於較大容器。
  • 如果兩個容器都不是另外一個容器的前綴子序列,則比較的結果取決於第一個不相等的元素的比較結果

3 順序容器操作

3.1 向順序容器添加元素

向順序容器添加元素的操作
操作 描述
c.push_back(t) c.emplace_back(args) 在c的尾部創建一個值爲t或由args創建的元素,返回void
c.push_front(t) c.emplace_front(args) 在c的頭部創建一個值爲t或由args創建的元素,返回void
c.insert(p, t) c.emplace(p, args) 在迭代器p指向的元素之前創建一個值爲t或由args創建的元素,返回指向新添加的元素的迭代器
c.insert(p, n, t) 在迭代器p指向的元素之前插入n個值爲t的元素,若n爲0,則返回p;否則返回指向新添加的第一個元素的迭代器
c.insert(p, b, e) 將迭代器b和e指定範圍內的元素插入到迭代器p指向的元素之前,b和e之間不能指向e。若範圍爲空,則返回p;否則返回指向新添加的第一個元素的迭代器
c.insert(p, il) il是一個花括號包圍的元素值列表。

Tip:這些操作會改變容器的大小,array不支持這些操作;forward_list有自己專用版本的insert和emplace;forward_list不支持push_back和emplace_back;vector和string不支持push_front和emplace_front;

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

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

使用emplace操作,是將參數傳遞給元素類型的構造函數。當調用push或insert成員時,是將元素類型的對象傳遞給他們,這些對象是拷貝到容器中;當調用emplace成員函數時,則是將參數傳遞給元素類型的構造函數,emplace成員使用這些參數在容器管理的內存空間中直接構造元素。示例:

class Book
{
    public:
    Book(string n = "",int i = 0)
        :name(n),price(i)
        {   
        }
    private:
    string name;
    int price;
};
vector<Book> c;
c.emplace_back("111",123);
c.push_back(Book("111",123));

3.2 訪問元素

在順序容器訪問元素的操作
操作 描述
c.back() 返回c中尾元素的引用。若c爲空,函數行爲未定義
c.front() 返回c中首元素的引用。若c爲空,函數行爲未定義
c[n] 返回c中下標爲n的元素的引用,若n>c.size(),則函數行爲未定義
c.at(n) 返回c中下標爲n的元素的引用,若如果下標越界,則拋出異常:out_of_range

Tip:at和下標操作只適用於string、vector、deque、array
back不是適用於forward_list

3.3 刪除元素

順序容器的刪除操作
操作 描述
c.pop_back() 刪除c中尾元素,若c爲空,則函數行爲未定義。函數返回void
c.pop_front() 刪除c中首元素,若c爲空,則函數行爲未定義。函數返回void
c.erase(p) 刪除迭代器p所指向的元素,返回一個指向被刪除元素之後元素的迭代器
c.erase(b,e) 刪除迭代器b和e所指向範圍內的元素。返回一個指向最後一個被刪除元素之後元素的迭代器
c.clear() 刪除c中的所有元素,返回void

Tip:

  • forward_list有特殊版本的erase
  • forward_list不支持pop_back;vector和string不支持pop_front。
  • 刪除deque中除首尾位置之外的任何元素都會使所有迭代器、引用、指針失效
  • 指向vector或string中刪除點之後位置的迭代器、引用、指針失效

3.4 特殊的forward_list操作

  forward_list是單向鏈表,由於在單向鏈表中,沒有簡單的方法來獲取一個元素的前驅,因此在一個forward_list中添加或刪除元素的操作是通過改變給定元素之後的元素來完成的。與其他容器的操作不同,forward_list定義了名爲insert_after、emplace_after和erase_after的操作。

在forward_list中插入或刪除元素操作
操作 描述
lst.before_begin() 返回指向鏈表首元素之前不存在的元素的迭代器,此迭代器不能解引用
lst.cbefore_begin() cbefore_begin()返回一個const_iterator
lst.insert_after(p, t) 在迭代器p之後的位置插入元素,t是一個對象
lst.insert_after(p, n, t) n表示數量
lst.insert_after(p, b, e) b和e表示範圍的一對迭代器
lst.insert_after(p, il) il表示一個花括號列表
emplace_insert(p, args) 使用agrs在p指定的位置之後創建一個元素。返回一個指向這個新元素的迭代器
lst.erase_after(p) 刪除p指向的位置之後的元素
lst.erase_after(b, e) 刪除b之後直到e之間的元素,返回一個指向被刪除元素之後元素的迭代器

3.5 改變容器大小

順序容器大小操作
操作 描述
c.resize(n) 調整c的大小爲n個元素
c.resize(n, t) 調整c的大小爲n個元素。任何新添加的元素都初始化爲值t

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

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

向容器添加元素後:

  • 如果容器是vector或string,且儲存空間被重新分配,則指向容器的迭代器、指針和引用都會失效。如果空間未出現分配,指向插入位置之前的元素的迭代器、指針和引用仍然有效,但在該位置之後的元素的迭代器、指針和引用將會失效
  • 對於deque,插入到除首尾之外的任何位置都會導致迭代器、指針和引用失效。如果在首尾位置添加元素,迭代器會失效,但指向存在的元素的引用和指針不會失效
  • 對於list和forward_list,指向容器的迭代器、指針和引用仍然有效。

在容器中刪除元素後:

  • 對於list和forward_list,指向容器其他位置的迭代器、引用指針仍然有效
  • 對於deque,若在首尾之外的任何位置刪除元素,那麼指向被刪除元素之外其他元素的迭代器、指針或引用會失效;若刪除首元素,則不受影響;若刪除尾元素,則尾後迭代器失效,其他不受影響
  • 對於vector或string,指向被刪除元素之前元素的迭代器、引用和指針仍然有效

4 vector對象是如何增長的

4.1 管理容量的成員函數

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

reserve並不改變容器中元素的數量,僅影響vector預先分配多大的內存空間。
只要沒有操作需求超出vector的容量,vector就不能重新分配內存空間。只有當在執行insert操作時,size與capacity相等,或者調用resize或reserve 時給定的大小超過當前capacity,vector纔可能重新分配內存空間,分配多少取決於具體實現。==如在需要分配新內存空間時將當前容量翻倍,此時可以調用shrink_to_fit來要求vector將超出當前大小的多餘內存退回給系統。==詳細說明將另外一篇文章vector對象增長說明

5 額外的string操作

5.1 構造string的其他方法

構造string的其他方法
操作 描述
string s(cp, n) s是cp指向的數組前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(),構造函數的行爲未定義

substr操作
示例如下:

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

string類還定義了兩個額外的成員函數:append和replace。append操作是在string末尾進行插入操作的一種簡寫形式。

s2.apppend("4th ed");        //等價方法:將"4th ed"追加到s2

5.2 string搜索操作

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默認爲0
s2, pos 從s中位置pos開始查找字符串s2pos默認爲0
cp, pos 從s中位置pos開始查找指針cp指向的以空字符結尾的C風格字符串。pos默認爲0
cp, pos, n 從s中位置pos開始查找指針cp指向的數組的前n個字符。pos和n無默認值

5.3 compare函數和數值轉換

compare的幾種參數形式
參數形式 描述
s2 比較ss2
pos1, n1, s2 將s中從pos1開始的n1個字符與s2進行比較
pos1, n1, s2, pos2, n2 s中從pos1開始的n1個字符與s2中從pos2開始的n2個字符進行比較
cp 比較scp指向的以空字符結尾的字符數組
pos1, n1, cp 將s中從pos1開始的n1個字符與cp指向的以空字符結尾的字符數組進行比較
pos1, n1, cp, n2 將s中從pos1開始的n1個字符與指針cp指向的地址開始的n2個字符進行比較
string和數值之間的轉換
操作 描述
to_string(val) 返回數值val的string表示,val可以是任何算術類型
stoi(s, p, b) stol(s, p, b) stoul(s, p, b) 返回s的起始子串(表示整數內容)的數值,返回類型爲int、long、
stoll(s, p, b) stoull(s, p, b) unsigned long、long long、unsigned long long。b表示轉換用的基數,默認爲10,p是size_t指針,用來保存s中第一個非數值字符的下標
stof(s, p) stod(s, p) stold(s, p) 返回s的起始子串(表示浮點數內容)的數值,返回類型分別是float、double、long double

6 容器適配器

  標準庫定義了三個順序容器適配器:stack、queue和priority_queue。

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

定義一個適配器:默認構造函數創建一個空對象,接受一個容器的構造函數拷貝改容器來初始化適配器。假定deq是一個deque<int>示例如下:

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

在構造適配器時,均要求容器具有添加、刪除以及訪問尾元素的能力。同時要注意以下幾點:

  • 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:

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();        //彈出棧頂元素,繼續循環
    
}
未列出的棧操作
操作 描述
s.pop() 刪除棧頂元素,但不返回該元素之
s.push(item) 創建一個新元素壓入棧頂,該元素通過拷貝或移動item而來
s.emplace(args) 或者由args構造
s.top() 返回棧頂元素,但不將元素彈出棧

棧默認基於deque實現,也可以再list或vector之上實現

隊列適配器

queue和priority_queue適配器定義在queue,以下是其支持的操作

未列出的queue和priority_queue操作
操作 描述
q.pop() 返回queue的首元素或priority_queue的最高優先級的元素
q.front() 返回首元素或尾元素,但不刪除此元素
q.back() 只適用於queue
q.top() 返回最高優先級元素,但不刪除該元素(只適用於priority_queue
q.push(item) 在queue末尾或priority_queue中恰當的位置創建一個元素,
q.emplace(agrs) 其值爲item,或者由agrs構造

queue默認基於deque實現,priority_queue默認基於vector實現;
queue也可以用list或vector實現,priority_queue也可以用deque實現;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章