11.3 關聯容器操作
關聯容器定義的額外的類型。
注意map的value_type是一個pair類型,而且pair的first成員是const類型,這是因爲我們從map中得到的pair返回的是引用,這意味着我們可以修改pair中的數據成員,而first成員是map容器的關鍵字,關鍵字是不允許改變的,爲了防止我們修改了關鍵字,所以將first設置爲const。
mapped_type表示的是map容器key關聯的value的類型。
11.3.1 關聯容器迭代器
關聯容器同樣有迭代器,map迭代器解引用返回的是pair,且par的first是常量類型。
set迭代器返回的是常量類型的迭代器,因爲set的關鍵字就是值,而關鍵字是不允許改變的。
因爲set的迭代器爲const,而map的pair的fist是const,所以有些泛型算法對於關聯容器是沒法使用的,因爲這些算法需要寫入值。所以當我們對關聯容器使用算法時,關聯容器當做輸入範圍時,只能使用只讀算法,但是當做目標位置時,可是使用inserter()進行插入。
如果泛型算法和關聯容器都有某一個算法,那麼優先使用關聯容器提供的算法。
對於關聯容器,我們同樣可以使用beign()和end()來遍歷元素,遍歷的元素是按照關鍵字的字典序升序輸出的。
練習
11.15
從之前的內容可以知道。
mappped_type爲vector<int>
key_type:int
value_type:pair<const int,vector<int>>
11.16
使用迭代器來將關鍵字3的值,改爲three
map<size_t, string> my_map = { {1,"one"},{2,"two"},{3,"four"} };
auto iter = my_map.begin();
++iter;
++iter;
(*iter).second = "three";
cout<<my_map[3]<<endl;
11.17
只有2是不合法的
首先因爲set的迭代器是const類型,所以無法修改迭代器指向的元素的值,但是copy算法是隻讀算法,所以不會修改multiset的內容。
其次是關聯容器的迭代器可以作爲目標位置的參數,進行insert,這是所有容器都有的成員函數,但是back_iterator()調用push_back(),set容器沒有。
//std::copy(v.begin(),v.end(),std::inserter(c,c.end()));
//std::copy(v.begin(),v.end(),std::back_inserter(c));
//std::copy(c.begin(),c.end(),std::inserter(v,v.end()));
std::copy(c.begin(), c.end(), std::back_inserter(v));
11.18
map<string, int> word_count;
map<string, int>::const_iterator map_it = word_count.cbegin();
while (map_it!=word_count.cend()) {
//todo
++map_it;
}
11.19
之前寫的Sale_data懶得copy過來了,使用string代替一下。
//自定義比較操作
bool compare_str_len(const string& str1,const string& str2) {
return str1.size() < str2.size();
}
//創建容器
multiset<string, bool (*)(const string& str1, const string& str2) > bookstore(compare_str_len);
//創建迭代器
multiset<string, bool(*)(const string& str1, const string& str2) >::const_iterator iter = bookstore.cbegin();
11.3.2 添加元素
在set和map添加元素時,如果關鍵字已經存在,則插入失敗。
向map中插入元素,需要插入pair類型的數據。添加元素的方法和之前創建pair的方式一樣。
可以使用列表初始化,make_pair以及顯式的創建一個pair。
關聯容器有如下的插入操作。
當我們向關聯容器中添加一個元素時,容器會返回一個pair。pair的first是一個迭代器指向具有指定關鍵字的元素。second是一個bool值,表示插入是否成功。
注意,無論是set和map,插入元素時都會返回一個pair。不要把map的元素爲pair類型和返回pair弄混了。
因爲multiset和multimap允許重複關鍵字,所以向multiset和multimap插入新元素時,返回的只是指向新元素的迭代器。沒有bool,因爲不管關鍵字是否存在,插入總是成功的。
練習
11.20
開放題
我覺得使用下標更加的容易理解。
因爲在map中如果關鍵字不存在,那麼就會創建一個pair,併爲關聯的值執行值初始化。我們在編碼的時候只需要一句話,++words[word].
而使用insert的話,首先需要檢測插入是否成功,如果插入失敗,需要在insert返回的pair上進行相關的操作。
這樣的代碼可讀性是很差的。
++(result_pair.first->second)
string detect(const string& str,const set<char> & ingore) {
//string temp_str = "";
std::ostringstream output_str("");
for (const auto & item:str) {
if (ingore.find(item) == ingore.end()) {
output_str << static_cast<char>(tolower(item));
}
}
output_str.flush();
return output_str.str();
}
map<string, size_t> words;
set<string> ingore_word_set = {"the","an","a","of","from","and"};
set<char> ingore_char_set = {',','.',';'};
string word;
while (cin>>word) {
////11.1
//if (ingore_word_set.find(word)== ingore_word_set.end()) {
// word = detect(word,ingore_char_set);
// ++words[word];
//}
//11.20
if (ingore_word_set.find(word)== ingore_word_set.end()) {
word = detect(word,ingore_char_set);
auto result_pair = words.insert({word,1});
if (!result_pair.second) {
++(result_pair.first->second);
}
}
}
for (const auto& item:words) {
cout << item.first << " :" << item.second << endl;
}
11.21
作用:爲單詞計數
相對於我11.20寫的代碼,要簡單一些。
調用insert會返回pair,無論是否插入成功,都會返回指向關鍵字-值對的迭代器。通過first訪問該迭代器,然後通過箭頭運算符,訪問該迭代器指向的元素的值,併爲值+1.
11.22
map的insert會返回一個pair,這個pair的first是指向容器中插入(或已存在)的關鍵字所在的鍵值對的迭代器。第二個參數爲是否插入成功
map<string, vector<int>> m;
std::pair<map<string,vector<int>>::iterator,bool> result = m.insert({ "a",{1,2,3,4} });
11.23
multimap是沒有下標運算符的
而且如果分兩次插入同一個關鍵字,那麼在multimap,會有兩個pair對應這個關鍵字。
//11.23
multimap<string, vector<string>> family = {
{"A",{"aa1","a2"}},
{"B",{"bb1","bb2","bb3"}}
};
vector<string> new_item = { "c1","cc2" };
family.insert({ "C",new_item });
family.insert(std::make_pair("A", new_item));
for (const auto& item : family) {
cout << item.first << ":" << std::ends;
for (const auto& member : item.second) {
cout << member<< std::ends;
}
cout << endl;
}
11.3.3 刪除元素
關聯容器提供三個版本的erase。
其中erase()可以傳入關鍵字,關聯容器將刪除所有和此關鍵字匹配的元素並返回刪除的元素的數量,對於關鍵字不能重複的容器,該值爲0或者1,而對於關鍵字可以重複的容器,該值>=0.
對於傳入迭代器的erase,關聯容器將返回刪除之後的元素的下一個迭代器。
對於傳入輸入範圍的erase,關聯容器將返回第二個迭代器。
11.3.4 map的下標操作
只有map和unordered_map纔有下標操作,set沒有下標操作,multimap也沒有。
map的下標操作分爲兩個
一個是下標運算符,一個是at
如果使用下標運算符來訪問map中的元素,如果該關鍵字不存在,則會創建一個關鍵字-值對插入到map中,併爲值執行值初始化。
具體步驟爲:
1.在map中搜索關鍵字
2.如果沒有找到則創建一個關鍵字-值對,插入到map中
3.對值進行值初始化
因爲使用下標運算符可能會添加新的元素,所以只有非const的map纔可以使用下標運算符。
只有使用下標運算符,如果關鍵字不存在會報錯,但是使用at不會,所以at,對於const的map也是可以用的。
而且at是會進行類型檢查,當元素不存在時會報錯。
下標操作的返回值
和vector,string類型不一樣,map的下標操作得到的值類型和使用迭代器得到的值類型是不一樣的。
map的下標操作得到的值類型爲mapped_type而使用迭代器解引用得到的值類型爲value_type。
下標操作返回的是值得引用,所以我們可以通過下標操作來修改值。
練習
11.24
1.創建一個關鍵字和值類型都爲int得map,並定義一個遍歷m
2.在m中尋找關鍵字0
3.沒有找到關鍵字0,所以添加一個關鍵字-值對,(0,0)
4.將關鍵字爲0對應的值賦值爲1
11.25
1.創建一個存儲int值得vector類型,並定義一個變量v
2.訪問v中下標爲0的元素,由於v爲空,所以報錯
11.26
根據前面學習到的,如果一個類型定義了<運算符,那麼這個類型就可以作爲關鍵字,那麼只要類型定義了<運算符,都可以對map進行下標操作。
下標操作返回的類型爲map的值類型。
map<string, int> m;
m["1"]=123;
11.3.5 訪問元素
C++ Primer書上說下標運算符和at()都只能在非const的map和unordered_map上使用,但是我在VS2017上,發現只有at()運算符是可以使用的,不知道這是微軟對編譯器做了優化還是怎麼的
關聯容器上訪問元素的方式有很多,這取決於我們想要的操作。
1.find,用來查找給定的定關鍵字是否在容器中,如果在則返回指向元素的迭代器,如果不在則返回尾後迭代器
2.cout,用來統計給定關鍵字在容器中的數量,在不允許重複的關聯容器中值爲0或者1,在multi類的容器中值>=0.
3.lower_bound()用來返回第一個關鍵字不小於k的迭代器,也就是第一個>=k的迭代器,有什麼用呢?如果我們使用默認<操作符來存放元素,那麼存入的元素的關鍵字順序是符合字典序的,那麼我們使用lower_bound()得到的第一個>=k的迭代器可以作爲迭代器範圍的第一個參數,如果k不存在,返回尾後迭代器
4.upper_bound()得到是是第一個大於k的迭代器,可以用來作爲迭代器範圍的第二個元素。如果不存在,返回尾後迭代器,所以如果k不存在,那麼lower_bound()==upper_bound();
5.equal_range(k)則是一次性找到等於k的一個範圍,它返回一個pair,first指向第一個匹配的元素的迭代器,second指向最後一個匹配的元素的下一個元素。如果沒有匹配到,則first和second都是end(),所以equal_range()可以同時做到4和5的事情
對於multimap和multiset插入的相同關鍵字的元素是相鄰存儲的。
所以我們可以使用cout(k)找到關鍵字在容器中的個數。使用find(k)找到第一個匹配的關鍵字,然後使用循環遍歷出相同關鍵字的元素。
multimap<int, string> m;
m.insert({1,"1"});
m.insert({1,"one"});
m.insert({1,"一"});
auto size = m.count(1);
auto iter = m.find(1);
while(size){
cout << iter->second << endl;
--size;
++iter;
}
也可以使用lower_bound和uppper_bound(),以及equal_range()來完成。
multimap<int, string> m;
m.insert({1,"1"});
m.insert({1,"one"});
m.insert({1,"一"});
for (auto iter = m.lower_bound(1); iter != m.upper_bound(1);++iter) {
cout << iter->second << endl;
}
auto two_iter = m.equal_range(1);
for (auto iter = two_iter.first; iter != two_iter.second;++iter) {
cout << iter->second << endl;
}
//這種方法更加的簡潔
for (auto pos = m.equal_range(1); pos.first != pos.second;++pos.first) {
cout << pos.first->second << endl;
}
練習
11.27
如果我僅僅需要知道某個關鍵字是否在元素中,那麼我會使用count。但是如果還需要對這個關鍵字進行訪問,修改等操作,那麼我使用find
11.28
map<string, vector<int>> m;
map<string, vector<int>>::iterator iter= m.find("123");
11.29
都返回都是容器的尾後迭代器,也就是不影響元素順序的插入位置。 後半句話,書上只寫提了這麼一下,具體怎麼理解,也沒有說。= =
11.30
如果key存在,那麼pos的first返回的是第一個和key匹配的元素的迭代器,因爲對迭代器解引用得到的是一個pair類,所以爲了訪問迭代器所指向元素的值,我們需要pos.first->second;
11.31
multimap<string, string> books = { {"1","1"} ,{"1","one"},{"1","一"} ,{"3","3"} };
auto iter = books.find("1");
/*if (iter != books.end()) {
books.erase(iter);
}*/
//如果使用key_value的話,可以一次全部刪除,但是使用迭代器是不行的
while (iter!=books.end()) {
books.erase(iter);
//在循環中更新迭代器
iter = books.find("1");
}
for (const auto& item:books) {
cout <<item.first<<":"<< item.second << endl;
}
11.32
上一個代碼已經打印出來了。
11.3.6
如果使用下標操作重複的爲關聯容器添加相同關鍵字的容器,那麼關鍵字-值對中的值,爲最後一個賦值的值。
但是insert,如果關鍵則已經存在於容器內,不會指向插入。
map<string, string> books = { {"1","1"} ,{"1","one"},{"1","一"} ,{"3","3"} };
books["1"] = "344";
for (const auto& item:books) {
cout <<item.first<<":"<< item.second << endl;
}
輸出爲
1:344
3:3
如果沒有那條賦值語句,則輸出
1:1
3:3
練習
11.33
map<string, string> buildMap(ifstream& map_file) {
map<string, string> m;
string word;
string target_word;
while (map_file>>word&&std::getline(map_file,target_word)) {
//去除空格
target_word = target_word.substr(1);
if (target_word.size()>0) {
m[word] = target_word;
}
else {
throw std::exception("target word is null");
}
}
return m;
}
void word_transform(ifstream& map_file,ifstream &input) {
auto trans_map = buildMap(map_file);
string text;
while (std::getline(input,text)) {
std::istringstream stream(text);
string word;
while (stream>>word) {
auto iter = trans_map.find(word);
if (iter!=trans_map.end()) {
cout <<iter->second << std::ends;
}
else {
cout << word << std::ends;
}
}
cout << endl;
}
}
11.34
原本在trans_map中沒有出現的關鍵字會被添加到trans_map中,trans_map[word],返回空字符串,程序編譯報錯。
11.35
不會起作用,因爲關鍵字已經存在於map中,所以相同關鍵字的元素會插入失敗
11.36
因爲添加了異常檢測,所以程序會報錯。