日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

《C++ Primer》筆記 第11章 關聯(lián)容器

 Coder編程 2021-05-05
關聯(lián)容器類型 解釋
按關鍵字有序保存元素 ——
map 關聯(lián)數(shù)組;保存關鍵字-值對
set 關鍵字即值,即只保存關鍵字的容器
multimap 關鍵字可重復出現(xiàn)的map
multiset 關鍵字可重復出現(xiàn)的set
無序集合 ——
unordered_map 用哈希函數(shù)組織的map
unordered_set 用哈希函數(shù)組織的set
unordered_multimap 哈希組織的map;關鍵字可以重復出現(xiàn)
unordered_multiset 哈希組織的set;關鍵字可以重復出現(xiàn)
  1. 類型mapmultimap定義在頭文件map中;setmultiset定義在頭文件set中;無序容器則定義在頭文件unordered_mapunordered_set中。

    // 統(tǒng)計輸入中每個單詞出現(xiàn)的次數(shù)
    map<string, size_t> word_count; // string到size_t的空map
    set<string> exclude = {"The", "But", "And", "Or", "An", "A", "the", "but", "and", "or", "an", "a"};
    string word;
    while(cin >> word)
    // 只統(tǒng)計不在exclude中的單詞
    if (exclude.find(word) == exclude.end())
    ++word_count[word]; // 獲取并遞增word的計數(shù)器
    
  2. 關聯(lián)容器不支持順序容器的位置相關操作,例如push_frontpush_back。原因是關聯(lián)容器中元素是根據(jù)關鍵字存儲的,這些操作對關聯(lián)容器沒有意義。關聯(lián)容器也不支持構造函數(shù)或插入操作這些接受一個元素值和一個數(shù)量值的操作。

  3. 關聯(lián)容器的迭代器都是雙向的。

  4. 定義關聯(lián)容器。當初始化一個map時,必須提供關鍵字類型和值類型。我們將每個關鍵字-值對包圍在花括號中:{key, value}來指出它們一起構成了map中的一個元素。在每個花括號中,關鍵字是第一個元素,值是第二個。

    map<string, size_t> word_count; // 空容器
    // 列表初始化
    set<string> exclude = {"The", "But", "And", "Or", "An", "A", "the", "but", "and", "or", "an", "a"};
    // 三個元素;authors將姓映射為名
    map<string, string> authors = { {"Joyce", "James"}, {"Austen", "Jane"}, {"Dickens", "Charles"} };
    // 定義一個有20個元素的vector
    vector<int> ivec = {0,1,2,3,4,5,6,7,8,9,0,1,2,3,4,5,6,7,8,9};
    // iset包含來自ivec的不重復的10個元素;miset包含所有20個元素
    set<int> iset(ivec.cbegin(), ivec.cend());
    multiset<int> miset(ivec.cbegin(), ivec.cend());
    
  5. 對有序容器——map、multimap、set以及multiset,關鍵字類型必須定義元素比較的方法。默認情況下,標準庫使用關鍵字類型的<運算符來比較兩個關鍵字。在集合類型中,關鍵字類型就是元素類型;在映射類型中,關鍵字類型是元素的第一部分的類型。

  6. 傳遞給排序算法的可調用對象必須滿足與關聯(lián)容器中關鍵字一樣的類型要求。

  7. 可以向一個算法提供我們自定義的比較操作,與之類似,也可以提供自己定義的操作來替代關鍵字上的<運算符。所提供的操作必須在關鍵字類型上定義一個嚴格弱序??梢詫栏袢跣蚩醋鳌靶∮诘扔凇?,雖然實際定義的操作可能是一個復雜的函數(shù)。無論我們怎樣定義比較函數(shù),它必須具備如下基本性質:

    • 兩個關鍵字不能同時“小于等于”對方;如果k1“小于等于”k2,那么k2絕對不能“小于等于”k1。
    • 如果k1“小于等于”k2,且k2“小于等于”k3,那么k1必須“小于等于”k3。
    • 如果存在兩個關鍵字,任何一個都不“小于等于”另一個,那么我們稱這兩個關鍵字是“等價”的。如果k1“等價于”k2,且k2“等價于”k3,那么k1必須“等價于”k3。
  8. 在實際編程中,重要的是,如果一個類型定義了“行為正?!钡?lt;運算符,則它可以用作關鍵字類型。

  9. 用來組織一個容器中元素的操作的類型也是該容器類型的一部分。為了指定使用自定義的操作,必須在定義關聯(lián)容器類型時提供此操作的類型。

    bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)
    {
    return lhs.isbn() < rhs.isbn();
    }
    
    // bookstore中多條記錄可以相同的ISBN
    // 用compareIsbn來初始化bookstore對象,這表示我們向bookstore添加元素時,通過調用compareIsbn來為這些元素排序
    // 即,bookstore中的元素以ISBN的順序進行排列
    multiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);
    
  10. pair的標準庫類型,定義在頭文件utility中。一個pair保存兩個數(shù)據(jù)成員。當創(chuàng)建一個pair時,我們必須提供兩個類型名,pair的數(shù)據(jù)成員將具有對應的類型。兩個類型不要求一樣。

  11. pair的默認構造函數(shù)對數(shù)據(jù)成員進行值初始化。也可以為每個成員提供初始化器:

    pair<string, string> anon; // anon是一個包含兩個空string的pair
    pair<string, string> author{"James", "Joyce"};
    
  12. 與其他標準庫類型不同,pair的數(shù)據(jù)成員是public的,兩個成員分別命名為first和second。我們用普通的成員訪問符號來訪問他們。

  13. map的元素是pair。

