日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

第11 章 关联容器

發布時間:2023/12/20 编程问答 64 豆豆
生活随笔 收集整理的這篇文章主要介紹了 第11 章 关联容器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

第11 章 關聯容器

關聯容器和順序容器有著根本的不同:關聯容器中的元素是按關鍵字來保存和訪問的。與之相對,順序容器中的元素是按它們在容器中的位置來順序保存和訪問的。

雖然關聯容器的很多行為與順序容器相同,但其不同之處反映了關鍵字的作用。

關聯容器支持高效的關鍵字查找和訪問。兩個主要的關聯容器(associative-container)類型是 mapset。map 中的元素是一些關鍵字-值(key-value)對:關鍵字起到索引的作用,值則表示與索引相關聯的數據。set 中每個元素只包含一個關鍵字:set 支持高效的關鍵字查詢操作——檢查一個給定關鍵字是否在 set 中。例如,在某些文本處理過程中,可以用一個 set 來保存想要忽略的單詞。字典則是一個很好的使用 map 的例子:可以將單詞作為關鍵字,將單詞釋義作為值。

標準庫提供 8 個關聯容器,如下表所示。

關聯容器類型 按關鍵字有序保存元素
map關聯數組:保存關鍵字-值對
set關鍵字即值,即只保存關鍵字的容器
multimap關鍵字可重復出現的 map
multiset關鍵字可以重復出現的 set
無序集合
unordered_map用哈希函數組織的 map
unordered_set用哈希函數組織的 set
unordered_multimap哈希組織的 map:關鍵字可以重復出現
unordered_multiset哈希組織的 set:關鍵字可以重復出現

11.1 使用關聯容器

map 是關鍵字-值對的集合。例如,可以將一個人的名字作為關鍵字,將其電話號碼作為值。我們稱這樣的數據結構為“將名字映射到電話號碼“。map 類型通常被稱為關聯數組(associative array)。關聯數組與“正常”數組類似,不同之處在于其下標不必是整數。我們通過一個關鍵字而不是位置來查找值。給定一個名字到電話號碼的 map,我們可以使用一個人的名字作為下標來獲取此人的電話號碼。

與之相對,set 就是關鍵字的簡單集合。當只是想知道一個值是否存在時,set 是最有用的。例如,一個企業可以定義一個名為 bad_checks 的 set 來保存那些曾經開過空頭支票的人的名字。在接受一張支票之前,可以查詢 bad_checks 來檢查顧客的名字是否在其中。

使用 map

一個經典的使用關聯數組的例子是單詞計數程序:

// 統計每個單詞在輸入中出現的次數 void count_word() {map<string, int> word_count; // string 到 int 的空 mapstring word;while (cin>>word){++word_count[word]; // 提取 word 的計數器并將其加一}for (const auto &w : word_count)// 對 map 中的每個元素{// 打印結果cout << w.first << " occurs " << w.second<< ((w.second > 1) ? " times" : " time") << endl;} }

此程序讀取輸入,報告每個單詞出現多少次。

類似順序容器,關聯容器也是模板。為了定義一個 map,我們必須指定關鍵字和值的類型。在此程序中,map 保存的每個元素中,關鍵字是 string 類型,值是 int 類型。當對 word_count 進行下標操作時,我們使用一個 string 作為下標,獲得與此 string 相關聯的 int 類型的計數器。

使用 set

上一個示例程序的一個合理擴展是:忽略常見單詞,如 “the”、“and”、"or"等。我們可以使用 set 保存想忽略的單詞,只對不出現在集合中的單詞統計出現次數。

// 統計每個單詞在輸入中出現的次數 void count_word1() {map<string, int> word_count; // string 到 int 的空 mapset<string> exclude = { "The","But","And","Or","An","A","the","but","and","or","an","a" };string word;while (cin >> word){// 只統計不在 exclude 中的單詞if (exclude.find(word) == exclude.end()){++word_count[word]; // 獲取 word 的計數器并將其加一} }for (const auto &w : word_count)// 對 map 中的每個元素{// 打印結果cout << w.first << " occurs " << w.second<< ((w.second > 1) ? " times" : " time") << endl;} }

與其他容器類似,set 也是模板。為了定義一個 set ,必須指明其元素類型,本例中是 string。與順序容器類似,可以對一個關聯容器的元素進行列表初始化(9.2.4節)。集合 exclude 保存了 12 個我們想忽略的單詞。

此程序與前一個程序的重要不同是,在統計每個單詞出現次數之前,我們檢查單詞是否在忽略集合中,這是在 if 語句中完成的:

if (exclude.find(word) == exclude.end())

find 調用返回一個迭代器。如果給定關鍵字在 set 中,迭代器指向該關鍵字。否則,find 返回尾后迭代器。在此程序中,僅當 word 不在 exclude 中時我們才更新 word 的計數器。

11.1 節練習

練習 11.1:描述 map 和 vector 的不同。

兩類容器的根本在于,順序容器中的元素是”順序“ 存儲的(鏈表容器中的元素雖然不是在內存中”連續“存儲的,但仍然是按”順序“存儲的)。理解”順序“ 的關鍵,是理解容器支持的操作形式以及效率。

對于 vector 這樣的順序容器,元素在其中按順序存儲,每個元素有唯一對應的位置編號,所有操作都是按編號(位置)進行的。例如,獲取元素(頭、尾、用下標獲取任意位置)、插入刪除元素(頭、尾、任意位置)、遍歷元素(按元素位置順序逐一訪問)。底層的數據結構是數組、鏈表,簡單但已能保證上述操作的高效。而對于依賴值的元素訪問,例如查找(搜索)給定值(find),在這種數據結構上的實現是要通過遍歷完成的,效率不佳。

而 map 這種關聯容器,就是為了高效實現“按值訪問元素”這類操作而設計的。為了達到這一目的,容器 中的元素是按關鍵字存儲的,關鍵字值與元素數據建立起對應關系,這就是“關聯”的含義。底層數據結構是紅黑數、哈希表等,可高效實現按關鍵字值查找、添加、刪除等操作。

練習 11.2:分別給出最適合使用 list、vector、deque、map 以及 set 的例子。

若元素很小(例如 int ),大致數量預先可知,在程序運行過程中不會劇烈變化。大部分情況下只在末尾添加或刪除需要頻繁訪問任意位置的元素,則 vector 可帶來最高的效率。若需頻繁在頭部和尾部添加或刪除元素,則 deque 是最好的選擇。

如果元素較大(如大的類對象),數量預先不知道,或是程序運行過程中頻繁變化,對元素的訪問更多是順序訪問全部或很多元素,則 list 很適合。

map 很適合對一些對象按它們的某個特征進行訪問的情形。典型的例如按學生的名字來查詢學生信息,即可將學生名字作為關鍵字,將學生信息作為元素值,保存在 map 中。

set,顧名思義,就是集合類型。當需要保存特定的值集合——通常滿足/不滿足某種要求的值集合,用 set 最方便。

// 練習 11.3:編寫你自己的單詞計數程序 void count_word() {map<string, int> word_count; // string 到 int 的空 mapstring word;while (cin >> word){++word_count[word]; // 提取 word 的計數器并將其加一}for (const auto &w : word_count)// 對 map 中的每個元素{// 打印結果cout << w.first << " 出現了 " << w.second<< " 次" << endl;} } // 練習 11.4:擴展你的程序,忽略大小寫和標點。例如,“example."、"example,"和”Example"應該遞增相同的計數器。// 編寫函數 trans,將單詞中的標點去掉,將大寫轉換為小寫 string &trans(string &s) {for (int p = 0; p < s.size(); p++){if (s[p] >= 'A' && s[p] <= 'Z'){s[p] -= ('A' - 'a');}else if (s[p] == ',' || s[p] == '.'){s.erase(p, 1);}}return s; } void count_word1() {map<string, int> word_count; // string 到 int 的空 mapstring word;while (cin >> word){++word_count[trans(word)]; // 獲取 word 的計數器并將其加一}for (const auto &w : word_count)// 對 map 中的每個元素{// 打印結果cout << w.first << " 出現了 " << w.second<< " 次" << endl;} }

11.2 關聯容器概述

關聯容器(有序的和無序的)都支持9.2節中介紹的普通容器操作。關聯容器不支持順序容器的位置相關的操作,例如 push_front 或 push_back。原因是關聯容器中元素是根據關鍵字存儲的,這些操作對關聯容器沒有意義。而且,關聯容器也不支持構造函數或插入操作這些接受一個元素值和一個數量值的操作。

除了與順序容器相同的操作外,關聯容器還支持一些順序容器不支持的操作。此外,無序容器還提供一些用來調整哈希性能的操作。

關聯容器的迭代器都是雙向的。

11.2.1 定義關聯容器

如前所示,當定義一個 map 時,必須既指明關鍵字類型又指明值類型;而定義一個 set 時,只需指明關鍵字類型,因為 set 中沒有值。每個關聯容器都定義了一個默認構造函數,它創建一個指定類型的空容器。在新標準下,我們也可以對關聯容器進行值初始化。

map<string, int> words_count; // 空容器// 列表初始化set<string> exclude = { "the","but","or","an","a","The","But","And","Or","An","A"};// 三個元素;autuors 將姓映射為名map<string, string> authors = { {"Joyce","James"},{"Austen","Jane"},{"Dickens","Charles"} };

與以往一樣,初始化器必須能轉換為容器中元素的類型。對于 set,元素類型就是關鍵字類型。

當初始化一個 map 時,必須提供關鍵字類型和值類型。我們將每個關鍵字-值對包圍在花括號中:

{key,value}

來指出它們一起構成了 map 中的一個元素。在每個花括號中,關鍵字是第一個元素,值是第二個。因此 authors 將姓映射到名,初始化后它包含三個元素。

初始化 multimap 和 multiset

一個 map 或 set 中的關鍵字必須是唯一的,即,對于一個給定的關鍵字,只能有一個元素的關鍵字等于它。容器 multimap 和 multiset 沒有此限制,它們都允許多個元素具有相同的關鍵字。例如,在我們用來統計單詞數量的 map 中,每個單詞只能有一個元素。另一方面,在一個詞典中,一個特定單詞則可具有多個與之關聯的詞義。

下面的例子展示了具有唯一關鍵字的容器與允許重復關鍵字的容器之間的區別。首先,我們將創建一個名為 ivec 的保存 int 的 vector,它包含 20 個元素:0 到 9 每個整數有兩個拷貝。我們將使用此 vector 初始化一個 set 和一個 multiset。

// 定義一個有 20 個元素的 vector,保存 0 到 9 每個整數的兩個拷貝vector<int> ivec;for (vector<int>::size_type i = 0; i != 10; ++i){ivec.push_back(i);ivec.push_back(i); //每個數重復保存一次}// iset 包含來自 ivec 的不重復的元素;miset 包含所有元素 20 個元素set<int> iset(ivec.cbegin(), ivec.cend());multiset<int> miset(ivec.cbegin(), ivec.cend());cout << ivec.size() << endl; // 打印出 20cout << iset.size() << endl; // 打印出 10cout << miset.size() << endl; // 打印出 20

11.2.1 節練習

練習 11.5:解釋 map 和 set 的區別。你如何選擇使用哪個?

當需要查找給定值所對應的數據時,應使用 map,其中保存的是 <關鍵字,值>對,按關鍵字訪問值。

如果只需判定給定值是否存在時,應使用 set,它是簡單的值的集合。

練習 11.6:解釋 set 和 list 的差別。你如何選擇使用哪個?

