今天是第十一篇筆記,主要內容是關聯容器。關聯容器的涉及到的內容相比順序容器是很少的,所以篇幅也是比較短小的。但是一些細節方面的問題還是比較晦澀的,經過一番掙扎後,我還是決定先放棄比較困難的部分。好讀書不求甚解從某些層面來說也許是好事,有些東西真的需要一定基礎才能理解透徹。在本節中,書上對無序容器的介紹很少,尤其是哈希相關的內容,基本就是一筆帶過。
有錯誤 請指正 謝謝
1.引言:
- 關聯容器和順序容器有着根本差異:
關聯容器中的元素按照關鍵字來保存和訪問,順序容器中的元素是按照它們在容器中的位置來順序保存和訪問的。關聯容器支持高效的關鍵字查找和訪問。
- 關聯容器分爲兩類:
map 和 set。map也叫關聯數組和映射類型,set稱爲集合類型。可以參考其中的數學意思。
map是關鍵字-值,成對存在,但是set中關鍵字就是值,是以單個元素的形式存在。
- 新版標準庫提供了8個關聯容器:
主要的異同有三點:
1.是map或是set. 2.是否允許重複關鍵字(multi 前綴)。3.是否有序(unordered前綴)。
在無需容器中,元素是依靠哈希函數進行組織的。
頭文件主要區分在無序和有序,同關鍵字是否重複無影響。有序容器的頭文件是map&set.無序是對應的unordered_map&unordered_set.
2.使用關聯容器:
- 1.map:關鍵字-值,存在一種映射關係。 set :關鍵字即是值,也叫集合類型。
- 2.凡是順序容器中位置相關的操作,在關聯容器中都是不支持的,意義不大。因爲關聯容器中同位置無關。
- 3.關聯容器中的迭代器都是雙向的。
- 4.定義關聯容器:
map:要同時指明關鍵字類型和值類型,即同時在尖括號中提供兩種類型信息。
set:只需要指明關鍵字類型。
都存在默認構造函數,即允許創建空容器。
初始化有兩種:拷貝或者範圍初始化.範圍一般是指花括號列表或者迭代器範圍。拷貝是指想匹配的容器之間的互相拷貝初始化。
multimap/multiset:主要區別在於此兩種是允許關鍵字重複的。帶multi前綴的是允許重複關鍵字的。
- 2.1關鍵字類型的要求:
有序關聯容器中對其關鍵字類型有要求:必須是定義了元素的比較方法,不然如何區分有序。默認是使用<進行比較,但是可以自定義操作進行替代。
使用自定義關鍵字類型的比較函數用來組織容器中的元素,這個時候比較操作也成爲類型的一部分,這個時候需要在方括號中提供自定義操作。
<span style="font-size:18px;"><span style="font-size:18px;">mutimap<Sales_data,decltype(Compare)*> book(Compare); </span></span>
<span style="font-size:18px;"><span style="font-size:18px;">這個地方使用的是函數指針類型,是可調用類型之一。在實例化的時候需要加上相應的函數名,此時的函數名自動轉換爲指針類型。</span></span>
- 2.2 pair類型:
pair的英文解釋是一對。是一種標準庫類型,定義在頭文件utility中,pair中的數據成員都是public的。
示例:
<span style="font-size:18px;"><span style="font-size:18px;">pair<string,string> spair{"Max","Cy"};</span></span>
其中兩個成員分別被命名爲first和second成員。我們可以通過.運算符進行訪問:<span style="font-size:18px;"><span style="font-size:18px;">cout<<spair.first<<endl;</span></span>
make_pair()函數:用給定的數據構建一個pair,其中類型可以由值類型提供。<span style="font-size:18px;"><span style="font-size:18px;">auto pa=make_pair("Max","Cy");//可以推斷出類型。</span></span>
3.關聯容器的操作:
<span style="font-size:18px;"><span style="font-size:18px;">key_type; //關鍵字類型。
value_type; //值類型。
mapped_type;//map特有類型。同key_type構成了一對。</span></span>
- 3.1關聯容器的迭代器:
解引用其迭代器會得到一個值類型,即上面提到的value_type.
key_type類型是const的,所以set的迭代器是const的。所以對其進行寫的操作是無意義的。
遍歷關聯容器:支持begin和end成員,輸出是按字典序排列的,因爲一開始的時候就用了<進行比較。
- 3.2添加元素:
關聯容器的insert成員可以添加一個元素或者一個範圍。當添加重複關鍵字的時候,對不允許關鍵字重複的容器來說,是無影響的。
當接受一個範圍(迭代器範圍或者初始值列表)時,不允許關鍵字重複時,只會插入第一個帶此關鍵字的元素。
當map中使用insert插入元素時,會返回一個pair類型,可以用來檢測是否插入成功。first成員是一個迭代器,指向插入的元素。second成員是bool用來表示是否插入成功。
- 3.3刪除成員:
<span style="font-size:18px;"><span style="font-size:18px;">c.erase(k);刪除關鍵字爲k的元素,返回刪除元素的個數。
c.erase(p);刪除迭代器p指向的元素,返回指向下一個元素的迭代器。
c.erase(b,e); 刪除迭代器b,e之間的元素,返回迭代器e.注意區間是左閉合。</span></span>
- 3.4 map的下標操作:
只有map和unordered_map提供了下標操作。至於允許關鍵字重複的容器,使用關鍵字下標會有衝突。
map的下標操作有兩種:類似數組的和at函數。
map的下標接受一個關鍵字,返回一個值類型,若此關鍵字不存在則會插入此不存在的關鍵字,並且對值執行值認初始化。
因爲下標可能插入元素,故只能對非const的map執行此操作。
但是使用at函數的時候不會出現此狀況,當關鍵字不存在的時候會拋出一個異常。
- 3.5訪問元素:
提供了多種訪問元素的方法:
c.find(k);
c.count(k);
c.lower_bound(k);
c.upper_bound(k);
c.equal_range(k);
具體的含義解釋可以參考書籍或者谷歌上的資料。
4.無序容器:
不再使用<來組織元素,而是通過使用一個哈希函數和一個關鍵字類型上的==運算符。
管理桶:無序容器在存儲上組織爲一個桶,每個桶保存0個或者多個元素。無序容器使用一個哈希函數將元素映射到桶。爲了訪問一個元素,首先計算元素的哈希值,它指出應該搜索哪個桶。容器將具有一個特定哈希值的元素都存儲到一個桶中。
無序容器對關鍵字類型的要求:
默認情況下使用==運算符比較元素,同時也會使用一個hash<key_type>類型對象來生成每個元素的哈希值。
5.總結:
定義了8個容器類型,區分點在三個維度上:1.是map or set.2.是否有multi.3.是否有unordered.
有序容器使用比較函數來比較關鍵字,從而元素是有序的。
無序容器使用hash<key_type> 和一個==運算符來組織元素。
無論是有序還是無序的,相同關鍵字的元素的都是相鄰存儲的。
6.習題解答:
11.1
<span style="font-size:18px;"><span style="font-size:18px;">不同:最顯著的區別就是map之中存在一種映射關係,而是vector卻沒有。</span></span>
11.2<span style="font-size:18px;"><span style="font-size:18px;">1.最適合用list:需要在任何位置插入元素。
2.最適合用vector:只需要在鏈表尾部進行操作。
3.最適合用deque:需要在頭尾操作。
4.最適合用map:兩種類型之間存在映射關係。
5.最適合用set:用於排除某種情況,需要對關鍵字進行高效查找。</span></span>
11.3#include <iostream>
#include <map>
#include <string>
using namespace std;
int main(){
map<string, size_t> word_count;
string word;
while (cin >> word)
++word_count[word];
for (const auto& x : word_count)
cout << x.first << " " << x.second << " " << endl;
system("pause");
return 0;
}</span></span>
11.4<span style="font-size:18px;"><span style="font-size:18px;">#include <iostream>
#include <map>
#include <string>
#include <cctype>
#include <set>
using namespace std;
int main(){
map<string, size_t> word_count;
set<string> exclude = { ",", "." };
string word;
while (cin >> word){
word[0]=tolower(word[0]);
if (exclude.find(word) == exclude.end()){
if (ispunct(word[word.size() - 1]))
word = word.erase(word.size() - 1, 1);
++word_count[word];
}
}
for (const auto& x : word_count)
cout << x.first << " " << x.second << " " << endl;
system("pause");
return 0;
}<span style="white-space:pre"> </span>//具體的思路是是同一化,不管大寫小寫全部轉換成一個格式。然後檢測末尾是否是標點符號,是的則刪去。</span></span>
11.5<span style="font-size:18px;"><span style="font-size:18px;">map是映射類型,而set是集合類型。
選擇的時候依據自己的數據之間時候存在默認關係。</span></span>
11.6<span style="font-size:18px;"><span style="font-size:18px;">set是一種集合類型,list是雙向鏈表,數據之間是串起來的。選擇依據是是否要進行插入刪除等操作。</span></span>
11.7
<span style="font-size:18px;">#include <iostream>
#include <map>
#include <string>
#include <algorithm>
#include <vector>
using namespace std;
int main(){
map<string,vector<string>> famls;
std::string lastName, chldName;
cout << "Ente lastnames :";
while (cin >> lastName&&lastName != "@q"){//這個地方要提供自己定義的結束標誌,不要用ctrl+z.
cout << "PLZ Enter children's name:\n";
while (cin >> chldName && chldName != "@q")
famls[lastName].push_back(chldName);
cout << "Ente lastname :";
}
for (auto e : famls){
cout << e.first << ":\n";
for (auto c : e.second)
cout << c << " ";
cout << "\n";
}
return 0;
}</span>
11.8
<span style="font-size:18px;">set自身帶有不重複的特點,即使重複的單詞被添加進去也不會影響到set.
#include <iostream>
#include <vector>
#include<string>
using namespace std;
int main(){
vector<string> svec = { "aa", "bb", "cc" };
string word;
auto f = [&](){cout << "Enter strings " << endl;
cin >> word;
return word != "@q"; };
while (f()){
for (auto x : svec){
if (x == word)
cout << "excluded!" << endl;
else
svec.push_back(word);
}
}
system("pause");
return 0;
}
</span>
11.9
<span style="font-size:18px;">map<string, list<int>> map_var;
map<string,list<std::size_t>> map_var;//也許用size_t更好一點。
</span>
11.10
<span style="font-size:18px;">取決於迭代器是否定義了<操作。vector的迭代器定了此操作,但是list沒有定義。
</span>
11.11
<span style="font-size:18px;">使用函數指針即可。
bool(*compreIsbn)(const Sales_data& lhs, const Sales_data& rhs) = compreIsbn;
</span>
11.12
<span style="font-size:18px;">#include <string>
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <utility>
using namespace std;
int main(){
vector<pair<string, int>> pvec;
string word;
int number;
while (cin >> word >> number){
auto temp=make_pair(word, number);
pvec.push_back(temp);
}
for (const auto&x : pvec){
cout << x.first << " " << x.second << endl;
}
system("pause");
return 0;
}</span>
11.13
<span style="font-size:18px;">#include <string>
#include <iostream>
#include <vector>
#include <list>
#include <map>
#include <utility>
using namespace std;
int main(){
vector<pair<string, int>> pvec;
string word;
int number;
while (cin >> word >> number){
//auto temp=make_pair(word, number);
pair<string, int> temp{ word, number };
//pair<string, int> temp = (word, number);
pvec.push_back(temp);
}
for (const auto&x : pvec){
cout << x.first << " " << x.second << endl;
}
system("pause");
return 0;
}</span>
11.14
<span style="font-size:18px;">#include <vector>
#include <iostream>
#include <string>
#include <utility>
#include <map>
using namespace std;
int main(){
map < string, vector<pair<string, string>>> family;
string last_name, name, birthday;
int number;
cout << "Enter last name ." << endl;
while (cin >> last_name){
cout << "Enter names and birthday of children ." << endl;
while (cin >> name >> birthday){
family[last_name].push_back(make_pair(name, birthday));
cout << "Continue or quit? 1 or 0 ." << "Enter your choice :";
cin >> number;
if (number == 0)
break;
else
cout << "Continue to Enter information of children's name and birthday ." << endl;
}
cout << "Continue or quit ? 1 or 0 ." << "Enter your choice :";
cin >> number;
if (number == 0)
break;
else
cout << "Continue to Enter information of children's last name ." << endl;
}
for (const auto& x : family){
cout << x.first << endl;
for (const auto&y : x.second)
cout << y.first << " " << y.second << endl;
cout << endl;
}
system("pause");
return 0;
}</span>
11.15
<span style="font-size:18px;">key_type: int;
mapped_type: vector<int>;
value_type: pair<int,vector<int>>;</span>
11.16
<span style="font-size:18px;">#include <map>
#include <iostream>
using namespace std;
int main(){
map<int, int> int_map = { 1, 1 };
auto iter = int_map.begin();
(*iter).second = 10;
system("pause");
return 0;
}</span>
11.17
<span style="font-size:18px;">第二個不合法。因爲其未定義push_back操作。</span>
11.18
<span style="font-size:18px;">const_iterator.</span>
11.19
<span style="font-size:18px;">using compare=bool (*)(const Sales_data& lhs,const Sales_data& rhs);
multiset<Sales_data,compare>::iterator iter=bookstore.begin();</span>
11.20
<span style="font-size:18px;">#include <iostream>
#include <vector>
#include<string>
#include <map>
using namespace std;
int main(){
map<string, size_t> smap;
string word;
auto func = [&]()->bool{
cout << "Enter words or Enter quit to stop " << endl;
cin >> word;
return word != "quit";
};
while (func()){
auto f = make_pair(word, 1);
auto result = smap.insert(f);
if (result.second == false)
++((*result.first).second);
}
for (const auto& x : smap)
cout << x.first << " " << x.second << endl;
system("pause");
return 0;
}
很明顯下標版本更好接受一點。
</span>
11.21
<span style="font-size:18px;">同上面函數功能等價,統計單詞個數,只是寫的比較簡便。</span>
11.22
<span style="font-size:18px;">argument:pair<string,vector<int>>;
return type: pair<map<string,vector<int>>::iterator,boo>;</span>
11.23
<span style="font-size:18px;">#include <vector>
#include <iostream>
#include <string>
#include <utility>
#include <map>
using namespace std;
int main(){
multimap < string, vector<string>> family;
string last_name, name;
int number;
cout << "Enter last name ." << endl;
while (cin >> last_name){
vector<string> temp;
cout << "Enter names of children ." << endl;
while (cin >> name){
temp.push_back(name);
cout << "More children ? 1(Y) or 0(N) ." << "Enter your choice :";
cin >> number;
if (number == 0)
break;
else
cout << "Continue to Enter information of children's name ." << endl;
}
family.insert({ last_name, temp });
cout << "More family ? 1(Y) or 0(N) ." << "Enter your choice :";
cin >> number;
if (number == 0)
break;
else
cout << "Continue to Enter last name ." << endl;
}
for (const auto& x : family){
cout << x.first<<":" << endl;
for (const auto& y : x.second)
cout << y << " ";
cout << endl;
}
system("pause");
return 0;
}//不得不犧牲美觀,因爲簡化着寫問題着實比較多。</span>
11.24
<span style="font-size:18px;">在原先是空的map中插入一個關鍵字是0,值爲的1的元素。</span>
11.25
<span style="font-size:18px;">報錯。應爲容器爲空,不可以使用下標運算符。</span>
11.26
<span style="font-size:18px;">//下標類型:key_type;
//返回類型:mapped_type.
#include <map>
#include <iostream>
using namespace std;
int main(){
map<int, int> m_int = { { 1, 1 }, { 2, 4 }, { 3, 9 }, { 4, 16 } };
cout << m_int[3] << endl;
cout << typeid(m_int[3]).name() << endl;
system("pause");
return 0;
}</span>
11.27
<span style="font-size:18px;">統計出現的次數用count.
查找是否存在用find。
</span>
11.28
<span style="font-size:18px;">#include <map>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main(){
map<string, vector<int>> m = { { "aa", { 1, 1 } } };
auto result = m.find("aa");
if (result == m.end())
cout << "Not in the container " << endl;
else
cout << "In the container " << endl;
system("pause");
return 0;
}</span>
11.29
<span style="font-size:18px;">lower_bound可能指向尾後迭代器或者返回一個指向大於給定元素的迭代器。
upper_bound如果容器中沒有大於給定值的元素,那麼會返回尾後迭代器,否則返回一個指向大於給定值的迭代器。
equal_range可能會返回一個pair類型,first和second成員都是尾後迭代器。</span>
11.30
<span style="font-size:18px;">因爲返回的是pair類型,pos.first表示返回的pair中的第一個迭代器。然後迭代器用箭頭運算符符訪問map中pair的元素。</span>
11.31
<span style="font-size:18px;">#include <map>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main(){
map <string, vector<string>> m_str = { { "Max", { "John,Carl,Bill" } } };
m_str.insert({ "Max", { "JoJo,Car,BiBi" } });
auto result=m_str.find("max");
if (result != m_str.end())
m_str.erase(result);
else
cout << "Wong hahah" <<endl;
auto temp=m_str.find("Gate");//查找不存在的元素。
if (temp != m_str.end())
m_str.erase(temp);
else
cout << "Not in the container " << endl;
system("pause");
return 0;
}</span>
11.32
<span style="font-size:18px;">#include <map>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main(){
multimap <string, vector<string>> m_str = { { "Max", { "John,Carl,Bill" } } };
m_str.insert({ "Max", { "JoJo,Car,BiBi" } });
for (const auto& x : m_str){
cout << x.first << " :" << endl;
for (const auto& y : x.second)
cout << y << " ";
cout << endl;
}
system("pause");
return 0;
}</span>
11.33
#include <fstream>
#include <iostream>
#include <map>
#include<string>
#include <sstream>
using namespace std;
//先把轉換規則存入一個map中。
map<string, string> build_map(ifstream& ifs){
string key, value;
map<string, string> trans_map;
while (ifs >> key&&getline(ifs, value))
trans_map[key] = value.substr(1);//刪除空格。
return trans_map;
}
//要判斷一個單詞是否需要進行轉換,判斷的規則應該是把給定的單詞同轉換規則文件進行比較。
const string& transform(string& word, map<string, string>& m){
//auto trans_map = build_map(ifs);
auto result = m.find(word);
if (result != m.end())
word = m[word];
return word;
}
//根據輸入進行轉換。
int main(){
string word, temp;
ifstream ifs("rule.txt");
auto trans_map = build_map(ifs);
while (getline(cin, word)){
istringstream is(word);
while (is >> temp)
cout << transform(temp, trans_map) << " ";
cout << endl;
}
system("pause");
return 0;
}
11.34
如果用下標:使用at函數,會拋出異常;使用[]下標,那麼會把不存在的元素插進map中。
以上兩種行爲都不符合自己的要求。
11.35
使用下標會把最後一個匹配的插入進去;
使用insert函數會把第一個匹配的插入進去。
11.36
沒有任何問題。關鍵字和空字符之間形成了一種映射關係。
測試的時候只需要在轉換規則裏面添加一行就可以了。
11.37
<span style="font-size:18px;">書本搬運計劃:
1.在關鍵字類型沒用明顯的序關係的時候。
2.某些情況下,維護元素的代價非常高。
有序的優勢當然是有順序。</span>
11.38
<span style="font-size:18px;">#include <unordered_map>
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main(){
unordered_map<string, size_t> word_count;
string word;
while (cin>>word){
++word_count[word];
}
for (const auto& x : word_count){
cout << x.first << "occurs " << x.second << ((x.second > 1) ? " times " : " time " )<< endl;
}
return 0;
}</span>
單詞轉換程序留待上面的空缺習題一起解決。
7.總結:
看了好多關於學習C++的評價,都說是一種自虐和舔傷口的行爲。在開始看C++ Primer這本書的時候,當時也是信心慢慢,可是看到後面越發覺得艱難,所以我決定改變下計劃,先把簡單的看完,然後集中精力攻克複雜知識。另外給大家發個福利,是大神做的答案然後在git上公開的。另外這位大神在豆瓣上建立了討論組,大家搜一下就可以看見了。
答案鏈接。
End