pair上的操作 解釋
pair<T1, T2> p; p是一個pair,兩個類型分別為T1和T2的成員都進行了值初始化
pair<T1, T2> p(v1, v2) p是一個成員類型為T1和T2的pair;first和second成員分別用v1和v2進行初始化
pair<T1, T2> p = {v1, v2}; 等價于p(v1, v2)
make_pair(v1, v2) 返回一個用v1和v2初始化的pair。pair的類型從v1和v2的類型推斷出來
p.first 返回p的名為first的(公有)數(shù)據(jù)成員
p.second 返回p的名為second的(公有)數(shù)據(jù)成員
p1 relop p2 關系運算符(<、>、<=、>=)按字典序定義:例如,當p1.first < p2.first!(p2.first < p1.first) && p1.second < p2.second成立時,p1 < p2true。關系運算符利用元素的<運算符來實現(xiàn)
p1 == p2或p1 != p2 當first和second成員分別相等時,兩個pair相等。相等性判斷利用元素的==運算符實現(xiàn)
pair<string, int> process(vector<string> &v)
{
// 處理v
if (!v.empty())
return {v.back(), v.back().size()}; // 列表初始化,下述等價
    // return pair<string, int>(v.back(), v.back().size());
    // return make_pair(v.back(), v.back().size());
else
return pair<string, int>(); // 隱式構造返回值
}
關聯(lián)容器額外的類型別名 解釋
key_type 此容器類型的關鍵字類型
mapped_type 每個關鍵字關聯(lián)的類型;只適用于map
value_type 對于set,與key_type相同;對于map,為pair<const key_type, mapped_type>
  1. 對于set類型,key_typevalue_type是一樣的;set中保存的值就是關鍵字。在一個map中,元素是關鍵字-值對。即,每個元素是一個pair對象,包含一個關鍵字和一個關聯(lián)的值。由于我們不能改變一個元素的關鍵字,因此這些pair的關鍵字部分是const的。

    set<string>::value_type v1; // v1是一個string
    set<string>::key_type v2; // v2是一個string
    map<string, int>::value_type v3; // v3是一個pair<const string, int>
    map<string, int>::key_type v4; // v4是一個string
    map<string, int>::mapped_type v5; // v5是一個int
    
  2. 當解引用一個關聯(lián)容器迭代器時,我們會得到一個類型為容器的value_type的值的引用。對map而言,value_type是一個pair類型,其first成員保存const的關鍵字,second成員保存值。

    // 獲得指向word_count中一個元素的迭代器
    auto map_it = word_count.begin();
    // *map_it是指向一個pair<const string, size_t>對象的引用
    cout << map_it->first; //打印此元素的關鍵字
    cout << " " << map_it->second; // 打印此元素的值
    map_it->first = "new key"; // 錯誤:關鍵字是const的
    ++map_it->second; // 正確:我們可以通過迭代器改變元素
    
  3. 雖然set類型同時定義了iterator和const_iterator類型,但兩種類型都只允許只讀訪問set中的元素。與不能改變一個map元素的關鍵字一樣,一個set中的關鍵字也是const的??梢杂靡粋€set迭代器來讀取元素的值,但不能修改:

    set<int> iset = {0,1,2,3,4,5,6,7,8,9};
    set<int>::iterator set_it = iset.begin();
    if (set_it != iset.end())
    {
    *set_it = 42; // 錯誤:set中的關鍵字是只讀的
    cout << *set_it << endl; // 正確:可以讀關鍵字
    }
    
  4. 當使用一個迭代器遍歷一個map、multimap、set或multiset時,迭代器按關鍵字升序遍歷元素。

    // 獲得一個指向首元素的迭代器
    auto map_it = word_count.cbegin();
    // 比較當前迭代器和尾后迭代器
    while (map_it != word_count.cend())
    {
    // 解引用迭代器,打印關鍵字-值對
    cout << map_it->first << " occurs " << map_it->second << " times" << endl;
    ++map_it; // 遞增迭代器,移動到下一個元素
    }
    
  5. 關聯(lián)容器的insert成員向容器中添加一個元素或一個元素范圍。由于map和set包含不重復的關鍵字,因此插入一個已存在的元素對容器沒有任何影響。

  6. insert有兩個版本,分別接受一對迭代器,或是一個初始化器列表,這兩個版本的行為類似對應的構造函數(shù)(對于一個給定的關鍵字,只有第一個帶此關鍵字的元素才被插入到容器中)。

  7. 對一個map進行insert操作時,必須記住元素類型是pair:

    // 向word_count插入word的4種方法
    word_count.insert({word, 1});
    word_count.insert(make_pair(word, 1));
    word_count.insert(pair<string, size_t>(word, 1));
    word_count.insert(map<string, size_t>::value_type(word, 1));
    