兩者都可以保存元素集合。

如果只需要順序訪問這些元素,或是按位置訪問元素,那么應該使用 list。如果需要快速判斷是否有元素等于給定值,則應使用 set。

// 練習 11.7:定義一個 map,關鍵字是家庭的姓,值是一個 vector,保存家中孩子(們) // 的名。編寫代碼,實現添加新的家庭以及向已有家庭中添加新的孩子。 void add_family(map<string, vector<string>> &families, const string &family) {if (families.find(family) == families.end())families[family] = vector<string>(); } void add_child(map<string, vector<string>> &families, const string &family,const string &child) {families[family].push_back(child); } void test_family() {map<string, vector<string>> families;add_family(families, "張");add_child(families, "張","強");add_child(families, "張", "剛");add_child(families, "王", "五");add_family(families, "王");for (auto f : families){cout << f.first << "家的孩子: ";for (auto c : f.second){cout << c << " ";}cout << endl;} }

// 練習 11.8:編寫一個程序,在一個 vector 而不是一個 set 中保存不重復的單詞。使用 set 的優點是什么? string &trans(string &s) {for (int p = 0; p < s.size(); p++){if (s[p] >= 'A' && s[p] <= 'Z')s[p] -= ('A' - 'a');else if (s[p] == ',' || s[p] == '.')s.erase(p, 1);} } // 使用 vector void save_words_toV() {vector<string> unique_word;string word;while (cin >> word){trans(word);if (find(unique_word.begin(), unique_word.end(), word) == unique_word.end())unique_word.push_back(word); // 添加不重復單詞}for (const auto &w : unique_word) // 打印不重復單詞{// 打印結果cout << w << " ";}cout << endl; }// 使用 set void save_words_toSet() {set<string> unique_word;string word;while (cin >> word){trans(word);unique_word.insert(word); // 添加不重復單詞}for (const auto &w : unique_word) // 打印不重復單詞{// 打印結果cout << w << " ";}cout << endl; }

使用 set 可以不用編寫檢查重復單詞的代碼。vector 可以保持單詞的輸入順序,而 set 則不能,遍歷 set,元素是按值的升序被遍歷的。

11.2.2 關鍵字類型的要求

關聯容器對其關鍵字類型有一些限制。對于有序容器——map、multimap、set 以及 multiset,關鍵字類型必須定義元素比較的方法。默認情況下,標準庫使用關鍵字類型的 < 運算符來比較兩個關鍵字。在集合類型中,關鍵字類型就是元素類型;在映射類型中,關鍵字類型是元素的第一部分的類型。

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

有序容器的關鍵字類型

可以向一個算法提供我們自己定義的比較操作(10.3節),與之類似,也可以提供自己定義的操作來代替關鍵字上的 < 運算符。所提供的操作必須在關鍵字類型上定義一個嚴格弱序(strict weak ordering)??梢詫栏袢跣蚩醋鳌靶∮诘扔凇薄K仨毦邆淙缦禄拘再|:

  • 兩個關鍵字不能同時“小于等于”對方;如果 k1 “小于等于” k2,那么 k2 絕不能“小于等于” k1。
  • 如果 k1 “小于等于” k2,且 k2 “小于等于” k3,那么 k1 必須 “小于等于” k3。
  • 如果存在兩個關鍵字,任何一個都不“小于等于” 另一個,那么我們稱這兩個關鍵字是“等價”的。如果 k1 “等價于” k2,且 k2 “等價于” k3,那么 k1 必須“等價于” k3。

如果兩個關鍵字是等價的(即,任何一個都不“小于等于”另一個),那么容器將它們視作相等來處理。當用作 map 的關鍵字時,只能有一個元素與這兩個關鍵字關聯,我們可以用兩者中任意一個來訪問對應的值。

使用關鍵字類型的比較元素

用來組織一個容器中元素的操作也是該容器類型的一部分。為了指定使用自定義的操作,必須在定義關聯容器類型時提供此操作的類型。如前所述,用尖括號指出要定義哪種類型的容器,自定義的操作類型必須在尖括號中緊跟著元素類型給出。

在尖括號中出現的每個類型,就僅僅是一個類型而已。當我們創建一個容器(對象)時,才會以構造函數參數的形式提供真正的比較操作(其類型必須與在尖括號中指定的類型相吻合)。

例如,我們不能直接定義一個 Sales_data 的 multiset,因為 Sales_data 沒有 < 運算符。但是,可以用 compareIsbn 函數來定義一個 multiset。此函數在 Sales_data 對象的 ISBN 成員上定義了一個嚴格弱序。函數 compareIsbn 應該像這樣定義

bool compareIsbn(const Sales_data &lhs,const Sales_data &rhs) {return lhs.isbn() < rhs.isbn(); }

為了使用自己定義的操作,在定義 multiset 時我們必須提供兩個類型:關鍵字類型 Sales_data,以及比較操作類型——應該是一種函數指針類型(6.7節),可以指向 compareIsbn。當定義此容器類型的對象時,需要提供相應使用的操作的指針。在本例中,我們提供一個指向 compareIsbn 的指針

// bookstore 中多條記錄可以有相同的 ISBN // bookstore 中的元素以 ISBN 的順序進行排列 multiset<Sales_data, decltype(compareIsbn)*>bookstore(compareIsbn);

此處,我們使用 decltype 來指出自定義操作的類型。記住,當用 decltype 來獲得一個函數指針類型時,必須加上一個 * 來指出我們要使用一個給定函數類型的指針。用 compareIsbn 來初始化 bookstore 對象,這表示當我們向 bookstore 添加元素時,通過調用 compareIsbn 來為這些元素排序。即,bookstore 中的元素將按它們的 ISBN 成員的值排序??梢杂?compareIsbn 代替 &compareIsbn 作為構造函數的參數,因為當我們使用一個函數的名字時,在需要的情況下它會自動轉化為一個指針。當然,使用 &compareIsbn 的效果也是一樣的。

11.2.2 節練習

// 練習 11.9:定義一個 map,將單詞與一個行號的 list 關聯,list 中保存的是單詞所出現的行號。 map<string, list<int>>word_lineno; string &trans(string &s) {for (int p = 0; p < s.size(); p++){if (s[p] >= 'A' && s[p] <= 'Z')s[p] -= ('A' - 'a');else if (s[p] == ',' || s[p] == '.')s.erase(p, 1);} } void save_word() {map<string, list<int>>word_lineno; //單詞到行號的映射string line;string word;int lineno = 0;ifstream in;if (!in){cout << "打開輸入文件失敗!" << endl;}while (getline(in, line)) //讀取一行{lineno++; //行號遞增istringstream l_in(line);//構造字符串流,讀取單詞while (l_in >> word){trans(word);word_lineno[word].push_back(lineno); //添加行號}}// 打印單詞行號for (const auto &w : word_lineno){cout << w.first << "所在行: ";for (const auto &i : w.second)cout << i << " ";cout << endl;} }

練習 11.10:可以定義一個 vector::iterator 到 int 的 map 嗎?list::iterator 到 int 的 map 呢?對于兩種情況,如果不能,解釋為什么

由于有序容器要求關鍵字類型必須支持比較操作 < ,因此

map<vector<int>::iterator, int> m1;

是可以的,因為 vector 的迭代器支持比較操作。而

map<list<int>::iterator,int> m2;

是不行的,因為 list 的元素不是連續存儲,其迭代器不支持比較操作。

練習 11.11:不使用 decltype 重新定義 bookstore。

// 首先用typedef 定義與 compareIsbn 相容的函數指針類型,然后用此類型聲明 multiset 即可。 typedef bool (*pf)(const Sales_data &, const Sales_data &) multiset<Sales_data,pf> bookstore(compareIsbn);

11.2.3 pair 類型

在介紹關聯容器的操作之前,我們需要了解名為 pair 的標準庫類型,它定義在頭文件 utility 中。

一個 pair 保存兩個數據成員。類似容器,pair 是一個用來生成特定類型的模板。當創建一個 pair 時,我們必須提供兩個類型名,pair 的數據成員將具有對應的類型。兩個類型不要求一樣:

pair<string, string> anon; // 保存兩個 string pair<string, size_t> word_count; // 保存一個 string 和一個 size_t pair<string, vector<int>> line; // 保存 string 和 vector<int>

pair 的默認構造函數對數據成員進行值初始化(參見3.3.1節)。因此,anon 是一個包含兩個空 string 的 pair,line 保存一個空 string 和一個空 vector。word_count 中的 size_t 成員值為 0,而 string 成員被初始化為空。

我們也可以為每個成員提供初始化器:

pair<string, string> author{"James","Joyce"};

這條語句創建一個名為 author 的 pair ,兩個成員被初始化 為"James"和"Joyce"。

與其他標準庫類型不同,pair 的數據成員是 public 的(7.2節)。兩個分別命名為 first 和 second。我們用普通的額乘以訪問符號(1.5.2節)來訪問它們,例如,在之前的單詞計數程序的輸出語句中我們就是這么做的:

// 打印結果 cout<< w.first << " occurs " << w.second<<((w.second > 1) ? "times" : "time") << endl;

此處,w 是指向 map 中某個元素的引用。map 的元素是 pair。下表列出了 pair 上的操作。

pair 上的操作 操作描述
pair<T1, T2> p;p 是一個 pair,兩個類型分別為 T1 和 T2 的成員都進行了值初始化(3.3.1節)
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 的(公有)數據成員
p.second返回 p 的名為 second 的(公有)數據成員
p1 relop p2關系運算符(<、>、<=、>=)按字典序定義:例如,當 p1.first < p2.first 或 !(p2.first < p1.first) && p1.second < p2.second 成立時,p1 < p2 為 true。關系運算符利用元素的 < 運算符來實現。
p1 == p2 p1 != p2當 first 和 second 成員分別相等時,兩個 pair 相等。相等性判斷利用元素的 == 運算符實現

創建 pair 對象的函數

想象有一個函數需要返回一個 pair。在新標準下,我們可以對返回值進行列表初始化(6.3.2節)

// 一個返回 pair 的函數 pair<string,int> process(vector<string> &v) {// 處理 vif (!v.empty()){return { v.back(),v.back().size() }; //列表初始化}else{return pair<string, int>(); // 隱式構造返回值} }

若 v 不為空,我們返回一個由 v 中最后一個 string 及其大小組成的 pair。否則,隱式構造一個空 pair,并返回它。

在較早的 C++ 版本中,不允許用花括號包圍的初始化器來返回 pair 這種類型的對象,必須顯式構造返回值:

if (!v.empty()){return pair<string, int>(v.back(),v.back().size()); }

我們還可以用 make_pair 來生成 pair 對象,pair 的兩個類型來自于 make_pair 的參數:

if (!v.empty()){return make_pair(v.back(),v.back().size()); }

11.2.3 節練習

