C++ Primer 筆記+習題解答(十一)

今天是第十一篇筆記,主要內容是關聯容器。關聯容器的涉及到的內容相比順序容器是很少的,所以篇幅也是比較短小的。但是一些細節方面的問題還是比較晦澀的,經過一番掙扎後,我還是決定先放棄比較困難的部分。好讀書不求甚解從某些層面來說也許是好事,有些東西真的需要一定基礎才能理解透徹。在本節中,書上對無序容器的介紹很少,尤其是哈希相關的內容,基本就是一筆帶過。

有錯誤 請指正 謝謝

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


發佈了53 篇原創文章 · 獲贊 3 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章