C++ 學習筆記之(9)-順序容器及適配器

C++ 學習筆記之(9)-順序容器及適配器

順序容器就是元素的順序與其加入容器時的位置相對應,而 關聯容器中元素的位置由元素相關聯的關鍵字值決定。所有容器類都共享公共的藉口,只是不同的容器會按不同方式對其進行擴展。

順序容器概述

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

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

  • 除了固定大小的array外,其他容器都提供高效、靈活的內存管理

  • stringvector將元素保存在連續的內存空間中,故支持隨機訪問,但中間位置添加和刪除操作非常耗時
  • listforward_list在任何位置添加和刪除操作都很快速,但不支持隨機訪問
  • deque支持隨機訪問,但中間位置添加和刪除元素代價很高,但兩端添加或刪除元素很快
  • forward_listarray是C++11增加的類型,array大小固定,不支持添加和刪除以及其他改變容器大小的操作。

容器庫概覽

迭代器

  • 迭代器範圍(iterator range):由一對迭代器表示,兩個迭代器分別指向同一個容器中的元素或者是尾元素之後的位置,通常被稱爲beingend 範圍是左閉合區間[begin, end)

 容器類型成員

容器定義了多個類型,如圖所示

container_type_alias

beginend成員

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

容器定義和初始化

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

container_define_and_initializer

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

兩種方法

  • 直接拷貝整個容器,要求兩個容器的類別及其元素類型必須相同

  • 拷貝由一個迭代器對指定的元素範圍,array除外, 不要求容器類別相同,元素類型也可以不用,但要能互相轉換

    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);  // 錯誤:容器類型必須匹配
    forward_list<string> words(articles.begin(), articles.end());  // 正確:元素類型可以轉換
  • 標準庫array的大小是類型的一部分,定義array時,除了指定元素類型,還要指定容器大小

    array<int, 42>;  // 類型爲:保存 42 個 int 的數組
    array<int, 10>::site_type i;  // 數組類型包括元素類型和大小
    array<int>::sizt_type j;  // 錯誤: array<int> 不是一個類型
  • 內置數組類型無法進行拷貝或對象賦值操作,但array可以

賦值和 swap

下表列出的與賦值相關的運算符可用於所有容器

container_assignment_operations

  • swap操作交換兩個相同類型容器的內容, 元素本身併爲交換, 只是交換了兩個容器的內部數據結構,故可在常數時間內完成,且指向容器的迭代器、引用和指針在swap操作後不會失效
  • swap兩個array會真正交換他們的元素,所需時間與array中元素數目成正比,且雖然指針、引用和迭代器綁定的元素保持不變,但元素值已經交換了。

容器大小操作

每個容器類型都有三個大小相關操作

  • 成員函數 size:返回容器中元素的數目, 但forward_list不支持size
  • empty:當size0時,返回true
  • max_size:返回一個大於或等於該類型容器所能容納的最大元素數的值

關係運算符

每個容器類型都支持相等運算符==!=, 除了無序關聯容器外的所有容器都支持關係運算符>、>=、<、<=

  • 若兩個容器大小相同且元素相等,則相等
  • 若大小不同,但較小容器每個元素都等於較大容器的對應元素時,較小容器大於較大容器
  • 若兩個容器都不是另一個容器的前綴子序列,結果取決於第一個不相等的元素的比較結果

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

順序容器操作

順序容器和關聯容器的不同之處在於組織元素的方式,所以導致元素的存儲、訪問、添加和刪除的不同。下面介紹順序容器特有的操作

向順序容器中添加元素

sequence_container_append_item

  • 對於vectorstring的尾部之外的位置添加元素,都需要移動元素,可能還會導致整個對象存儲空間的重新分配。重新分配對象的存儲空間需要分配新的內存,並將元素從舊空間移動到新空間中

  • 對於deque 首尾之外的任何位置添加元素,都要移動元素,

  • 當用對象初始化容器或插入到容器中時,實際上放入到容器中的是對象值的拷貝,並不是對象本身。

  • 記住,insert插入在元素之前,並且返回指向新插入元素的迭代器

  • emplace操作將參數傳遞給元素類型的構造函數,直接在容器管理的內存空間中構造函數。而pushinsert操作是傳遞元素類型對象,然後拷貝到容器中

    // 在 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", 25, 15.99));

訪問元素

sequence_container_access_item

  • 訪問成員函數(即frontback、下標和at)返回的都是引用。若容器是const對象,則返回值是const引用
  • 下標運算符不檢查下標是否合法,若想確保下標合法,可使用at成員函數,越界會拋出out_of_range異常