// 練習 11.12:編寫程序,讀入 string 和 int 的序列,將每個 string 和 int 存入一個 pair 中,pair 保存在一個 vector 中。 void save_to_pair() {string s;int i;vector<pair<string, int>> v;while (cin >> s && cin >> i){v.push_back(pair<string, int>(s, i));;}// 打印數據for (const auto &d : v)cout << d.first << " " << d.second << endl; } // 練習 11.13:編寫上一題程序的三個版本,分別采用不同的方法創建 pair。解釋你認為哪種形式最易于編寫和理解,為什么? void save_to_pair1() {string s;int i;vector<pair<string, int>> v;while (cin >> s && cin >> i){//第一種 默認初始化v.push_back(pair<string, int>(s, i));//第二種 列表初始化v.push_back({ s,i });//第三種v.push_back(make_pair(s, i));}// 打印數據for (const auto &d : v)cout << d.first << " " << d.second << endl; } // 練習 11.14: 擴展 11.2.1 中編寫的孩子姓到名的 map,添加一個 pair 的 vector,保存孩子的名和生日。 void add_family(map<string, vector<pair<string, string>>> &families, const string &family) {families[family]; } void add_child(map<string, vector<pair<string, string>>> &families, const string &family,const string &child,const string &birthday) {families[family].push_back({ child,birthday }); } void test_family() {// vector 中保存 pairmap<string, vector<pair<string, string>>> families;add_family(families, "張");add_child(families, "張","強","2000-1-1");add_child(families, "張", "剛","1980-2-2");add_child(families, "王", "五","1990-1-1");add_family(families, "王");for (auto f : families){cout << f.first << "家的孩子: ";for (auto c : f.second){cout << c.first << " (生日" << c.second << "), ";}cout << endl;} }

11.3 關聯容器操作

關聯容器額外的類型別名 類型別名描述
key_type此容器類型的關鍵字類型
mapped_type每個關鍵字關聯的類型;只適用于 map
value_type對于 set,與 key_value 相同; 對于 map,為 pair<const key_type, mapped_type>

對于 set 類型,key_typevalue_type 是一樣的;set中保存的值就是關鍵字。在一個 map 中,元素是關鍵字-值對。即,每個元素是一個 pair 對象,包含一個關鍵字和一個關聯的值。由于我們不能改變一個元素的關鍵字,因此這些 pair 的關鍵字部分是 const 的。

set<string>::value_type v1; // v1 是一個 stringset<string>::key_type v2; // v2 是一個 stringmap<string, int>::value_type v3; // v3 是一個 pair<const string, int>map<string, int>::key_type v4; // v4 是一個 stringmap<string, int>::mapped_type v5;// v5 是一個 int

與順序容器一樣(9.2.2節),我們使用作用域運算符來提取一個類型的成員——例如,map<string, int>::key_type。

只有 map 類型(unordered_map、unordered_multimap、multimap 和 map )才定義了 mapped_type。

11.3.1 關聯容器迭代器

當解引用一個關聯容器迭代器時,我們會得到一個類型為容器的 value_type 的值的引用。對 map 而言,value_type 是一個 pair 類型,其 first 成員保存 const 的關鍵字,second 成員保存值:

map<string, int> word_count;// 獲得指向 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; // 正確:我們可以通過迭代器改變元素

必須記住,一個 map 的 value_type 是一個 pair,我們可以改變 pair 的值,但不能改變關鍵字成員的值。

set 的迭代器是 const 的

雖然 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; //正確:可以讀關鍵字}

遍歷關聯容器

map 和 set 都支持 begin 和 end 操作。與往常一樣,我們可以用這些函數獲取迭代器,然后用迭代器來遍歷容器。例如,我們可以編寫一個循環來打印單詞計數程序的結果,如下

map<string, int> word_count;// 獲得指向 word_count 中一個元素的迭代器auto map_it = word_count.cbegin();// 比較當前迭代器和尾后迭代器while (map_it != word_count.cend()){// 解引用迭代器,打印關鍵字-值對cout << map_it->first << " occurs "<< map_it->second << "times" << endl;++map_it; //遞增迭代器,移動到下一個元素}

本程序的輸出是按字典序排列的。當使用一個迭代器遍歷一個 map、multimap、set 或 multiset 時,迭代器按關鍵字升序遍歷元素。

關聯容器和算法

我們通常不對關聯容器使用泛型算法(參見第 10 章)。關鍵字是 const 這一特性意味著不能將關聯容器傳遞給修改或重排容器元素的算法,因為這類算法需要向元素寫入值,而 set 類型中的元素是 const 的,map 中的元素是 pair,其第一個成員是 const 的。

關聯容器可用于只讀取元素的算法。但是,很多這類算法都有搜索序列。由于關聯容器中的元素不能通過它們的關鍵字進行(快速)查找,因此對其使用泛型搜索算法幾乎總是個壞主意。

在實際編程中,如果我們真要對一個關聯容器使用算法,要么是將它當作一個序列,要么當作一個目的位置。例如,可以用泛型 copy 算法將元素從一個關聯容器拷貝到另一個序列。類似的,可以調用 inserter 將一個插入器綁定(10.4.1節)到一個關聯容器。通過使用 inserter,我們可以將關聯容器當作一個目的位置來調用另一個算法。

11.3.1 節練習

練習 11.15:對一個 int 的 vector 的 map,其 mapped_type、key_type 和 value_type 分別是什么?

vector; int ; pair<const int, vector>.

// 練習 11.16:使用一個 map 迭代器編寫一個表達式,將一個值賦予一個元素。 void map_it() {map<string, int> mp;auto it = mp.begin();it->second = 0; } // 練習 11.17:假定 c 是一個 string 的 multiset,v 是一個 string 的 vector, // 解釋下面的調用。指出每個調用是否合法: void test() {multiset<string> c;vector<string> v;copy(v.begin(), v.end(), inserter(c, c.end()));copy(v.begin(), v.end(), back_inserter(c));copy(c.begin(), c.end(), inserter(v, v.end()));copy(c.begin(), c.end(), back_inserter(v)); }

set 的迭代器是 const 的,因此只允許訪問 set 中的元素,而不能改變 set。與 map 一樣,set 的關鍵字也是 const 的,因此也不能通過迭代器來改變 set 中元素的值。

因此,前兩個調用試圖將 vector 中的元素復制到 set 中,是非法的。而后兩個調用將 set 中的元素復制到 vector 中,是合法的。

練習 11.19:定義一個變量,通過對 11.2.2 節中的名為 bookstore 的 multiset 調用 begin() 來初始化這個變量。寫出變量的類型,不要使用 auto 或 decltype。

typedef bool (*pf)(const Sales_data &,const Sales_data &); multiset<Sales_data, pf> bookstore(compareIsbn); ... pair<const Sales_data, pf>::iterator it = bookstore.begin();

11.3.2 添加元素

關聯容器的 insert 成員向容器中添加一個元素或一個元素范圍。由于 map 和 set (以及對應的無序類型)包含不重復的關鍵字,因此插入一個已存在的元素對容器沒有任何影響:

vector<int> ivec={2,4,6,8,2,4,6,8};set<int> set2; // 空集合set2.insert(ivec.cbegin(), ivec.cend()); // set2 有 4 個元素cout << endl;set2.insert({ 1,3,5,7,1,3,5,7 }); // set2 現在有 8 個元素

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

向 map 添加元素

向一個 map 進行 insert 操作時必須記住元素類型是 pair 。通常,對于想要插入的數據,并沒有一個現成的 pair 對象??梢栽?insert 的參數列表中創建一個 pair:

map<string, int> word_count;string word;// 向 word_count 插入 word 的 4 種方法word_count.insert({ word,1 });word_count.insert(make_pair(word, 1));word_count.insert(pair<string, int>(word, 1));word_count.insert(map<string, int>::value_type(word, 1));

如我們所見,在新標準下,創建一個 pair 最簡單的的方法是在參數列表中使用花括號初始化。也可以調用 make_pair 或顯式構造 pair。最后一個 insert 調用中的參數:

map<string, int>::value_type(s, 1) 關聯容器 insert 操作 操作描述
c.insert(v)
c.emplace(args)
v 是 value_type 類型的對象;args 用來構造一個元素
對于map 和 set ,只有當元素的關鍵字不在 c 中時才插入(或構造)元素。函數返回一個 pair,只包含一個迭代器,指向具有指定關鍵字的元素,以及一個指示是否成功的 bool 值。
對于 multimap 和 multiset ,總會插入(或構造)給定元素,并返回一個指向新元素的迭代器
c.insert(b, e)
c.insert(il)
b 和 e 是迭代器,表示一個 c::value_type 類型值的范圍;il 是這種值的花括號列表。函數返回 void
對于 map 和 set,只會插入關鍵字不在 c 中的元素。對于 multimap 和 multiset ,則會插入范圍中的每個元素
c.insert(p, v)
c.emplace(p, args)
類似 insert(v)(或 emplace(args)),但將迭代器 p 作為一個提示,指出從哪里開始搜索新元素應該存儲的位置。返回一個迭代器,指向具有給定關鍵字的元素

檢測 insert 的返回值

insert(或 emplace)返回的值依賴于容器類型和參數。對于不包含重復關鍵字的容器,添加單一元素的 insert 和 emplace 版本返回一個 pair,告訴我們插入操作是否成功。pair 的 first 成員是一個迭代器,指向具有給定關鍵字的元素;second 成員是一個 bool 值,指出元素是插入成功還是以及存在于容器中。如果關鍵字已在容器中,則 insert 什么事情也不做,且返回值中的 bool 部分為 false。如果關鍵字不存在,則元素被插入容器中,且 bool 值為 true。

作為一個例子,我們用 insert 重寫單詞計數程序:

// 單詞計數程序 void word_count() {// 統計每個單詞在輸入中出現次數的一種更煩瑣的方法map<string, int>word_count;string word;while (cin >> word){// 插入一個元素,關鍵字等于 word,值為 1;// 若 word 已在 word_count 中,insert 什么也不做auto ret = word_count.insert({ word,1 });if (!ret.second) // word 已在 word_count 中{++ret.first->second; // 遞增計數器}} }

對于每個 word,我們嘗試將其插入到容器中,對應的值為 1。若 word 已在 map 中,則什么都不做,特別是與 word 相關聯的計數器的值不變。若 word 還未在 map 中,則此 string 對象被添加到 map 中,且計數器的值被置為 1。

if 語句檢查返回值的 bool 部分,若為 false,則表明插入操作未發生。在此情況下,word 已存在于 word_count 中,因此必須遞增此元素所關聯的計數器。

展開遞增語句

在這個版本的單詞計數程序中,遞增計數器的語句很難理解。通過添加一些括號來反映出運算符的優先級,使表達式更容易理解:

++((ret.first)->second); // 等價表達式

下面我們一步一步來解釋此表達式:

ret 保存 insert 返回的值,是一個 pair

ret.first 是 pair 的第一個成員,是一個 map 迭代器,指向具有給定關鍵字的元素

ret.first-> 解引用此迭代器,提取 map 中的元素,元素也是一個 pair

ret.first->second map 中元素的值部分

++ret.first->second 遞增此值

向 multiset 或 multimap 添加元素

我們的單詞計數程序依賴于這樣一個事實:一個給定的關鍵字只能出現一次。這樣任意給定的單詞只有一個關聯的計數器。我們有時希望能添加具有相同關鍵字的多個元素。例如,可能想建立作者到他所著書籍題目的映射。在此情況下,每個作者可能有多個條目,因此我們應該用 multimap 而不是 map。由于一個 multi 容器中的關鍵字不必唯一,在這些類型上調用 insert 總會插入一個元素:

multimap<string, string> authors;// 插入第一個元素,關鍵字為 Barth,Jhonauthors.insert({ "Barth,Jhon","Sot-Weed Factor" });// 正確:添加第二個元素,關鍵字也是 Barth,Jhonauthors.insert({ "Barth,Jhon","Lost in the Funhouse" });for (auto value : authors){cout << value.first << " " << value.second << endl;} }