關聯(lián)容器insert操作 解釋
c.insert(v)或c.emplace(args) v是value_type類型的對象;args用來構造一個元素。對于map和set,只有當元素的關鍵字不在c中時才插入(或構造)元素。函數(shù)返回一個pair,包含一個迭代器,指向具有指定關鍵字的元素,以及一個指示插入是否成功的bool值。對于multimap和multiset,總會插入(或構造)給定元素,并返回一個指向新元素的迭代器。
c.insert(b, e)或c.insert(il) b和e是迭代器,表示一個c::value_type類型值的范圍;il是這種值的花括號列表。函數(shù)返回void。對于map和set,只插入關鍵字不在c中的元素。對于multimap和multiset,則會插入范圍中的每個元素。
c.insert(p, v)或c.emplace(p, args) 類似insert(v)(或emplace(args)),但將迭代器p作為一個提示,指出從哪里開始搜索新元素應該存儲的位置。返回一個迭代器,指向具有給定關鍵字的元素。
從關聯(lián)容器刪除元素 解釋
c.erase(k) 從c中刪除每個關鍵字為k的元素。返回一個size_type值,指出刪除的元素的數(shù)量
c.erase(p) 從c中刪除迭代器p指定的元素。p必須指向c中一個真實元素,不能等于c.end()。返回一個指向p之后元素的迭代器,若p指向c中的尾元素,則返回c.end()
c.erase(b,e) 刪除迭代器對b和e所表示的范圍中的元素。返回e
map和unordered_map的下標操作 解釋
c[k] 返回關鍵字為k的元素;如果k不在c中,添加一個關鍵字為k的元素,對其進行值初始化
c.at(k) 訪問關鍵字為k的元素,帶參數(shù)檢查;若k不在c中,拋出一個out_of_range異常
  1. map和unordered_map容器提供了下標運算符和一個對應的at函數(shù)。

  2. set類型不支持下標,因為set中沒有與關鍵字相關聯(lián)的“值”。

  3. 我們不能對一個multimap或一個unordered_multimap進行下標操作,因為這些容器中可能有多個值與一個關鍵字相關聯(lián)。

  4. 如果關鍵字并不在map中,會為它創(chuàng)建一個元素并插入到map中,關聯(lián)值將進行值初始化。

  5. 由于下標運算符可能插入一個新元素,我們只可以對非const的map使用下標操作。

  6. 通常情況下,解引用一個迭代器所返回的類型與下標運算符返回的類型是一樣的。但對map則不然:當對一個map進行下標操作時,會獲得一個mapped_type對象;但當解引用一個map迭代器時,會得到一個value_type對象。

  7. 與其他下標運算符相同的是,map的下標運算符返回一個左值。

  8. 另一方面,有時只是想知道一個元素是否已在map中,但在不存在時并不想添加元素。在這種情況下,就不能使用下標運算符,應該使用find:

    if (word_count.find("foobar") == word_count.end())
    cout << "foobar is not in the map" << endl;
    
