C 自修入門實境秀、C Primer 5版研讀秀 94 ~12.3.1. Design of the Query Program~練習12...
頁431
11.3.2. Adding Elements
如何在關聯式容器中新增元素、加入元素
關聯式容器的加入元素運算(容器運算)
12:29
關聯式容器也有insert成員函式(見表11.4,頁432)是負責將一個或一個範圍的元素加入到關聯式容器裡的。因為map和set(包括它們的無序版本型別)其鍵值都不能重複,如果試著加入一個與已存在的鍵值同值的元素,是不會有任何作用的。
vector<int> ivec = {2,4,6,8,2,4,6,8}; // ivec 這個vector容器現有8個元素
set<int> set2; // 這是一個空的set
set2.insert(ivec.cbegin(), ivec.cend()); // 執行此式後set2 僅會有4個元素
set2.insert({1,3,5,7,1,3,5,7}); // 承前行,執行此行後set2則會有8個元素(新增了單數值1、3、5、7進去)
23:48
帶有一對迭代器作為引數的insert或是帶著一個初始器串列(initializer list)的instert版本,其作用與其對應的建構器(constructor,§11.2.1頁423)是一樣的:在同個鍵值下,只有第一個元素能被插入。(若容器原有這個鍵值了,這個元素就不會再被插入了【也就是一個key就只能有一個元素。這是非multi版本的才適用吧!】)
Adding Elements to a map 新增元素到map容器
38:30
當我們在map上作insert的運算,要注意要插入的元素必須是個pair型別。然而通常我們並不會有一個符合此要求的物件(現成的pair物件)來作為元素插入到map,而是會在insert的引數列中建構一個pair物件來操作:
// 在word_count這個map容器中加入元素(字彙word)的四種方式
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));
就如我們所見,在C++11的新標準下最簡單的方式就是用一對的大括弧括起要用來初始化的值,以建構一個pair物件。此外,我們也可以調用make_pair函式、或明確建構一個pair(用pair的建構器來建構一個pair)物件。在上述最後一個對insert的調用中,其引數:
map<string,size_t>::value_type(s,1)
頁432
就會建構一個新而適用的pair物件來插入到map中。
第94集50:20
Table 11.4. Associative Container insert Operations
表11.4 :關聯式容器的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的物件;用b和e指出要插入的來源其首尾範圍。
il(initializer list)就是一個被大括號括起的一些C::value_type物件。這樣的運算回傳的是void。(即不回傳東西)
對map、set來說,只會插入容器c尚未有的元素;而multimap、multiset就會插入所有指定範圍中的元素到容器c中。
c.insert(p,v)
c.emplace(p,args) 如同insert(v)或emplace(args),但是是用迭代器p來指出要在c的哪個位值開始找起、是否含有v或args的鍵值來作插入元素。回傳一個指向該鍵值的迭代器。【守真按:若已具有此鍵值,則會是一個指向該鍵值的迭代器;若新增元素成功,則指向該新增的元素。在有序容器中是經過排序的結果。】
Like insert (v) (or emplace (args)), but uses iterator p as a hint for where to begin the search for where the new element should be stored. Returns an iterator to the element with the given key.
第94集 1:45:00 2:1:10 2:5:20
typedef map<string, size_t> mss;
map<string, size_t> m{ {"d",1},{"b",2},{"c",1} };
mss::iterator mssit = m.begin();
mssit++;
m.insert(mssit, {"a",3});
ostream_iterator<string>o(cout,",");
ostream_iterator<size_t>o1(cout,";");
mssit = m.begin();
while (mssit!=m.end())
{
*o++ = mssit->first;
*o1++ = mssit++->second;
}
關聯式容器既然與元素儲存的「位置」無關(頁423),又何故要「where」?!這真的有趣(趣味小品的趣)
Testing the Return from insert
測試由insert回傳的值
第94集49:40 2:5:37 2:9:00 翻譯不要「愚忠」於原文!——特別是非文學性的文本——才是好翻譯
關聯式容器的insert或emplace成員函式回傳的值是什麼,是由該容器的型別與參數來決定的。如果是一個鍵值不能重複的容器,那麼insert與emplace回傳的型別就會是一個pair,來表示插入新元素的操作是否成功。在這個pair中的first資料成員存放的就是一個迭代器,它指向的就是那個具有擬插入鍵值的元素位置,而其second成員則是存放了一個bool值來指出該鍵值的元素是否有成功插入到容器中;若其值為false,就是表示容器中原本就有該鍵值的元素了。如果該容器已具有該鍵值之元素,那麼insert就不會有任何動作,它回傳的就會是個pair,而pair的second成員值就會是個false。若容器中尚無該鍵值的元素,那麼那個準備要插入的元素就會被插入到容器中、且second的值就會是true。
像我們就可以用insert來改寫先前的那個字頻計算程式:
//計算輸入文字之字頻較為繁瑣、繁冗的方式(more verbose way);【守真按:這樣寫為什麼是較為累贅、冗長、繁瑣呢?因為前面用下標運算子([]運算子,subscript operator)來寫就可以直接同時做到檢查鍵值與完成插入元素的動作;簡潔明瞭。】
map<string, size_t> word_count; // 一個空的map,由string可以對應(map)到(可以找到)size_t
string word;
while (cin >> word) {
// 試著插入一個元素到map中,它是一個鍵值是word而值是1的新元素。;
// 如果鍵值word已然存在這個word_count map中,那麼insert就沒有任何作用
auto ret = word_count.insert({word, 1});
if (!ret.second) // ret:retrun;當鍵值word已經存在在word_count這個map中了:
++ret.first->second; // 就遞增計數【運算子優先順序「.、->」等同,而前綴的「++」低2級;參見頁166。所以ret.first是指向map中該鍵值元素的迭代器,解參考後得到該元素,再取其元素的「值」部分(second)來++遞增】
}
第94集 3:45:50
對於每個輸入的文字word,我們預設都會將它以「值」為1的元素來加入(insert)到map容器word_count中。如果word已存在於word_count,對word_count就不會有任何影響,尤其是,並不會改動到word鍵值上的「值」。
頁433
但若word並不在word_count中,那麼這個word就會被加入到word_count裡面,作為新元素的鍵值,而這新加入的元素,它的「值」就會是我們在初始器中{word, 1}提供的「1」。
if判斷式則是在檢查在insert回傳的pair中,second的bool值是否是false。如果是的話,那麼insert就不會有任何動用;而既然word已經存在在word_count中了,我們也就必須要依這個word的值找到這個鍵值並將它所對應到的「值」(也就是word的計數器)遞增加1。
第94集3:51:20
4:10:00 C# 檔案總管汰重-WindowsFormsApplication1 上傳github
4:51:40
Unwinding the Syntax
語法解釋
5:3:10
關於上開語法之解釋
在這個版本中的字頻程式,其遞增字頻計數的述句「++ret.first->second;」或許不易理解,因此在此略作解釋(unwinding)。只要我們能將這個述句(expression)先用(by first)括號括起來,來表示運算子的先後順序是怎樣的,(§4.1.2,頁136)那麼這個遞增字頻計數的述句就會容易理解得多了:
++((ret.first)->second); // 這是相等於原來那個述句的表述式(expression)
這個表達式就是在:
1.ret儲存了一個由insert運算後傳回的值,它是一個pair(型別的物件)。
2.ret.first就在要存取這個pair中的first資料成員,而這個first是一個迭代器,它指向了map容器裡合乎要插入鍵值的元素。
3.ret.first->則是對這個迭代器作解參考運算,來提取它指向的map元素。而這個元素也是一個pair。
4.ret.first->second就是在存取map容器元素中的「值」,也就是那個字頻的計數器。
5.++ret.first->second就會遞增那個計數器。
把以上的程序統合起來看,就可以理解在這個版本的字頻程式中,其遞增字頻計數的述句,會先取得一個指向map中,合乎word這個鍵值元素的迭代器,然後再遞增這個元素所關聯到的計數值。
對於還在使用舊的編譯器的讀者或者還是習慣舊C++標準的讀者來說,像我們這樣對ret的宣告和定義(即其初始化),對他們來說,看起來是很詭異的:
pair<map<string,size_t>::iterator,bool> ret=word_count.insert(make_pair(word,1));
或許,對他們來說,我們正在定義一個pair物件,而其second的型別是個bool,是可以理解的;然而這個pair的第1個部分(first)對他們來說,卻是難懂得多了。這個first所代表的是一個迭代器,這個迭代器是由map<string,size_t>這樣型別的容器來界定的。
Adding Elements to multiset or multimap
新增元素到非唯一鍵值的容器multiset和multimap
5:28:15第94集 沒錄到的看臉書直播480集 https://www.facebook.com/oscarsun72/videos/2549157495195369/
前面那個字頻程式是基於鍵值不重複的原理而運作的,而在這樣的情形下,也只會有一個與某文字相關聯的計數器存在。然而有時也可能會有需要加入相同鍵值元素的需求,比如說我們可能會需要一個由作者可以對應到這個作者他所寫的作品的關聯式資料。在此情形下,就必然會有不只一種作品會與某一作者發生關聯。要配合這樣的需求,我們就得用上multimap而不是map來儲存這樣的資訊。【守真按:其實也可以用容器型別作為「值」的型別,一樣能用map滿足如是的需求】因為multi類型的容器,其鍵值是允許重複的,因此在這類容器上的insert運算,將永遠都能插入一個新的元素進去:
頁434
multimap<string,string>authors;
//加入第一個元素到authors中,它的鍵值是「Barth,John」
authors.insert({"Barth,John","Sot-Weed Factor"});
//這也是可以的:加入第二個也是「Barth,John」這樣鍵值的元素到authors中
authors.insert({"Barth,John","Lost in the Funhouse"});
對於這些允許鍵值重複的容器來說,帶著一個用來插入的元素作為引數的insert運算,決定都能成功地插入該元素,並回傳一個指向該新插入元素的迭代器;而在這樣的情況下,當然就沒有必要再回傳那個用來表示插入成功與否的bool了。
練習11.20
5:39:30
請試著用insert而不是用下標運算來改寫前面§11.1(頁421)的那個字頻程式。您覺得哪一種寫法是較為容易且易於理解的,為什麼?
#include<iostream>
#include<map>
using namespace std;
int main() {
// count the number of times each word occurs in the input
map<string, size_t> word_count; // empty map from string to size_t
string word;
while (cin >> word)
{
pair<map<string, size_t>::iterator, bool> inR = word_count.insert({ word, 1 });
if (!inR.second) ++inR.first->second;
}
//++word_count[word]; // fetch and increment the counter for word
for (const auto& w : word_count) // for each element in the map
// print the results
cout << w.first << " occurs " << w.second
<< ((w.second > 1) ? " times" : " time") << endl;
}
5:45:21
練習11.21
假設word_count是一個由string映射(map)到size_t的map容器,而word是一個string,解釋以下的迴圈在做什麼:
while(cin>>word)
++word_count.insert({word,0}).first->second;
在map上執行insert運算,就是回傳一個pair,pair中的first就是指向該鍵值的迭代器。將之解參考後取得該鍵值的map元素,而map的元素是一個<string,size_t>,所以對之成員存取second,就是取得size_t的值,然後再加以「++」。仍是字頻計算程式的改寫而已。
#include<iostream>
#include<map>
using namespace std;
int main() {
// count the number of times each word occurs in the input
map<string, size_t> word_count; // empty map from string to size_t
string word;
while (cin >> word)
++word_count.insert({ word, 0 }).first->second;//其實就是下列(即練習11.20)的濃縮
//while (cin >> word)
//{
// pair<map<string, size_t>::iterator, bool> inR = word_count.insert({ word, 1 });
// if (!inR.second) ++inR.first->second;
//}
//原來的下標(subscript)運算
//++word_count[word]; // fetch and increment the counter for word
for (const auto& w : word_count) // for each element in the map
// print the results
cout << w.first << " occurs " << w.second
<< ((w.second > 1) ? " times" : " time") << endl;
}
5:53:17
練習11.22
如果有一個map容器,它長的像是這樣:map<string,vector<int>>。請寫出在這個map上執行insert運算時要帶入的引數型別、及運算後會回傳的型別分別是什麼。
這裡做insert的引數型別是pair<string,vector<int>>;而insert回傳型別也是pair,而是:
pair<map<string,vector<int>>::iterator,bool>
#include<iostream>
#include<map>
#include<vector>
using namespace std;
int main() {
map<string, vector<int>> m;
string word;
int i;
vector<int>v;
while (cin >> word)
{
cin >> i;
v.push_back(i);
cin >> i;
pair<map<string, vector<int>>::iterator, bool> insResult =
m.insert(pair<string, vector<int>>(word, v));
if (!insResult.second) insResult.first->second.push_back(i);
//以上3行程式碼可濃縮為下一式
//(m.insert(pair<string,vector<int>>(word,v)).first->second).push_back(i);
}
for (const auto& w : m) // for each element in the map
// print the results
cout << w.first << " occurs " << w.second.back()
<< ((w.second.size() > 1) ? " times" : " time") << w.second.size()<<
endl;
}
6:3:05
練習11.23
重寫前面§11.2.1(頁424,練習11.7)的練習,將map改為multimap,一樣鍵值是家庭姓氏,而「值」是存放了每家孩子名字的vector。
#include<iostream>
#include<map>
#include<vector>
#include<iterator>
using namespace std;
int main() {
multimap<string, vector<string>> m;
istream_iterator<string>in(cin), end;
string lastName; vector<string>v;
while (in != end)
{
lastName = *in;
m.insert(pair<string, vector<string>>(lastName, v))->second.push_back(*++in);
++in;
}
ostream_iterator<string>out(cout, ",");
for (auto a : m)
{
cout << a.first << ":";
copy(a.second.cbegin(), a.second.cend(), out);
cout << endl;
}
}
11.3.3. Erasing Elements
11.3.3刪除關聯式容器的元素
6:9:40 6:23:19
erase()成員函式
關聯式容器定義了3種的erase運算,在表11.5有列出來。就像循序容器一樣,我們也可以藉由傳給erase一個迭代器、或一對迭代器來刪除指定的一個元素、或一個迭代器範圍內的所有元素。這3種關聯式容器的erase運算和其相對應的循序容器erase運算一樣,都會刪除指定的元素,而且並不會回傳東西(回傳void)此說與Table 11.5相矛盾!。
關聯式容器還提供了一個額外的、帶了一個key_type作為引數的erase運算(成員函式),這樣的erase會刪除符合指定鍵值的所有元素,並回傳一個數值來表示有多少這樣的元素已被刪除。因此,我們可以利用這樣的erase來在前面那個word_count印出結果前清除一個我們想要刪除的字彙:
//根據鍵值來做erase的話會回傳被刪除元素的數量
if(word_count.erase(removal_word))
cout<<"ok: "<< removal_word << "removed\n";
else cout<<"oops: " << removal_word <<" not found!\n";
對於不可重複鍵值的關聯式容器來說,這樣的erase回傳的永遠都只會是0或1。如果回傳的是0,那麼就表示想要刪除的鍵值就並不存在於該容器中。
頁435
而對於鍵值可重複的關聯式容器來說,這樣回傳的值當然就可能會是大於1的整數。
auto cnt=authors.erase("Barth, John");
如果這個authors是我們在§11.3.2(頁434)創建的那個multimap容器,那麼上列程式碼中的cnt(counter)值就會是2。
Table 11.5. Removing Elements from an Associative Container
表11.5 :從一個關聯式容器中移除元素
c.erase(k)
從c容器中刪除所有符合鍵值k的元素。回傳一個size_type來指出有多少的元素已被刪除了。
c.erase(p) 從c容器中刪除迭代器p所指向的元素。p必須指向容器c實存的元素,p決定不能和c.end()回傳的迭代器等值。經過這樣的運算回傳的會是一個指向p原位置下一元素的迭代器、或回傳一個c.end()這樣的迭代器——只要p原指向的是c容器中的最後一個元素,回傳的就會是這樣的迭代器。
c.erase(b,e) 刪除在c中,由b、e這對迭代器所畫出範圍內的所有元素;回傳的會是e
正文說不回傳這表卻說回傳:
These versions of erase are similar to the corresponding operations on sequential containers: The indicated element(s) are removed and the function returns void.
11.3.4. Subscripting a map
11.3.4對一個map下標(map的下標運算)
6:45:40 7:19:40
map和unordered_map容器還提供了下標運算子和一種對應的at成員函式(§9.3.2,頁348)。at函式會在下一頁的表11.6中描述。因為set並沒有「值」來與鍵值作關聯,所以set這類的容器並不支援這樣的運算。正因為set元素的本身就是鍵值,所以想要依某鍵值而去提取它關聯的「值」這樣的操作,當然對set這樣的容器來說就會是毫無意義的了,因為都已經知道其「值」是什麼了,(在set中值=鍵值)還想要提取fetch什麼呢?【下標本身的意義,就是想要根據有限的資訊想要知道更多的。這裡下標也像工程下標一樣,或拍賣會上,對所要拍賣的物件進行下標一樣。】當然這樣的下標運算也不能用在multimap和unordered_multimap上面,因為在multi這類容器中,關聯到鍵值的「值」並不是唯一的(一對一的),所以所謂的就鍵值來「提取fetching」唯一關聯的「值」這一行為也就沒什麼意義可言了。
就像我們之前用過的下標運算子一樣,map的下標運算也帶了一個索引值作為引數,這個值在此就是一個鍵值;藉由這個指定的鍵值來提取(fetches)與它唯一相應的「值」出來。【下標一定都是一對一】然而與其他下標運算子的運算不同的是:如果用來下標的鍵值(索引值)並不存在於map容器中,那麼下標運算就會依據這個鍵值來創建一個新的元素到map中;而此新元素對應此鍵值的「值」則會是一個經由值初始化(value initialize,§3.3.1,頁98)的「值」。
舉例來說,當我們寫出像下面這樣的程式碼時:
map<string,size_t>word_count;//一個空的map
//下列這行,如果word_count這個map容器中並不存在鍵值「Anna」這樣的元素,那麼就會插入一個經由值初始化的元素到map中:它的鍵值會是「Anna」,而「值」則會是「0」;然後再將這個「值」指定(assign)為1。
word_count["Anna"]=1;
程式在執行時就會發生以下的情形:
在word_count中尋找檢查search是否有一個元素,其鍵值為「Anna」;而這樣的元素未被找到。
一個新的鍵值與值對組(key-value pairs)的元素被插入到word_count中。這個新元素的鍵值型別是const string,其值為「Anna」;而其「值」的部分則會被值初始化——在此例中,「值」的值就會是「0」。
提取fetch這個新插入元素的「值」,並將其值設定為「1」。
頁436
就是因為在對map做下標時很可能會新增元素進去,所以我們應該只對非唯讀的map,使用下標運算。
注意:
對map做下標運算,和對array和vector做下標是非常不同的:如果用來下標的鍵值原本並不存在於map中,那麼決定會插入一個新的鍵值(也就是帶著這個鍵值的元素)到這個map容器裡。
Table 11.6. Subscript Operation for map and unordered_map
表11.6 : map與unordered_map的下標運算
c[k] 回傳一個鍵值為k的元素(「值」)。如果c容器中並無此鍵值,那麼就會加入這個鍵值的元素進去,它的「值」的部分是經由值初始化的。
c.at(k) 可作為對存取鍵值為k元素的檢查,如果k並不在c中,那麼就會丟出一個out_of_range的例外情形(§5.6,頁193)。
7:10:50 7:38:23 7:43:10
Using the Value Returned from a Subscript Operation
使用下標運算回傳的值
下標map所回傳的型別也會和之前其他的下標運算不同。通常(之前那些下標運算),對迭代器做解參考回傳的型別會與下標運算子回傳的型別相同。但是對於map來說,卻並不是如此。當對map做下標後,我們會得到一個mapped_type物件,而若我們對map的迭代器做解參考,回傳的則會是一個value_type物件(§11.3,頁428)。
通常,就如同其他的下標運算,map的下標運算子回傳的也是左值(lvalue,§4.1.1,頁135)。正是因為回傳的是左值,所以就可以對其元素同時進行讀與寫的操作:
cout<<word_count["Anna"];//由Anna這個鍵值來提取它對應到的「值」,把這個「值」給印出來,印出來的會是「1」。
++word_count["Anna"];//提取鍵值Anna對應到的「值」出來,並將其遞增加1。
cout<<word_count["Anna"];//再用Anna來提取其所對的「值」,把它給印出來,此時就印出「2」。
注意:不像vector或string;map的下標運算子回傳的型別和解參考map迭代器後得到的型別是不同的。
8:1:43
對map下標時,若下標的鍵值並不存在於map中,下標運算就會依此鍵值自動在map中新增一個這樣鍵值的元素進去。這一屬性,有時可以讓我們寫出極其簡潔的程式碼;如在§11.1,頁421中的那個字頻程式迴圈內的程式碼,就能依此原理,極致簡化。【守真按:即此一屬性,有其利,如前;亦有其弊,如後:】但是,有時我們也只是想知道某一鍵值的元素是否已在map中,而並不想動到map中的元素,那麼我們就決定不能用map的下標運算子來操作。
當對map做下標時,回傳的會是mapped_type(即及「值」的型別),而對map的迭代器作解參考(dereference)則會得到pair的型別(value_type)
⑧找對主詞
一個是元素值(迭代器所指)一個是元素下面的「值」(即mapped_type),下標針對的是這個「值」,不是元素值。
map下標回傳的值是左值(lvalue),因為是左值,所以我們可以藉由此左值(傳址(pass by reference))來改動其所指的元素下面的「值」(mapped_type型別的物件)。
鍵值(key_type)是不能動的const,而值(mapped_type)是可以動的。
8:18:10
11.3.5. Accessing Elements
存取關聯式容器(associative container)中的元素
關聯式容器提供了幾種(various)找到元素的方法,詳見表11.7(頁438)所列。至於到底要用哪一種尋找元素的方式,就端看我們想要解決的是怎樣的問題。如果我們只是想知道某元素存在是否已在某容器中,那麼最適合的方式就莫過於find函式了。若操作的對象是鍵值不可重複的容器,那麼不管我們是用find或count來判斷,都是非常合適的。然而,若是要在鍵值可以重複的容器中來判斷的話,那麼如果是選用了count,就可能得付出額外且無謂的代價。【殺雞焉用牛刀】因為count除了能知道在容器中是否已有該元素外,它在執行時,還得再去計算在該容器中到底有多少這樣的元素。【可見慎選適當方法(成員函式)的重要!】可見,在這種情況下,若我們並不需要計數,當然還是用find就好。
find()
只要找到元素,不管它有幾個,就用find
count()
要計數(符合條件的元素有幾個)才用count
9:1:55 9:24:01
對無序容器(unordered container)測試equal_range運算
https://github.com/oscarsun72/prog1-C-Primer-5th-Edition-s-Exercises/blob/p438unordered_c_equal_range_test/prog1/prog1.cpp
unordered_multimap<string, size_t> word_count; // empty map from string to size_t
string word;
while (cin >> word)
{
auto inR = word_count.insert({ word, 1 });
//if (!inR.second) ++inR.first->second;
}
auto pp=word_count.equal_range("孫守真");//無序容器有equal_range, 卻不能執行lower_bound和upper_bound!
頁437
習題章節11.3.4
練習11.24
以下程式在做些什麼事?
map<int,int>m;
m[0]=1;
以「0」這個鍵值來存取其所對應的「值」,並將此「值」的值設定為1。
9:36:50
#include<iostream>
#include<map>
using namespace std;
int main() {
map<int,int> m;
cout << m.size() << endl;//=0
m[0] = 1;
cout << m.size() << endl;//=1
cout << m.begin()->first << ":" << m.begin()->second << endl;//=「0:1」
}
練習11.25
比較下列程式與前一練習的程式有什麼異同?
vecotr<int>v;
v[0]=1;
這個是用「0」為索引值來存取v中第0個位置的元素(即首位元素、第一個元素),然後再將此元素的值,設定為「1」。這裡vector<int>的int,與v[0]的「0」無關,與「1」才有關。而前一練習的「1」,是與map<int,int>的「,int>」有關的。
#include<iostream>
#include<vector>
using namespace std;
int main() {
vector<int> v;
cout << v.size() << endl;//=0
//v[0] = 1;//error :vector subscript out of range
try
{
v.at(0) = 1;
}
catch (const std::exception& ex)
{
cout << v.size() << endl;//=0
cerr << ex.what() << endl;
}
}
9:38:22由at成員函式與下標運算子同一作用這一點也可明顯看到其實所謂的運算子,也不過就是函式的一種或云「變種」或「變態」。
練習11.26
哪種型別可以作為對map下標的型別?下標運算子回傳的又是什麼樣的型別?寫出一個實例,來演示當要對map做下標運算時能用怎樣的型別,又其下標運算子回傳的又會是什麼樣的型別。
map<,>::key_type才能對map下標。
若是map,其下標運算子回傳的,就是map<,>::mapped_type型別。
https://github.com/oscarsun72/prog1-C-Primer-5th-Edition-s-Exercises/blob/exercise11_26review/prog1/prog1.cpp
map<string, size_t> word_count;
string word;
while (cin >> word)
{
word_count.insert({ word, 0 }).first->second++;//前綴、後綴在這裡是沒有差的,因為都比成員存取運算子要殿後才執行
}
map<string, size_t>::key_type mapK{"孫守真"};
map<string, size_t>::mapped_type mapped = word_count[mapK];
cout << mapped << endl;
map下標(subscript)針對的是mapped_type(元素下面的那個「值」),回傳的是mapped_type
是藉由key_type來找到mapped_type,所以只要是與key_type相容的型別均可用來下標
#include<iostream>
#include<map>
using namespace std;
int main() {
map<int, string>m;//下標(subscript)要用角括弧中逗號前的相容型別,下標回傳的型別則為逗號後的型別
cout << m.size() << endl;//=0
m[1.1] = "i";
cout << m.size() << endl;//=1
cout << m.begin()->first << ":" << m.begin()->second << endl;//=1:i
/*Warning C4244 'argument': conversion from 'double' to 'int', possible loss of data*/
string s = m[1.3];
auto as = m[1.3];//type of as is string
cout << m.size() << endl;//=1
cout << m.begin()->first << ":" << m.begin()->second << endl;//=1:i
}
10:16:10
set<int>iset={0,1,2,3,4,5,6,7,8,9};
iset.find(1);//回傳一個迭代器指向鍵值為1的元素
iset.find(11);//找不到就回傳一個迭代器指向最後一個元素後的位置(其值等於iset.end())
iset.count(1);//回傳1
iset.count(11);//回傳會是0
回傳的不是常值的迭代器就表示擬(或可以)以此迭代器對其所指元素進行編輯(只能編輯「值」,故在set中是唯讀的,set只有鍵值,沒有「值」可編輯)
10:22:20
Using find Instead of Subscript for maps
盡量用map的find成員函式也不要對map做下標運算
!!這一段,了無新意,前面內容都已提過了!!
對於map和unordered_map這樣的容器,他們的下標運算子提供了最便捷存取其「值」的方式。然而就如我們先前所見,下標運算有個嚴重的副作用:那就是如果下標用的鍵值並不存在於原容器中,那麼下標運算就會依其鍵值自動插入一個新的元素。這樣的作用是否適當,完全取決於我們預期的操作是什麼。之前那個字頻程式就是依靠這樣的作用,來將一個原本不在容器中的鍵值,藉由下標運算的方式安插到容器裡面,使得這個容器可以新增一個鍵值為該鍵值而其「值」為0的元素。
有時我們想要知道符合某個鍵值的元素是否已在map容器中,而並不想去動到map,那麼我們就決定不能用下標運算子來做這樣的檢驗或測試,因為若是這樣做,當原本沒有那個元素時,下標運算就會自行插入一個那樣的元素進去。【守真按:一句話一直繞著講!一直繞!】在這種情況下,我們就該改用find,而不是用下標運算子來操作:
if(word_count.find("foobar")==word_count.end())
cout<<"foobar is not in the map"<<endl;
10:44:20 10:52:10
Finding Elements in a multimap or multiset
在multimap或multiset中尋找元素
在鍵值不可重複的關聯式容器中尋找元素,其結果很單純:要嘛元素在容器裡,要嘛它就不在。然而對於鍵值可以重複的容器而言,這樣的運算就顯得複雜得多,因為可能會有許多的元素是符合要找的條件的。當multimap或multiset含有多個符合某個鍵值值的元素時,這些元素在容器中都會被排在一塊。【其實即使無序的map(unordered_multimap)也是會排在一塊,其鍵值都是經過排序。參見前面對無序容器(unordered container)測試equal_range運算。】
頁438
Table 11.7. Operations to Find Elements in an Associative Container
表11.7 尋找關聯式容器元素的運算 8:41:00
表11.7 :尋找關聯式容器元素的運算
對無序容器而言,並沒有lower_bound 和upper_bound運算。
下標和at函式也只能用在非常值的map與unordered_map上。
c.find(k) 回傳一個指向(第一個)k鍵值的元素,當k並不存在於容器中,就回傳一個尾端後迭代器(off-the-end iterator)。
c.count(k) 回傳在容器c中鍵值為k元素的數目。對鍵值不可重複的容器來說,回傳的值永遠是0或1。
c.lower_bound(k) 傳回一個迭代器,指向第一個鍵值不小於k的元素。
c.upper_bound(k) 傳回一個迭代器,指向第一個鍵值大於k的元素。
c.equal_range(k)
傳回一個由2個迭代器組成的對組(pair)物件,來指出所有鍵值是k的元素。如果容器c中並沒有這樣的鍵值,那麼回傳的pair,其2個迭代器都會是c.end()【即尾端後迭代器】。
Returns a pair of iterators denoting the elements with key k. If k is
not present, both members are c.end().【經測試後,這個pair的first即lower_bound回傳的,而secend即upper_bound回傳的,但在無序容器中,只有equal_range運算,卻沒有lower_bound與upper_bound運算;因此若有需要在無序容器中取得這二個運算的傳回值,就可以用equal_range來取代】
11:2:23
比如說,在那個由作者與其作品關聯起來的map的應用上,我們可能會想要印出某個作者他所有的作品。要滿足這樣的需求,其實至少有3種方式。而其中最簡明的莫過於利用find和count這兩個成員函式來完成這樣的操作:
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;//記下我們已經印出了幾部作品名稱
}
string search_item("Alain de Botton"); // author we'll look for
auto entries = authors.count(search_item); // number of elements
auto iter = authors.find(search_item); // first entry for this author
// loop through the number of entries there are for this author
while (entries)
{
cout << iter->second << endl; // print each title
++iter; // advance to the next title
--entries; // keep track of how many we've printed
}
在上列程式碼中,我們最先是用count來判斷到底有多少符合該作者的作品存在,並且用find來取得指向第一部作品的迭代器。至於那個while迴圈到底要重複幾次【英文版原文while誤作for,中文版也不校訂!】,就完全取決於count回傳的值。若count回傳的是「0」,那麼執行流程就不可能進入到這個迴圈。
注意:我們可以確定的是,在對multmap或multiset作巡覽時,會依序、挨次回傳所有符合某個鍵值的元素。【其實無序的也是,詳前對無序容器(unordered container)測試equal_range運算】11:27:00
The number of iterations of the for loop depends on the number returned from count .
明明例子是用「while」,英文版誤矣!
中文版翻譯有誤:
We are guaranteed that iterating across a multimap or multiset returns all the elements with a given key in sequence.
我們可以保證,迭代過一個multimap或multiset會回傳一個序列中具有給定鍵值的所有元素
我們有把握
are guaranteed that是被動式
會按順序地回傳所有符合指定鍵值的元素出來
A Different, Iterator-Oriented Solution
另一種,以迭代器為主導的解決方式
另一種基於迭代器的解決方式
此外,我們也可以用lower_bound和upper_bound來滿足我們查找元素的需求。這兩個成員函式都帶了一個鍵值作為引數,並會回傳一個迭代器。如果在容器中存在要找的鍵值,那麼lower_bound回傳的就會是一個指向第一個符合鍵值元素的迭代器,而upper_bound回傳的則會是一個指向最後一個符合鍵值的元素後那個位置的迭代器。如果元素沒有找到,那麼lower_bound和upper_bound回傳的值就會相等:都會是一個指向可以照順序插入新元素的那個位置的迭代器。也就是這樣,以同一個鍵值來調用lower_bound和upper_bound就會產生一個迭代器範圍(iterator range,§9.2.1,頁331),這個範圍包含的是所有符合該鍵值的元素。
當然這些運算也可能會回傳尾端後迭代器(off-the-end iterator)。當我們要找的鍵值,是原容器中最大的值,用這個鍵值來調用upper_bound就會回傳一個尾端後迭代器(off-the-end iterator)。
頁429
Table 11.3. Associative Container Additional Type Aliases
表11.3 :關聯式容器額外的型別別名【其實乃map與set的型別成員(type member),所以在定義此型別物件時,一定要用到範疇運算子「::」】第94集10:14:00
key_type 某個關聯式容器型別的鍵值型別。
對於map容器來說,就是pair<const key_type,mapped_type>
mapped_type 和每個鍵值關聯的型別(就是和鍵值對應到的值型別,被映射(map)的型別),這個型別只有map能用。即「值」的型別。
value_type 對set來說,就是key_type。對map而言,value_type就是pair<const key_type, mapped_type>這樣的型別。
留言