對允許重復關鍵字的容器,接受單個元素的 insert 操作返回一個指向新元素的迭代器。這里無須返回一個 bool 值,因為 insert 總是向這類容器中加入一個新元素。

11.3.2 節練習

練習 11.20:重寫的單詞計數程序,使用 insert 代替下標操作。你認為哪個程序更容易編寫和閱讀?解釋原因。

// 統計每個單詞在輸入中出現的次數 void count_word() {map<string, int> word_count; // string 到 int 的空 mapstring word;while (cin>>word){++word_count[word]; // 提取 word 的計數器并將其加一}for (const auto &w : word_count)// 對 map 中的每個元素{// 打印結果cout << w.first << " occurs " << w.second<< ((w.second > 1) ? " times" : " time") << endl;} }

使用下標操作更簡潔易讀

練習 11.21:假定 word_count 是一個 string 到 int 的 map ,word 是一個 string,解釋下面循環的作用:

while (cin >> word) {++word_count.insert({word,0}).first->seond; }

循環不斷從標準輸入讀入單詞(字符串),直至遇到文件結束或錯誤。

每讀入一個單詞,構造 pair {word,0},通過 insert 操作插入到 word_count 中。insert 返回一個 pair ,其 first 成員是一個迭代器。若單詞(關鍵字)已存在于容器中,它指向已有元素;否則,它指向新插入的元素。

因此,.first 會得到這個迭代器,指向 word 對應的元素。繼續使用 ->second,可獲得元素的值的引用,即單詞的計數。若單詞是新的,則其值為 0,若已存在,則值是之前出現的次數。對其進行遞增操作,即完成出現次數加 1。

練習 11.22:給定一個 map<string, vector>,對此容器的插入一個元素的 insert 版本,寫出其參數類型和返回類型。

參數類型是一個 pair, first 成員的類型是 map 的關鍵字類型 string,second 成員的類型是 map 的值的類型 vector:

pair<string,vector>

返回類型也是一個 pair,first 成員的類型是 map 的迭代器,second 成員的類型是布爾型:

pair<map<string, vector>::iterator, bool>

// 練習 11.23:11.2.1 節練習中的 map 以孩子的姓為關鍵字,保存他們的名的 vector,用 multimap 重寫此 map。 void add_child(multimap<string, string> &families, const string &family, const string &child) {families.insert({ family,child });} void test_family() {multimap<string, string> families;add_child(families, "張", "強");add_child(families, "張", "剛");add_child(families, "王", "五");for (auto f : families){cout << f.first << "家的孩子: " << f.second << endl;} } i

11.3.3 刪除元素

關聯容器定義了三個版本的 erase。如下表所示。與順序容器一樣,我們可以通過傳遞給 erase 一個迭代器或一對迭代器來刪除一個元素或一個元素范圍。這兩個版本的 erase 與對應的順序容器的操作非常相似:指定的元素被刪除,函數返回 void。

關聯容器提供一個額外的 erase 操作,它接受一個 key_type 參數。此版本刪除所有匹配給定關鍵字的元素(如果存在的話),返回實際刪除的元素的數量。我們可以用此版本在打印結果之前從 word_count 中刪除一個特定的單詞:

// 刪除一個關鍵字,返回刪除的元素數量if (word_count.erase(remove_word))cout << "OK: " << remove_word << " removed\n";else cout << "oops: " << remove_word << " not found!\n";

對于保存不重復關鍵字的容器,erase 的返回值總是 0 或 1.若返回值為 0,則表明想要刪除的元素并不在容器中。

對允許重復關鍵字的容器,刪除元素的數量可能大于 1:

auto cnt = authors.erase("Barth,Jhon");

如果 authors 是 11.3.2 節中創建的 multimap,則 cnt 的值為2。

從關聯容器刪除元素 操作描述
c.erase(k)從 c 中刪除每個關鍵字為 k 的元素。返回一個 size_type 的值,指出刪除的元素的數量
c.erase§從 c 中刪除迭代器 p 指定的元素。p 必須指向 c 中一個真實元素,不能等于 c.end()。返回一個指向 p 之后元素的迭代器,若 p 指向 c 中的尾元素,則返回 c.end()
c.erase(b,e)刪除迭代器 b 和 e 所表示的范圍中的元素。返回 e

11.3.4 map 的下標操作

map 和 unordered_map 容器提供了下標運算符和一個對應的 at 函數,如下表所示。set 類型不支持下標,因為 set 中沒有與關鍵字相關聯的“值”。元素本身就是關鍵字,因此“獲取與一個關鍵字相關聯的值”的操作就沒有意義了。我們不能對一個 multimap 或一個 unordered_multimap 進行下標操作,因為這些容器中可能有多個值與一個關鍵字相關聯。

類似我們用過的其他下標運算符,map 下標運算符接受一個索引(即,一個關鍵字),獲取與此關鍵字相關聯的值。但是,與其他下標運算符不同的是,如果關鍵字并不在 map 中,會為它創建一個元素并插入到 map 中,關聯值將進行值初始化(3.3.1 節)。

例如,如果我們編寫如下代碼

map<string, int> word_count; // 插入一個關鍵字為 Anna 的元素,關聯值進行值初始化;然后將 1 賦予它 word_count["Anna"] = 1;

將會執行如下操作:

  • 在 word_count 中搜索關鍵字為 Anna 的元素,未找到。
  • 將一個新的關鍵字-值對插入到 word_count 中。關鍵字是一個 const_string,保存 Anna。值進行值初始化,在本例中意味著值為 0.
  • 提取出新插入的元素,并將值 1 賦予它。

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

對一個 map 使用下標操作很不相同:使用一個不在容器中的關鍵字作為下標,會添加一個具有此關鍵字的元素到 map 中。

map 和 unordered_map 的下標操作 操作描述
c[k]返回關鍵字為 k 的元素;如果 k 不在 c 中,添加一個關鍵字為 k 的元素,對其進行值初始化
c.at(k)訪問關鍵字為 k 的元素,帶參數檢查;若 k 不在 c 中,拋出一個 out_of_range 異常

使用下標操作的返回值

map 的下標運算符與我們用過的其他下標運算符的另一個不同之處是其返回類型。通常情況下,解引用一個迭代器所返回的類型與下標運算符返回的類型是一樣的。但對 map 則不然:當對一個 map 進行下標操作時,會獲得一個 mapped_type 對象;但當解引用一個 map 迭代器時,會得到一個 value_type 對象(11.3節)。

與其他下標運算符相同的是,map 的下標運算符返回一個左值(4.1.1節)。由于返回的是一個左值,所以我們既可以讀也可以寫元素:

cout<<word_count["Anna"];// 用 Anna 作為下標提取元素;會打印出 1 ++word_count["Anna"]; // 提取元素,將其增1 cout<<word_count["Anna"];//提取元素并打印它;會打印出 2

11.3.4 節練習

練習 11.24:下面的程序完成什么功能?

map<int, int> m; m[0]=1;

若 m 中已有關鍵字 0,下標操作提取出其值,賦值語句將值置為 1。

否則,下標操作會創建一個 pair (0,0),即關鍵字為 0,值為 0(值初始化),將其插入到 m 中,然后提取其值,賦值語句將值置為 1。

練習 11.25:對比下面程序與上一題程序:

vector<int> v; v[0]=1;

對于 m,"0"表示“關鍵字 0”。而對于 v,“0” 表示“位置 0”。

若 v 中已有不少于一個元素,即存在“位置 0”元素,則下標操作提取出此位置的元素(左值),賦值操作將其置為 1。而 map 的元素是 pair 類型,下標提取的不是元素,而是元素的第二個成員,即元素的值。

如 v 尚為空,則下標提取出的是一個非法左值(下標操作不做范圍檢查),向其賦值可能導致系統崩潰等嚴重后果。

練習 11.26:可以用什么類型來對一個 map 進行下標操作?下標操作運算符返回的類型是什么?請給出一個具體例子——即,定義一個 map ,然后寫出一個可以用來對 map 進行下標操作的類型以及下標運算符將會返回的類型。

對 map 進行下標操作,應使用其 key_type ,即關鍵字類型。

而下標操作返回的類型是 mapped_type,即關鍵字關聯的值的類型。

比如:map<string, int> 用來進行下標操作的類型是 string,下標操作返回的類型是 int。

11.3.5 訪問元素

關聯容器提供多種查找一個指定元素的方法,如下表所示。如果我們所關心的只不過是一個特定元素是否已在容器中,可能 find 是最佳選擇。對于不允許重復關鍵字的容器,可能使用 find 還是 count 沒什么區別。但是對于運行重復關鍵字的容器,count 還會做更多工作:如果元素在容器中,它還會統計有多少個元素有相同的關鍵字。如果不需要計數,最好使用 find:

set<int> iset = { 0,1,2,3,4,5,6,7,8,9 };iset.find(1); // 返回一個迭代器,指向 key == 1 的元素iset.find(11); // 返回一個迭代器,其值等于 iset.end()iset.count(1); // 返回 1iset.count(0); // 返回 0 在一個關聯容器中查找元素的操作 操作描述
lower_bound 和 upper_bound 不適用于無序容器
下標 和 at 操作只適用于非 const 的 map 和 unordered_map
c.find(k)返回一個迭代器,指向第一個關鍵字為 k 的元素
c.count(k)返回關鍵字等于 k 的元素的數量。對于不允許重復關鍵字的容器,返回值永遠是 0 或 1
c.lower_bound(k)返回一個迭代器,指向第一個關鍵字不小于 k 的元素
c.upper_bound(k)返回一個迭代器,指向第一個關鍵字大于 k 的元素
c.equal_range(k)返回一個迭代器 pair,表示關鍵字等于 k 的元素的范圍。若 k 不存在,pair 的兩個成員均等于 c.end()

對 map 使用 find 代替下標操作

對 map 和 unordered_map 類型,下標運算符提供了最簡單的提取元素的方法。但是,使用下標操作有一個嚴重的副作用:如果關鍵字還未在 map 中,下標操作會插入一個具有給定關鍵字的元素。

但有時,我們只是想知道一個給定元素是否在 map 中,而不想改變 map。在這種情況下,應該使用 find:

if(word_count.find("floor")==word_count.end())cout<<"floor is not in the map"<<endl;

在 multimap 或 multiset 中查找元素

對于允許重復關鍵字的容器來說,查找一個元素的過程更為復雜:在容器中可能有很多元素具有給定的關鍵字。如果一個 multimap 或 multiset 中有多個元素具有給定關鍵字,則這些元素在容器中會相鄰存儲。

例如,給定一個作者到著作題目的映射,我們可能想點一個特定作者的所有著作。可以用三種不同的方法來解決這個問題。最直觀的方法是使用 find 和 count:

multimap<string, string> authors;// 插入第一個元素,關鍵字為 Barth,Jhonauthors.insert({ "Barth,Jhon","Sot-Weed Factor" });// 正確:添加第二個元素,關鍵字也是 Barth,Jhonauthors.insert({ "Barth,Jhon","Lost in the Funhouse" }); // 打印一個特定作者的所有著作string search_item("Alain de Botton"); // 要查找的作者auto entries = authors.count(search_item); // 元素的數量auto iter = authors.find(search_item); // 此作者的第一本書// 用一個循環查找此作者的所有著作while (entries){cout << iter->second << endl; // 打印每個題目++iter; // 前進到下一本書--entries; // 記錄已經打印了多少本書}