在一個關聯(lián)容器中查找元素的操作 解釋
—— lower_bound和upper_bound不適用于無序容器
—— 下標和at操作只適用于非const的map和unordered_map
c.find(k) 返回一個迭代器,指向第一個關鍵字為k的元素,若k不在容器中,則返回尾后迭代器
c.count(k) 返回關鍵字等于k的元素的數(shù)量。對于不允許重復關鍵字的容器,返回值永遠是0或1
c.lower_bound(k) 返回一個迭代器,指向第一個關鍵字不小于k的元素
c.upper_bound(k) 返回一個迭代器,指向第一個關鍵字大于k的元素
c.equal_range(k) 返回一個迭代器pair,表示關鍵字等于k的元素的范圍。若k不存在,pair的兩個成員均等于c.end()
  1. 在multimap或multiset中查找元素

    string search_item("Alain de Botton"); // 要查找的作者
    auto entries = authors.count(search_item); // 元素的數(shù)量
    auto iter = authors.find(search_item); // 此作者的第一本書
    // 用一個循環(huán)查找此作者的所有著作
    while (entries)
    {
    cout << iter->second << endl; // 打印每個題目
    ++iter; // 前進到下一本書
    --entries; // 記錄已經打印了多少本書
    }
    // 當我們遍歷一個multimap或multiset時,保證可以得到序列中所有具有給定關鍵字的元素
    
  2. 用相同的關鍵字調用lower_bound和upper_bound會得到一個迭代器范圍,表示所有具有該關鍵字的元素的范圍。如果關鍵字在容器中,lower_bound返回的迭代器將指向第一個具有給定關鍵字的元素,而upper_bound返回的迭代器則指向最后一個匹配給定關鍵字的元素之后的位置。如果元素不在multimap或multiset中,則lower_bound和upper_bound會返回相等的迭代器——指向一個不影響排序的關鍵字插入位置。

    // authors和search_item的定義,與前面的程序一樣
    // beg和end表示對應此作者的元素的范圍
    for (auto beg = authors.lower_bound(search_item), 
         end = authors.upper_bound(search_item); 
         beg != end; 
         ++beg)
    cout << beg->second << endl; // 打印每個題目
    // lower_bound返回的迭代器可能指向一個具有給定關鍵字的元素,但也可能不指向。
    // 如果關鍵字不在容器中,則lower_bound會返回關鍵字的第一個安全插入點——不影響容器中元素順序的插入位置。
    
  3. 直接調用equal_range:此函數(shù)接受一個關鍵字,返回一個迭代器pair。若關鍵字存在,則第一個迭代器指向第一個與關鍵字匹配的元素,第二個迭代器指向最后一個匹配元素之后的位置。若未找到匹配元素,則兩個迭代器都指向關鍵字可以插入的位置。

    // authors和search_item的定義,與前面的程序一樣
    // pos保存迭代器對,表示與關鍵字匹配的元素范圍
    for (auto pos = authors.equal_range(search_item); 
         pos.first != pos.second; 
         ++pos.first)
    cout << pos.first->second << endl; // 打印每個題目
    // 此程序本質上與前一個使用upper_bound和lower_bound的程序是一樣的。
    
  4. 綜合應用(一個單詞轉換的map):

#include <map>
#include <vector>
#include <iostream>
#include <fstream>
#include <string>
#include <stdexcept>
#include <sstream>

using std::cout;
using std::endl;
using std::getline;
using std::ifstream;
using std::istringstream;
using std::map;
using std::runtime_error;
using std::string;
using std::vector;

map<string, string> buildMap(ifstream &map_file)
{
    map<string, string> trans_map; // holds the transformations
    string key;                    // a word to transform
    string value;                  // phrase to use instead
    // read the first word into key and the rest of the line into value
    while (map_file >> key && getline(map_file, value))
    {
        if (value.size() > 1) // check that there is a transformation
        {
            trans_map[key] = value.substr(1); // skip leading space
        }
        else
        {
            throw runtime_error("no rule for " + key);
        }
    }
    return trans_map;
}

const string &transform(const string &s, const map<string, string> &m)
{
    // the actual map work; this part is the heart of the program
    auto map_it = m.find(s);
    // if this word is in the transformation map
    if (map_it != m.cend())
    {
        return map_it->second; // use the replacement word
    }
    else
    {
        return s; // otherwise return the original unchanged
    }
}

// first argument is the transformations file;
// second is file to transform
void word_transform(ifstream &map_file, ifstream &input)
{
    auto trans_map = buildMap(map_file); // store the transformations

    // for debugging purposes print the map after its built
    cout << "Here is our transformation map: \n\n";
    for (auto entry : trans_map)
    {
        cout << "key: " << entry.first << "\tvalue: " << entry.second << endl;
    }
    cout << "\n\n";

    // do the transformation of the given text
    string text;                 // hold each line from the input
    while (getline(input, text)) // read a line of input
    {
        istringstream stream(text); // read each word
        string word;
        bool firstword = true; // controls whether a space is printed
        while (stream >> word)
        {
            if (firstword)
            {
                firstword = false;
            }
            else
            {
                cout << " "; // print a space between words
            }
            // transform returns its first argument or its transformation
            cout << transform(word, trans_map); // print the output
        }
        cout << endl; // done with this line of input
    }
}