刪除元素

sequence_container_delete_item

特殊的forward_list操作

由於在forward_list中,添加或刪除元素都會改變後繼元素,所以添加和刪除的操作都是通過改變給定元素之後的元素完成的。

forward_list_insert_and_delete_item

改變容器大小

如圖所示,可使用resize增大或縮小容器。array不支持

sequence_container_resize_operation

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

對容器進行添加或刪除元素的操作可能會使指向容器元素的指針、引用或迭代器失效

添加元素

  • vectorstring
    • 重新分配存儲空間:迭代器、指針和引用都會失效
    • 未重新分配存儲空間:指向插入位置之前的元素的迭代器、指針和引用仍有效,指向之後的將失效
  • deque
    • 插入到除首尾位置之外的任何位置:都會導致迭代器、指針和引用失效
    • 若在首尾位置添加元素:迭代器失效,但指針和引用有效
  • listforward_list
    • 指向容器的迭代器(包括尾後迭代器和首前迭代器)、指針和引用仍有效

刪除元素(指向被刪除元素的迭代器、指針和引用都會失效)

  • vectorstring
    • 指向被刪除元素之前的迭代器、引用和指針:仍有效
  • deque
    • 若在首尾之外的任何位置刪除元素:都會失效
    • 若刪除尾元素:尾後迭代器也會失效,其他不受影響
    • 刪除首元素:都有效
  • listforward_list
    • 都有效

vector對象是如何增長的

vector爲了支持快速隨機訪問,故將元素連續存儲。當空間不足時,就會重新分配空間,將元素從舊空間移動到新空間,然後添加新元素,釋放舊存儲空間。 vectorstring通常會分配比新空間需求更大的內存空間,預留些空間做備用,可以減少容器空間重新分配的次數

sequence_container_size_management_operations
* vector的實現策略是每次分配新內存空間時將當前容量翻倍
* shrink_to_fit請求vector將超出當前大小的多餘內存退回給系統,但標準庫並不保證

額外的string操作

構造string的其他方法

C++ 學習筆記之(3)-字符串、向量和數組 介紹過string的構造函數,以及與其他順序容器相同的構造函數外,string還有其他三個構造函數,如表所示

string_other_constructors

  • 若用const char *創建string時,指針執行的數組必須以空字符結尾,拷貝操作遇到空字符時停止

substr 操作

string_substr_operation

改變string的其他方法

  • replace草市是調用eraseinsert的一種簡寫形式

string_modification_operations

string_modification_operations_2

string搜索操作

  • string::nposstatic成員,類型爲const string::size_type類型,初始值爲-1,無符號類型
    string_search_operations

compare函數

除了關係運算符外,string類型還提供了一組compare函數,與C標準庫的strcmp函數很相似

string_compare_functions

數值轉換

新標準引入了多個函數,用來實現數值數據與標準庫string之間的轉換。

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

    string s2 = "pi = 3.14";
    // 轉換 s2 中以數字開始的第一個子串,結果 d = 3.14
    d = stod(s2.substr(s2.find_first_of("+-.0123456789")));
  • string不能轉換爲一個數值,則函數拋出invalid_argument異常。若轉換得到的數值無法用任何類型表示,則拋出out_of_range異常
    string_to_number_functions

容器適配器

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

  • 適配器(adaptor):標準庫的通用概念。容器、迭代器和函數都有適配器,其本質上是一種機制,可以是某種事物的行爲表現成另一種事物。

adaptor_operations_and_types

  • 默認情況下, stackqueue基於deque實現的, priority_queue是在vector之上實現的

  • 所有適配器都要求容器具有添加和刪除元素的能力,故適配器不能構造在array之上

棧適配器

stack只要求push_backpop_backback操作,故可使用除arrayforward_list之外的任何容器構造

stack_adaptor_operations

隊列適配器

  • queue適配器要求backpush_backfrontpush_front, 故可構造於listqueue之上,但不能基於vector構造
  • priority_queue除了frontpush_backpop_back操作外,還要求隨機訪問能力, 故可構造於vectordeque之上,但不能基於list構造
    queue_and_priority_queue_operations

結語

  • 標準庫容器是模板類型,用來保存給定類型的對象。順序容器中元素是順序存放的,通過位置來訪問
  • 所有容器(除array外)都提供高效的動態內存管理
  • 在容器中執行添加和刪除操作後,要注意這些操作可能是其迭代器、引用和指針失效
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章