首先調用 count 確定此作者共有多少本著作,并調用 find 獲得一個迭代器,指向第一個關鍵字為此作者的元素。while 循環的迭代次數依賴于 count 的返回值。特別是,如果 count 返回 0,則循環一次也不執行。

一種不同的,面向迭代器的解決方法

我們還可以用 lower_bound 和 upper_bound 來解決此問題。這兩個操作都接受一個關鍵字,返回一個迭代器。如果關鍵字在容器中,lower_bound 返回的迭代器將指向第一個具有給定關鍵字的元素,而 upper_bound 返回的迭代器則指向最后一個匹配給定關鍵字的元素之后的位置。如果元素不在 multimap 中,則 lower_bound 和 upper_bound 會返回相等的迭代器——指向一個不影響排序的關鍵字插入位置。因此,用相同的關鍵字調用 lower_bound 和 upper_bound 會得到一個迭代器范圍,表示所有具有該關鍵字的元素的范圍。

當然,這兩個操作返回的迭代器可能是容器的尾后迭代器。如果我們查找的元素具有容器中最大的關鍵字,則此關鍵字的 upper_bound 返回尾后迭代器。如果關鍵字不存在,且大于容器中任何關鍵字,則 lower_bound 返回的也是尾后迭代器。

使用這兩個操作,我們可以重寫前面的程序:

// 使用 lower_bound 和 upper_bound 重寫前面的程序// 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 和 upper_bound 返回相同的迭代器,則給定關鍵字不在容器中。

equal_range 函數

解決此問題的最后一種方法是三種方法中最直接的:直接調用 equal_range 即可。此函數接受一個關鍵字,返回一個迭代器 pair。若關鍵字存在,則第一個迭代器指向第一個與關鍵字匹配的元素,第二個迭代器指向最后一個匹配元素之后的位置。若未找到匹配元素,則兩個迭代器都指向關鍵字可以插入的位置。

可以用 equal_range 來再次修改我們的程序:

// pos 保存迭代器對,表示與關鍵字匹配的元素范圍for (auto pos = authors.equal_range(search_item); pos.first != pos.second; ++pos.first)cout << pos.first->second << endl; // 打印每個題目

11.3.5 節練習

// 練習 11.28:對一個 string 到 int 的 vector 的 map,定義并初始化一個變量來保存在其上并調用 find 所返回的結果 map<string, vector<int>> m; // 保存 find 返回結果的變量 map<string, vector<int>>::iterator iter;

練習 11.29:如果給定的關鍵字不在容器中,upper_bound、lower_bound 和 equal_range 分別會返回什么?

lower_bound 返回第一個具有給定關鍵字的元素,upper_bound 則返回最后一個具有給定關鍵字的元素之后的位置。即,這兩個迭代器構成包含所有具有給定關鍵字的元素的范圍。若給定關鍵字不在容器中,兩個操作顯然應構成一個空范圍,它們返回相當的迭代器,指出關鍵字的正確插入位置——不影響關鍵字的排序。如果給定關鍵字比容器中所有關鍵字都大,則此位置是容器的尾后位置 end 。

equal_range 返回一個 pair,其 first 成員等價于 lower_bound 返回的迭代器,second 成員等價于 upper_bound 返回的迭代器。因此,若給定關鍵字不在容器中,first 和 second 都指向關鍵字的正確插入位置,兩個迭代器構成一個空范圍。

練習 11.30:對于本節最后一個程序中的輸出表達式,解釋運算對象 pos.first->second 的含義。

equal_range 返回一個 pair,其 first 成員與 lower_bound 的返回結果相同,即指向容器中第一個具有給定關鍵字的元素。因此,對其解引用會得到一個 value_type 對象,即一個 pair ,其 first 為元素的關鍵字,即給定關鍵字,而 second 為關鍵字關聯的值。在本例中,關鍵字為作者,關聯的值為著作的題目。因此 pos.first->second 即獲得給定作者的第一部著作的題目。

// 練習 11.31:編寫程序,定義一個作者及其作品的 multimap。使用 find 在 multimap 中查找一個元素并 // erase 刪除它。確保你的程序在元素不在 map 中時也能正常運行。 void remove_author(multimap<string, string> &books, const string &author) {auto pos = books.equal_range(author);if (pos.first == pos.second){cout << "沒有 " << author << " 這個作者" << endl;}else{books.erase(pos.first, pos.second);} } // 練習 11.32:使用上一題定義的 multimap 編寫一個程序,按字典序打印作者列表和他們的作品 void print_books(multimap<string, string> &books) {cout << "當前書目包括: " << endl;for (auto &book : books) // 遍歷所有書籍,打印之cout << book.first << ", 《" << book.second << "》" << endl;cout << endl; }

11.3.6 一個單詞轉換的 map

我們將以一個程序結束本節的內容,它將展示 map 的創建、搜索以及遍歷。這個程序的功能是這樣的:給定一個 string,將它轉換為另一個 string。程序的輸入是兩個文件。第一個文件保存的是一些規則,用來轉換第二個文件中的文本。每條規則由兩部分組成:一個可能出現在輸入文件中的單詞和一個用來替換它的短語。表達的含義是,每當第一個單詞出現在輸入中時,我們就將它替換成對應的短語。第二個輸入文件包含要轉換的文本。

如果單詞轉換文件的內容如下所示:

brb be right back k okay? y why r are u you pic picture thk thanks! 18r later

我們希望轉換的文本為

where r u y dont u send me a pic k thk 18r

則程序應該生成這樣的輸出:

where are you why dont you send me a picture okay? thanks! later

單詞轉換程序

我們的程序將使用三個函數。函數 word_transform 管理整個過程。它接受兩個 ifstream 參數:第一個參數綁定到單詞轉換文件,第二個參數應綁定到我們要轉換的文本文件。函數 buildMap會讀取轉換規則文件,并創建一個 map,用于保存每個單詞到其轉換內容的映射。函數transform接受一個 string,如果存在轉換規則,返回轉換后的內容。

我們首先定義 word_transform 函數。最重要的部分是調用 buildMap 和transform:

void word_transform(ifstream &map_file, ifstream &input) {auto trans_map = bulidMap(map_file); // 保存轉換規則string text; // 保存輸入中的每一行while (getline(input, text)) // 讀取一行輸入{istringstream stream(text); // 讀取每個單詞string word;bool firstword = true; // 控制是否打印空格while (stream >> word){if (firstword)firstword = false;elsecout << " "; // 在單詞間打印出一個空格// transform 返回它的第一個參數或其轉換之后的形式cout << transform(word, trans_map); //打印輸出}cout << endl; // 完成一行的轉換} }

函數首先調用 buildMap 來生成單詞轉換 map,我們將它保存在 trans_map 中。函數的剩余部分處理輸入文件。while 循環用 getline 一行一行地讀取文件。這樣做的目的是使得輸出中的換行位置能和輸入文件中一樣。為了從每行中獲取單詞,我們使用了一個嵌套的 while 循環,他用一個 istringstream (8.3節) 來處理當前行中的每個單詞。

在輸出過程中,內層 while 循環使用一個 bool 變量 firstword 來確定是否打印一個空格。它通過調用 transform 來獲得要打印的單詞。transform 的返回值或者是 word 中原來的 string,或者是 trans_map 中指出的對應的轉換內容。

建立轉換映射

函數 buildMap 讀入給定文件,建立起轉換映射。

map<string,string> bulidMap(ifstream &map_file) {map<string, string> trans_map; // 保存轉換規則string key; // 要轉換的單詞string value; // 替換后的內容// 讀取第一個單詞存入 key 中,行中剩余內容存入 valuewhile (map_file >> key && getline(map_file, value)){if (value.size() > 1) // 檢查是否有轉換規則{trans_map[key] = value.substr(1);// 跳過前導空格}else{throw runtime_error("no rule for " + key);}}return trans_map; }

map_file 中的每一行對應一條規則。每條規則由一個單詞和一個短語組成,短語可能包含多個單詞。我們用 >> 讀取要轉換的單詞,存入 key 中,并用 getline 讀取這一行中的剩余內容存入 value 。由于 getline 不會跳過前導空格(3.2.2節),需要我們來跳過單詞和它的轉換內容之間的空格。在保存轉換規則之前,檢查是否獲得了一個以上的字符。如果是,調用 substr(9.5.1節)來跳過分隔單詞及其轉換短語之間的前導空格,并將得到的子字符存入 trans_map 。

注意,我們使用下標運算符來添加關鍵字-值對。我們隱含地忽略了一個單詞在轉換文件中出現多次的情況。如果真的有單詞出現多次,循環會將最后一個對應短語存入 trans_map 。當 while 循環結束后,trans_map 中將保存著用來轉換輸入文本的規則。

生成轉換文本

函數 transform 進行實際的轉換工作。其參數是需要轉換的 string 的引用和轉換規則 map。如果給定 string 在 map 中,transform 返回相應的短語。否則,transform 直接返回 string:

const string& transform(const string &s, const map<string, string> &m) {// 實際的轉換工作;此部分是程序的核心auto map_it = m.find(s);// 如果單詞在轉換規則 map 中if (map_it != m.cend()){return map_it->second; // 使用替換短語}else{return s; // 否則返回原 string} }

函數調用 find 來確定給定 string 是否在 map 中。如果存在,則 find 返回一個指向對應元素的迭代器。否則,find 返回尾后迭代器。如果元素存在,我們解引用迭代器,獲得一個保存關鍵字和值的 pair (11.3節),然后返回成員 second,即用來替代 s 的內容。

11.3.6 節練習

練習 11.33:實現你自己版本的單詞轉換程序。

map<string,string> bulidMap(ifstream &map_file) {map<string, string> trans_map; // 保存轉換規則string key; // 要轉換的單詞string value; // 替換后的內容// 讀取第一個單詞存入 key 中,行中剩余內容存入 valuewhile (map_file >> key && getline(map_file, value)){if (value.size() > 1) // 檢查是否有轉換規則{trans_map[key] = value.substr(1);// 跳過前導空格}else{throw runtime_error("no rule for " + key);}}return trans_map; } const string& transform(const string &s, const map<string, string> &m) {// 實際的轉換工作;此部分是程序的核心auto map_it = m.find(s);// 如果單詞在轉換規則 map 中if (map_it != m.cend()){return map_it->second; // 使用替換短語}else{return s; // 否則返回原 string} } void word_transform(ifstream &map_file, ifstream &input) {auto trans_map = bulidMap(map_file); // 保存轉換規則string text; // 保存輸入中的每一行while (getline(input, text)) // 讀取一行輸入{istringstream stream(text); // 讀取每個單詞string word;bool firstword = true; // 控制是否打印空格while (stream >> word){if (firstword)firstword = false;elsecout << " "; // 在單詞間打印出一個空格// transform 返回它的第一個參數或其轉換之后的形式cout << transform(word, trans_map); //打印輸出}cout << endl; // 完成一行的轉換} }

練習 11.34:如果你將 transform 函數中的 find 替換為下標運算符,會發生什么情況?

find 僅會查找給定關鍵字在容器中是否出現,若容器中不存在給定關鍵字,它返回尾后迭代器。當關鍵字存在時,下標運算符的行為與 find 類似,但當關鍵字不存在時,它會構造一個pair (進行值初始化),將其插入到容器中。對于單詞轉換程序,這會將不存在的內容插入到輸出文本中,這顯然不是我們所期望的。

練習 11.35:在 buildMap 中,如果進行如下改寫,會有什么效果?

trans_map[key] = value.substr(1); 改為 trans_map({key, value.substr(1)})

當 map 中沒有給定關鍵字時,insert 操作與下標操作+賦值操作的效果類似,都是將關鍵字和值的 pair 添加到 map 中。

但當 map 中已有給定關鍵字,也就是新的轉換規則與上一條已有規則要轉換同一個單詞時,兩者的行為是不同的。下標操作會獲得具有該關鍵字的元素(也就是已有規則)的值,并將新讀入的值賦予它,也就是用心讀入的規則覆蓋了容器中的已有規則。但 insert 操作遇到關鍵字已存在的情況,則不會改變容器內容,而是返回一個值指出插入失敗。因此,當規則文件中存在多條規則轉換相同單詞時,下標+賦值的版本最終會用最后一條規則進行文本轉換,而 insert 版本則會用第一條規則進行文本轉換。

練習 11.36:我們的程序并沒有檢查輸入文件的合法性。特別是,它假設轉換規則文件中的規則都是有意義的。如果文件中的某一行包含一個關鍵字、一個空格,然后就結束了,會發生什么?預測程序的行為并進行驗證,再與你的程序進行比較。

11.4 無序容器

tring word;
bool firstword = true; // 控制是否打印空格
while (stream >> word)
{
if (firstword)
firstword = false;
else
cout << " "; // 在單詞間打印出一個空格
// transform 返回它的第一個參數或其轉換之后的形式
cout << transform(word, trans_map); //打印輸出
}
cout << endl; // 完成一行的轉換
}
}

練習 11.34:如果你將 transform 函數中的 find 替換為下標運算符,會發生什么情況?<span style="background:#eef0f4;">find 僅會查找給定關鍵字在容器中是否出現,若容器中不存在給定關鍵字,它返回尾后迭代器。當關鍵字存在時,下標運算符的行為與 find 類似,但當關鍵字不存在時,它會構造一個pair (進行值初始化),將其插入到容器中。對于單詞轉換程序,這會將不存在的內容插入到輸出文本中,這顯然不是我們所期望的。</span>練習 11.35:在 buildMap 中,如果進行如下改寫,會有什么效果?

trans_map[key] = value.substr(1);
改為 trans_map({key, value.substr(1)})

<span style="background:#eef0f4;">當 map 中沒有給定關鍵字時,insert 操作與下標操作+賦值操作的效果類似,都是將關鍵字和值的 pair 添加到 map 中。</span><span style="background:#eef0f4;">但當 map 中已有給定關鍵字,也就是新的轉換規則與上一條已有規則要轉換同一個單詞時,兩者的行為是不同的。</span><span style="background:#d4e9d5;">下標操作會獲得具有該關鍵字的元素(也就是已有規則)的值,并將新讀入的值賦予它,也就是用心讀入的規則覆蓋了容器中的已有規則。</span><span style="background:#dad5e9;">但 insert 操作遇到關鍵字已存在的情況,則不會改變容器內容,而是返回一個值指出插入失敗。</span><span style="background:#eef0f4;">因此,當規則文件中存在多條規則轉換相同單詞時,下標+賦值的版本最終會用最后一條規則進行文本轉換,而 insert 版本則會用第一條規則進行文本轉換。</span>練習 11.36:我們的程序并沒有檢查輸入文件的合法性。特別是,它假設轉換規則文件中的規則都是有意義的。如果文件中的某一行包含一個關鍵字、一個空格,然后就結束了,會發生什么?預測程序的行為并進行驗證,再與你的程序進行比較。[外鏈圖片轉存中...(img-2s7XEuJ5-1642757185254)]## 11.4 無序容器

總結

以上是生活随笔為你收集整理的第11 章 关联容器的全部內容,希望文章能夠幫你解決所遇到的問題。

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

久草网首页 | 国产精品mv在线观看 | 一区二区日韩av | 亚洲美女免费精品视频在线观看 | 成人h视频 | 亚洲综合激情网 | 色噜噜日韩精品一区二区三区视频 | 成人精品电影 | 狠狠躁日日躁狂躁夜夜躁 | 久久av网址 | 亚洲区精品视频 | 国产自制av | 中文在线www| 最近中文字幕在线中文高清版 | 久久综合毛片 | 五月花激情 | 日韩精品一区二区三区免费观看视频 | 久久久久中文 | 中文字幕第一页在线vr | 欧美成人精品在线 | 91精品一区二区在线观看 | 在线免费高清一区二区三区 | 一级性生活片 | 探花视频免费在线观看 | 免费三级在线 | 你操综合| 视频一区视频二区在线观看 | 久久久亚洲精品 | 国产精品片 | 久久久久久看片 | 国内小视频 | 国产三级久久久 | 成人一级视频在线观看 | 中文字幕精品www乱入免费视频 | 日韩欧美国产免费播放 | 国产一级高清 | 在线国产中文字幕 | 伊人黄| 国产在线视频资源 | 四虎最新入口 | 久久久久国产a免费观看rela | 国产91勾搭技师精品 | 久草在线免费播放 | 国产黄色一级大片 | 青春草视频在线播放 | 一级免费片 | 欧美嫩草影院 | 亚洲国产精品成人av | www,黄视频 | 欧美三级高清 | 久久精品一区二区三区中文字幕 | 精品国内自产拍在线观看视频 | 亚洲精品一区二区精华 | 久久天天躁夜夜躁狠狠躁2022 | 又黄又爽又刺激的视频 | 欧美日韩视频免费看 | 欧美午夜视频在线 | 中文字幕精品一区二区三区电影 | 日韩在线观看视频网站 | 狠狠干天天 | 日韩在线观看一区二区 | 国产97视频在线 | 又黄又爽又刺激视频 | 黄色一级在线观看 | 国产精品视频一二三 | 亚洲最新在线视频 | 亚洲三级精品 | 96视频免费在线观看 | 国产精品免费久久久久影院仙踪林 | 国产日韩精品一区二区三区 | 色的网站在线观看 | 在线成人国产 | 婷婷av网站 | 国产视频亚洲精品 | 天天做天天爱夜夜爽 | 午夜精品视频在线 | 激情深爱 | 国产一区视频在线 | 成年人视频在线 | 日韩av成人| 日韩精品视频在线观看免费 | 国产亚洲午夜高清国产拍精品 | 三级黄色理论片 | 国产99免费 | a级免费观看 | 中文字幕人成不卡一区 | 色综合久久久久久久久五月 | 黄在线免费看 | 久久免费视频这里只有精品 | 狠狠干狠狠色 | 精品国产理论 | 日日天天干 | 日韩欧美视频一区二区 | av丝袜天堂| 开心色激情网 | 久久免费看毛片 | 高清一区二区三区 | 中文字幕 国产 一区 | 在线观看一区 | 国产精品美女视频网站 | 一区二区在线电影 | 欧美性生交大片免网 | 一区二区在线影院 | 日韩一三区 | 欧美影片 | 丁香婷婷激情国产高清秒播 | 久久人人添人人爽添人人88v | 亚洲h在线播放在线观看h | 福利视频一区二区 | 亚洲一区精品人人爽人人躁 | 国产高清视频在线观看 | 欧美精品一二三 | 在线电影中文字幕 | 中文字幕av全部资源www中文字幕在线观看 | 成人毛片在线观看视频 | 日本公妇色中文字幕 | 成人精品电影 | 五月婷婷免费 | 亚洲三级在线 | 欧美日韩中文字幕综合视频 | 精品国产亚洲一区二区麻豆 | 91欧美视频网站 | 成人动态视频 | 黄色a三级 | 激情婷婷色 | 韩国精品一区二区三区六区色诱 | www.天天色 | 一区免费观看 | 亚洲精品综合一二三区在线观看 | 五月婷婷香蕉 | 天无日天天操天天干 | 九九久 | 九色精品免费永久在线 | 超碰官网 | 国产中文字幕视频在线观看 | 亚洲精品中文字幕视频 | 日韩视频在线一区 | 免费看麻豆| 日韩精品一区二区三区第95 | 亚洲毛片一区二区三区 | 黄色网在线免费观看 | 一 级 黄 色 片免费看的 | 99国产精品一区二区 | 黄色亚洲精品 | 国产亚洲婷婷免费 | 91精品国产一区二区在线观看 | 一本一道久久a久久综合蜜桃 | 亚洲综合国产精品 | 国产精品完整版 | 亚洲精选视频免费看 | 97在线免费观看视频 | 久久亚洲私人国产精品va | 91亚洲精品久久久中文字幕 | 天天插日日射 | 一区二区在线电影 | 亚洲欧洲成人精品av97 | 国产精品久久久电影 | 色婷婷九月| 亚洲 欧美 精品 | 亚洲国产成人久久综合 | 色午夜 | 欧美一级大片在线观看 | 日韩在线视频网站 | 97超碰人人爱 | 日本精品视频在线观看 | 日本一区二区三区免费看 | 中文字幕一区二区三区视频 | 久久一级电影 | 久久中国精品 | 亚洲一区动漫 | 天天看天天干 | 欧美一级性生活片 | 在线视频1卡二卡三卡 | 成 人 黄 色 视频 免费观看 | 成 人 a v天堂 | 精品免费久久久久 | 国产小视频在线播放 | 亚洲精品欧洲精品 | 国产视频丨精品|在线观看 国产精品久久久久久久久久久久午夜 | 4438全国亚洲精品观看视频 | 国产麻豆视频免费观看 | 日韩av中文字幕在线免费观看 | 日韩精品免费在线 | 日韩黄色一级电影 | 日韩在线免费电影 | 日韩在线无| 日日摸日日添日日躁av | 久草网站| 欧美综合久久久 | 久久av观看 | 国产一二区视频 | 九九热免费视频在线观看 | 大胆欧美gogo免费视频一二区 | 国产精品毛片一区视频播 | 精品一区精品二区高清 | 99r精品视频在线观看 | 欧美日韩在线第一页 | 96国产在线| 天天艹| 国产在线视频在线观看 | 中文字幕欧美日韩va免费视频 | 黄色成人影院 | 性色视频在线 | 国产一级黄色片免费看 | 91大神一区二区三区 | 成年人免费看片 | 国产精品av久久久久久无 | 精品在线一区二区三区 | 欧美日韩一区二区在线观看 | 久草视频免费观 | 成片视频在线观看 | www.夜夜爽 | 日韩精品一区二区免费视频 | 日韩在线第一区 | 天天操天天操天天操天天操天天操天天操 | 麻豆久久一区二区 | 精品免费在线视频 | 久久国产日韩 | 99久久超碰中文字幕伊人 | 久久综合婷婷综合 | 久久精品看片 | 黄色亚洲片 | 亚洲成人精品 | 成片免费观看视频大全 | 日韩免费福利 | 国产v在线| 国内精品视频一区二区三区八戒 | 国产99区| 久久任你操 | 午夜精品电影 | 草久电影 | 亚洲一区 影院 | 808电影| 日日夜夜免费精品视频 | 欧美韩国日本在线 | 国产精品成人自产拍在线观看 | 天天插日日射 | 国产精品 中文在线 | 中文十次啦| 天天操夜夜看 | 精品一区二三区 | 久久av网 | 伊人电影在线观看 | 国产流白浆高潮在线观看 | 天天躁日日躁狠狠躁 | 午夜精品麻豆 | 日韩最新在线视频 | 天天天插 | 黄色大片免费播放 | 国产做a爱一级久久 | 日韩天天干 | 亚洲情影院| 国产精品精 | 久久成人资源 | 最新久久免费视频 | 日韩黄在线观看 | 久久久精品 | 开心激情网五月天 | 国产精品 日韩 | 少妇自拍av | 西西444www大胆高清视频 | 色婷婷亚洲精品 | 欧美一区日韩一区 | 久久人人爽av | 一级黄色片在线观看 | 91片黄在线观 | 欧美日韩高清国产 | 日韩在线看片 | 久久艹中文字幕 | 69视频在线播放 | 国模视频一区二区 | 超碰人人做 | 国产a级免费 | 一区二区三区在线视频观看58 | 很黄很色很污的网站 | www狠狠操 | 天天综合久久综合 | 亚洲h色精品| 91精品国自产在线偷拍蜜桃 | 日日夜夜中文字幕 | 亚洲精品乱码白浆高清久久久久久 | 国产精品手机在线 | 91丨九色丨首页 | 天天爽综合网 | 亚洲精品黄色片 | 久久久久久久久精 | 一本一本久久a久久精品综合 | 亚洲女人天堂成人av在线 | 99免费在线播放99久久免费 | 午夜三级毛片 | av 一区 二区 久久 | 色婷婷狠狠五月综合天色拍 | 三级动图 | 久久久亚洲网站 | 天天天天天天天操 | 91丨九色丨国产在线 | 99在线视频免费观看 | 免费在线观看日韩 | 成人av一区二区兰花在线播放 | 日本丶国产丶欧美色综合 | 又湿又紧又大又爽a视频国产 | 精品国产乱码久久久久久浪潮 | 亚洲激精日韩激精欧美精品 | 伊人看片| 亚洲精品久久久久久久不卡四虎 | 亚洲国产三级在线 | 91人人插| 色婷五月天 | 国产免费片 | 午夜久久网 | 久久免费精品一区二区三区 | 中文字幕精品一区二区三区电影 | 色婷婷综合成人av | 久草在线视频在线 | 8x成人在线 | 天天插天天狠天天透 | 亚洲成a人片在线观看中文 中文字幕在线视频第一页 狠狠色丁香婷婷综合 | 超碰97人人爱 | 亚洲一区二区视频在线 | 天天爱天天爽 | 99视频在线免费看 | 亚洲乱码精品久久久久 | 91成品视频| 日韩xxxxxxxxx| 色多多污污在线观看 | 天堂av在线网 | 亚洲美女在线国产 | 又色又爽又激情的59视频 | 丁香视频 | 亚洲国产精品视频在线观看 | 国产精品视频免费 | 亚洲污视频| 国产福利一区二区在线 | 狠狠色丁香婷婷综合视频 | 久久调教视频 | 91久久影院| 亚洲国产97在线精品一区 | 亚洲理论在线 | 91麻豆精品国产 | 国产成人精品999 | av先锋影音少妇 | 精品久久久久久久久久久院品网 | 一区二区三区手机在线观看 | 成人在线视频论坛 | 91大神dom调教在线观看 | av高清一区二区三区 | 国产美女被啪进深处喷白浆视频 | 视频在线国产 | 日韩精品中文字幕av | 狠狠狠色丁香婷婷综合久久88 | 天天射天天干 | 丁香六月激情婷婷 | 十八岁免进欧美 | 波多野结衣视频一区二区三区 | 337p西西人体大胆瓣开下部 | 日本精品久久 | 国产综合小视频 | 久久中文字幕导航 | 日本中文在线播放 | 中文字幕一区二区三区乱码不卡 | 免费观看性生交大片3 | 国产精品专区在线 | 久久久久久久久毛片精品 | 免费视频a | 日韩va在线观看 | 欧美ⅹxxxxxx | 99色在线播放 | 精品国产一区二区三区久久久蜜臀 | 日韩在线 一区二区 | 国内丰满少妇猛烈精品播 | 久久精品电影院 | 在线观看小视频 | 精品国产一区二区在线 | 免费在线观看视频一区 | 在线观看你懂的网站 | 在线亚洲精品 | 亚洲精品国偷拍自产在线观看蜜桃 | 日韩av影视在线 | 免费高清av在线看 | 91av在线不卡 | 婷婷丁香六月 | 中文字幕一区二区三区精华液 | 中文字幕有码在线 | 韩国av永久免费 | 美女免费黄网站 | 亚洲视频专区在线 | 天堂资源在线观看视频 | 精品久久久久久亚洲综合网站 | 国产精品久久久久久久久久久久 | 黄色电影网站在线观看 | 精品久久久久国产 | 在线婷婷 | 欧美极品在线播放 | 色婷婷在线播放 | 久久免费毛片 | 麻豆传媒视频在线 | 天天操伊人 | 免费h漫在线观看 | www色综合 | 欧美日韩高清在线一区 | 精品国产免费看 | 亚洲精品白浆高清久久久久久 | 国产精品永久免费在线 | 国产精彩视频一区 | 日批网站免费观看 | 日本狠狠色 | 在线高清 | 麻豆成人小视频 | 国产尤物一区二区三区 | 在线观看免费中文字幕 | 人人干人人干人人干 | 国产高清免费 | 亚洲人在线 | 毛片网站在线 | 色综合久久悠悠 | 久艹视频在线免费观看 | 久久久精品福利视频 | 成人在线免费观看网站 | 毛片网站在线看 | 亚洲国产三级在线观看 | 中文免费 | 精品在线99| 国产精品com | 久久好看免费视频 | 亚洲女人天堂成人av在线 | 欧美日韩亚洲精品在线 | 欧美久久久久久久久久久久 | 国产精品视频免费看 | 91av手机在线观看 | 深爱激情五月婷婷 | 欧美日韩aaaa| 精品国产成人在线 | 欧美电影在线观看 | 在线国产高清 | 九九九九热精品免费视频点播观看 | 亚洲一区二区精品3399 | 一区二区不卡在线观看 | 最新国产中文字幕 | 手机看片| 91av在线免费观看 | 麻豆视频免费看 | 一级特黄aaa大片在线观看 | 最近免费在线观看 | 人人插人人艹 | 婷婷五月情| 亚洲欧洲日韩在线观看 | 国产精品刺激对白麻豆99 | 久久成人国产精品一区二区 | 66av99精品福利视频在线 | 一区二区三区在线视频观看58 | 97日日| 久久高清免费视频 | 成人av影视观看 | 久久综合99 | 日韩视频在线观看视频 | 亚洲六月丁香色婷婷综合久久 | 久久婷婷五月综合色丁香 | 国产免费观看高清完整版 | 欧美另类69 | 午夜视频在线观看一区二区三区 | 在线三级中文 | 国产中文字幕网 | 久久人人做 | 毛片一区二区 | 国产精品免费一区二区 | 欧美一级艳片视频免费观看 | 日日夜操 | 久久综合亚洲鲁鲁五月久久 | 字幕网资源站中文字幕 | 色综合天天狠狠 | 99视频这里有精品 | 欧美成人xxxx| 国产精品s色 | 亚洲伦理一区 | 亚洲视频免费 | 免费av在线网| 人人看人人做人人澡 | 在线观看精品黄av片免费 | 欧美日韩视频在线播放 | 欧美a级在线免费观看 | 国产一级视频在线免费观看 | 超碰在线人人 | 久久亚洲私人国产精品va | 国产精品12| 色在线视频网 | 91九色蝌蚪国产 | 天天色天天色天天色 | 91免费在线看片 | 色 中文字幕 | 亚洲在线高清 | 精品99在线 | 免费开视频| 婷婷丁香色 | 久久毛片高清国产 | 在线免费观看国产黄色 | 久久九九久久精品 | 精品国产三级a∨在线欧美 免费一级片在线观看 | 久久免费资源 | 天天舔天天射天天操 | 开心激情婷婷 | 欧美日韩不卡一区二区 | 蜜臀av夜夜澡人人爽人人 | 久久久精品久久日韩一区综合 | 国产五月色婷婷六月丁香视频 | 国产精品久久久一区二区三区网站 | 日韩久久精品一区二区 | 久久精品一区二区 | 天天色天天射天天综合网 | av一区在线| 最新超碰| 天天搞夜夜骑 | 欧美 另类 交 | 日韩性色 | 亚洲精品视频在线免费播放 | 亚洲视频电影在线 | 久久免费成人精品视频 | 日韩欧美在线综合网 | 午夜视频在线观看一区二区 | 久久亚洲欧美 | 91麻豆精品国产自产在线 | 波多野结衣最新 | 中文字幕亚洲综合久久五月天色无吗'' | 黄色特一级 | 国产一级做a爱片久久毛片a | 国产视频首页 | 91精品在线观看视频 | 手机看国产毛片 | 午夜精品久久久久久久久久 | 四虎免费av | 欧美一级免费片 | 亚洲国产精品女人久久久 | 久久婷婷丁香 | 色偷偷网站视频 | 天天综合婷婷 | 国产午夜精品在线 | 全黄色一级片 | 在线观看你懂的网站 | 伊人成人激情 | v片在线看 | 99久久久久成人国产免费 | 992tv人人网tv亚洲精品 | 久草在线视频资源 | 91亚洲精品久久久久图片蜜桃 | 欧美另类z0zx | 成人在线视频在线观看 | 亚洲人成人在线 | 日本午夜在线亚洲.国产 | av成年人电影 | www.狠狠干 | 日日爽天天爽 | 欧洲高潮三级做爰 | 91视频免费播放 | 天堂av色婷婷一区二区三区 | 六月激情丁香 | 99精品视频在线观看播放 | 国产精品久久久久aaaa九色 | 亚洲 欧美 另类人妖 | 97成人资源站 | av免费线看| 日韩免费在线观看网站 | 精品国产一二三 | 激情欧美日韩一区二区 | 久久99精品久久久久久清纯直播 | 香蕉手机在线 | 在线观看www视频 | 久久成人视屏 | 五月激情在线 | 国产一区二区精品 | 玖玖爱国产在线 | 久草免费资源 | 精品一区免费 | 中文字幕综合在线 | 国产精品99久久久久久久久 | 又黄又爽的视频在线观看网站 | 久久国产精品影视 | 就要干b | 日本三级人妇 | 黄色小说在线观看视频 | 亚洲黄色网络 | 日产中文字幕 | 激情网在线观看 | 韩国精品福利一区二区三区 | 日韩在线观看电影 | 国产成人久久精品一区二区三区 | 国产亚洲激情视频在线 | 这里只有精彩视频 | 欧美亚洲免费在线一区 | 精品一区二区三区在线播放 | 久久99中文字幕 | 99久久精品久久久久久清纯 | 五月天电影免费在线观看一区 | 在线观看一区二区精品 | 中文字幕视频一区 | 色香蕉在线 | a在线v | 99热99re6国产在线播放 | 亚洲精品乱码久久久久久蜜桃动漫 | 国产精品成人一区二区三区吃奶 | 日韩中文字幕在线看 | www.香蕉视频| 亚洲国产中文字幕在线观看 | 日本亚洲国产 | 国产精品美女久久久久aⅴ 干干夜夜 | 国产精品久久久久久久午夜片 | 日韩精品三区四区 | 五月婷婷影院 | 婷婷丁香色综合狠狠色 | 午夜视频欧美 | 在线观影网站 | 国产精品久久久久久久久久ktv | 永久免费av在线播放 | 亚洲一级电影视频 | 亚洲精品影视在线观看 | 国产又粗又猛又黄又爽的视频 | 色av资源网 | 亚洲男男gⅴgay双龙 | 免费av的网站| 黄色h在线观看 | 在线观看深夜福利 | 日韩大片在线观看 | 午夜精品久久久久久久爽 | 色先锋av资源中文字幕 | 草在线| 一区二区精品在线观看 | 玖玖视频网 | 亚洲综合情| 中文字幕在线观看网站 | 国产专区在线 | 中文字幕资源在线 | 中文字幕在线视频第一页 | 黄色一级片视频 | 午夜久久精品 | 久久女同性恋中文字幕 | www毛片com| 亚洲少妇激情 | 日日夜夜操操操操 | 亚洲一区二区黄色 | 午夜电影久久久 | 日本三级在线观看中文字 | 午夜在线免费观看 | 亚洲激情六月 | 久久精品二区 | 色伊人网| 日韩欧美中文 | 欧美久久电影 | 色视频在线看 | 久久久久久97三级 | 五月激情视频 | 久艹视频在线观看 | 成人18视频| 久久国产高清视频 | 久久艹艹 | 97成人在线免费视频 | 日韩深夜在线观看 | 国产中文字幕久久 | 少妇性aaaaaaaaa视频 | 国产精品毛片一区二区在线 | 国产成人精品一区二区三区福利 | 丁香婷婷综合激情 | 日韩欧美一区二区在线观看 | 999视频在线播放 | 视频在线播放国产 | 91综合在线| 亚洲激情在线播放 | 久草影视在线 | 狠狠色狠狠综合久久 | 国产福利小视频在线 | 国产精品日韩欧美 | 亚洲国产日韩在线 | av高清影院| 又黄又刺激视频 | 亚洲精品在 | 天天综合在线观看 | 激情五月婷婷综合 | 免费a v网站| 国产精品第二十页 | 欧美a影视| 婷婷在线视频观看 | 一区二区高清在线 | 亚洲精品综合一二三区在线观看 | 毛片永久新网址首页 | 欧美精品亚州精品 | 美州a亚洲一视本频v色道 | 精品电影一区二区 | 亚洲视频久久久久 | av再线观看 | 国产亚洲精品精品精品 | 久久香蕉影视 | 国产色拍拍拍拍在线精品 | 久久精品美女视频 | 久久理论影院 | 久久久久久毛片精品免费不卡 | 亚洲国产网站 | 国产亚州av | 狠狠色丁香婷婷 | 亚洲精品一区二区三区新线路 | 视频成人免费 | 成人在线电影观看 | 又黄又刺激视频 | 91av视频免费在线观看 | av片中文字幕 | 国产aaa大片 | 人人爽影院 | 久久久久久国产精品999 | 久久久 激情 | 狠狠操天天干 | 最近中文国产在线视频 | 婷婷在线观看视频 | 欧美性极品xxxx做受 | 波多野结衣在线观看一区二区三区 | 97电影院网| 欧美精品黑人性xxxx | 91超国产| 国产精品国产三级国产aⅴ无密码 | 成人毛片久久 | 日韩电影中文 | 中文字幕一区二区三区乱码在线 | 日韩福利在线观看 | 91视频88av| 免费观看性生活大片3 | 2019中文最近的2019中文在线 | 精品福利在线观看 | 日韩欧美69 | 久久免费看 | 国产精品久久久久久久久久妇女 | 日韩高清 一区 | 色网站黄 | 又紧又大又爽精品一区二区 | 国产一区在线播放 | 99热精品久久 | 超级碰99| 欧美日韩视频网站 | 欧美天天射 | 亚洲欧美在线视频免费 | 深夜成人av | 精品99久久久久久 | 亚洲电影久久久 | 亚洲资源 | 国产精品一区二区免费看 | 日韩在线无 | 国产日韩精品一区二区三区在线 | 九九久久成人 | 日韩欧美一区二区在线观看 | 亚洲狠狠婷婷综合久久久 | 999久久国产精品免费观看网站 | 视频在线一区二区三区 | 亚洲性视频 | 黄色.com| 精品久久久久久电影 | 亚洲国产精品电影在线观看 | 国产精品高清免费在线观看 | 超碰在线97观看 | 天天干天天操天天爱 | 色诱亚洲精品久久久久久 | 亚洲免费在线视频 | 国产精品嫩草影视久久久 | 欧美精品免费一区二区 | 东方av在线免费观看 | 国产亚洲精品电影 | 91免费网址 | 天天综合人人 | 久久免费看毛片 | 69av国产 | www五月天 | 中文字幕亚洲欧美 | 成人中心免费视频 | 久久成人一区 | 97夜夜澡人人双人人人喊 | 91高清视频免费 | 看黄色.com | 久久久久北条麻妃免费看 | 亚洲一级二级三级 | 97热视频| 91精品在线播放 | 国产亚洲视频在线免费观看 | 久久免费视屏 | 奇米网网址 | 欧美一级淫片videoshd | 国产理论片在线观看 | 国产一区高清在线观看 | 亚洲成aⅴ人在线观看 | 亚洲日本va在线观看 | 91视频免费国产 | 青青河边草免费直播 | 亚洲乱亚洲乱亚洲 | 国产精品3| 欧美国产日韩激情 | 深爱五月激情网 | 五月天激情综合 | 精品免费国产一区二区三区四区 | 97在线精品视频 | 日本久久精品视频 | 成人试看120秒 | 99精品视频一区二区 | 在线观看视频你懂 | 免费看片网页 | 日韩 在线 | 激情偷乱人伦小说视频在线观看 | 99精品视频精品精品视频 | 黄色com | 天天干婷婷 | 欧美在线1区 | 亚洲日本一区二区在线 | 日韩色在线观看 | 亚洲精品综合久久 | 国产精品9999久久久久仙踪林 | 欧美日韩中文在线 | avav片| 亚洲闷骚少妇在线观看网站 | 久久xxxx | 久久精品久久99精品久久 | av综合在线观看 | 亚洲国产精品传媒在线观看 | 五月天中文字幕 | 一级黄色在线免费观看 | 国产在线高清视频 | 91精品国产综合久久福利 | 亚洲视频播放 | 日本xxxx裸体xxxx17 | 久久一二三四 | 黄网站a | 欧美激情视频在线免费观看 | 国产精品免费久久久久久久久久中文 | 91网址在线观看 | 免费看的黄网站软件 | 中文字幕视频 | 日韩高清在线一区二区三区 | 国产精品系列在线观看 | 东方av免费在线观看 | 国产精品国产三级国产aⅴ9色 | 在线播放视频一区 | 久久人人爽视频 | 亚洲国产大片 | 97超碰成人在线 | 丁香六月国产 | 久久亚洲精品国产亚洲老地址 | 国产资源av | 亚洲精品中文字幕视频 | 色播五月激情五月 | 丝袜美腿在线 | 成年人在线视频观看 | 日本中文字幕在线免费观看 | 欧美一区二区日韩一区二区 | 国产精品欧美久久久久无广告 | 久久影院一区 | 成人午夜免费福利 | 美女网站在线看 | 欧美一级免费在线 | 久久午夜免费观看 | 精品在线免费观看 | 国产一区成人 | 国内精品视频久久 | 91污视频在线观看 | 天天射天天操天天干 | 国产一区二区在线精品 | 国产精品高清在线观看 | 天天激情综合 | 国产精品va| 黄色三级视频片 | 国产护士hd高朝护士1 | 亚洲欧美色婷婷 | 日韩中文字幕免费电影 | 欧美日韩亚洲在线观看 | 亚洲欧美国产精品久久久久 | 欧美精品在线视频 | 亚洲精品在线观看的 | 狠狠做深爱婷婷综合一区 | 国产精品久久久久久久av电影 | 久亚洲| 在线观看第一页 | 日韩精品国产一区 | 国产在线2020 | 国产一区二区影院 | 免费福利在线观看 | 亚洲热久久| 国产久草在线观看 | 97超碰人人网| 亚洲精品国偷自产在线99热 | 亚洲精品乱码久久久久v最新版 | 2020天天干天天操 | 国产人成看黄久久久久久久久 | 午夜精品一二三区 | 免费国产在线精品 | 国产精品亚洲综合久久 | 欧美日韩中文字幕在线视频 | 91色蜜桃| 97国产一区| 成人国产电影在线观看 | 日韩一区二区三免费高清在线观看 | 韩国精品在线 | 国产一性一爱一乱一交 | 欧美资源| 久久久婷 | 一级片色播影院 | 日日操狠狠干 | 国产成人高清在线 | 亚洲激情六月 | 成人av免费在线播放 | 看全黄大色黄大片 | 日本久久91 | 在线观看黄网站 | 91九色成人| 久久久久久99精品 | 国产精品福利无圣光在线一区 | 免费三级影片 | 国产呻吟在线 | 91重口视频 | 国产精品免费av | 天天爽人人爽夜夜爽 | 一二三区高清 | 日韩一区二区在线免费观看 | www.婷婷色| 开心综合网 | 久久久久久国产一区二区三区 | 欧美日韩中文在线视频 | 视频国产在线观看18 | 亚洲欧美日韩国产一区二区 | 国产黄色片在线 | 操操色| 婷婷久操| 国产精品高清免费在线观看 | 国产精品欧美久久久久久 | 激情婷婷综合 | 国产成人精品999 | 国产在线精品福利 | 91网免费观看 | 五月天婷亚洲天综合网精品偷 | 婷婷久久一区 | 激情五月***国产精品 | 天天摸夜夜添 | 国产成人精品亚洲日本在线观看 | 国产99久久精品一区二区300 | av免费网页 | 黄色一级动作片 | 特级毛片在线免费观看 | 中文字幕视频网 | 一区二区不卡高清 | 国产免费嫩草影院 | 免费开视频 | 免费美女久久99 | 国产精品久久99精品毛片三a | 日韩在线首页 | 亚洲视频六区 | 在线成人性视频 | 色婷婷福利 | 国产精品久久久久久久婷婷 | 91系列在线 | 亚洲码国产日韩欧美高潮在线播放 | 国产中文字幕在线免费观看 | www.com在线观看 | 最新中文字幕在线播放 | 国产不卡av在线播放 | 探花在线观看 | 91久久丝袜国产露脸动漫 | 97视频在线观看免费 | 狠狠色丁香婷婷综合久久片 | av大全在线播放 | 成年人视频免费在线 | 欧美成人在线网站 | 99久久夜色精品国产亚洲 | 国产成人一区三区 | 国产精品第52页 | 亚洲一区二区三区miaa149 | 久久精品国产一区二区 | 欧美大片www| 成人a视频片观看免费 | 欧美日本日韩aⅴ在线视频 插插插色综合 | 久久国产电影 | 韩日电影在线 | 国产精品久久麻豆 | 久久亚洲福利视频 | 国产字幕在线播放 | www日韩在线观看 | 波多野结衣日韩 | 色中色综合 | 久久精品播放 | 91爱爱电影 | 97人人爽人人 | 婷婷网站天天婷婷网站 | 91免费版在线观看 | 欧美在线观看视频一区二区三区 | 亚洲一区二区三区毛片 | 久久理伦片 | 国产精品入口66mio女同 | 天天躁日日躁狠狠躁av麻豆 | 亚洲女人av | 91精品国产91久久久久福利 | 少妇啪啪av入口 | 最近日韩中文字幕中文 | 久久久久久美女 | 亚洲精品免费看 | 麻豆视频在线免费观看 | 国产一区二区精品久久 | 在线观看蜜桃视频 | 日韩欧美精品在线视频 | 在线播放视频一区 | 国产成人精品日本亚洲999 | 国产亚洲精品久久久久久移动网络 |