int main(int argc, char **argv)
{
    // open and check both files
    if (argc != 3)
    {
        throw runtime_error("wrong number of arguments");
    }

    ifstream map_file(argv[1]); // open transformation file
    if (!map_file)              // check that open succeeded
    {
        throw runtime_error("no transformation file");
    }

    ifstream input(argv[2]); // open file of text to transform
    if (!input)              // check that open succeeded
    {
        throw runtime_error("no input file");
    }

    word_transform(map_file, input);

    return 0; // exiting main will automatically close the files
}
  1. 管理桶:無序容器在存儲上組織為一組桶,每個桶保存零個或多個元素。無序容器使用一個哈希函數(shù)將元素映射到桶。為了訪問一個元素,容器首先計算元素的哈希值,它指出應該搜索哪個桶。容器將具有一個特定哈希值的所有元素都保存在相同的桶中。如果容器允許重復關鍵字,所有具有相同關鍵字的元素也都會在同一個桶中。因此,無序容器的性能依賴于哈希函數(shù)的質量和桶的數(shù)量和大小。
無序容器管理操作 解釋
桶接口 ——
c.bucket_count() 正在使用的桶的數(shù)目
c.max_bucket_count() 容器能容納的最多的桶的數(shù)量
c.bucket_size(n) 第n個桶中有多少個元素
c.bucket(k) 關鍵字為k的元素在哪個桶中
桶迭代 ——
local_iterator 可以用來訪問桶中元素的迭代器類型
const_local_iterator 桶迭代器的const版本
c.begin(n), c.end(n) 桶n的首元素迭代器和尾后迭代器
c.cbegin(n), c.cend(n) 與前兩個函數(shù)類似,但返回const_local_iterator
哈希策略 ——
c.load_factor() 每個桶的平均元素數(shù)量,返回float值
c.max_load_factor() c試圖維護的平均桶大小,返回float值。c會在需要時添加新的桶,以使得load_factor<=max_load_factor
c.rehash(n) 重組存儲,使得bucket_count>=n且bucket_count>size/max_load_factor
c.reserve(n) 重組存儲,使得c可以保存n個元素且不必rehash
  1. 默認情況下,無序容器使用關鍵字類型的==運算符來比較元素,它們還使用一個hash<key_type>類型的對象來生成每個元素的哈希值。標準庫為內置類型(包括指針)提供了hash模板。還為一些標準庫類型,包括string和智能指針類型定義了hash。因此,我們可以直接定義關鍵字是內置類型(包括指針類型)、string還是智能指針類型的無序容器。

  2. 但是,我們不能直接定義關鍵字類型為自定義類類型的無序容器。我們需要提供函數(shù)來替代==運算符和哈希值計算函數(shù)。

    size_t hasher(const Sales_data &sd)
    {
        // 我們的hasher函數(shù)使用一個標準庫hash類型對象來計算ISBN成員的哈希值
        // 該hash類型建立在string類型之上
    return hash<string>()(sd.isbn());
    }
    bool eqOp(const Sales_data &lhs, const Sales_data &rhs)
    {
        // eqOp函數(shù)通過比較ISBN號來比較兩個Sales_data
    return lhs.isbn() == rhs.isbn();
    }
    
    using SD_multiset = unordered_multiset<Sales_data, decltype(hasher)*, decltype(eqOp)*>;
    // 參數(shù)是桶大小、哈希函數(shù)指針和相等性判斷運算符指針
    SD_multiset bookstore(42, hasher, eqOp);
    
  3. 如果我們的類定義了==運算符,則可以只重載哈希函數(shù)。

    // 使用FooHash生成哈希值;Foo必須有==運算符
    unordered_set<Foo, decltype(FooHash)*> fooSet(10, FooHash);
    
  4. 有序容器使用比較函數(shù)來比較關鍵字,從而將元素按順序存儲。默認情況下,比較操作是采用關鍵字類型的<運算符。無序容器使用關鍵字類型的==運算符和一個hash<key_type>類型的對象來組織元素。

    本站是提供個人知識管理的網絡存儲空間,所有內容均由用戶發(fā)布,不代表本站觀點。請注意甄別內容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內容,請點擊一鍵舉報。
    轉藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多