map 是鍵-值對的集合。map 類型通常可理解為關聯數組(associative array) :可使用鍵作為下標來獲取一個值,正如內置數組類型一樣。而關聯的本質在於元素的值與某個特定的鍵相關聯, 而並非通過元素在數組中的位置來獲取。
map 對象的定義
要使用 map 對象,則必須包含 map 頭文件。在定義 map 對象時,必須分別指明鍵和值的類型(value type)
// count number of times each word occurs in the input
map<string, int> word_count; // empty map from string to int
這個語句定義了一個名為 word_count 的 map 對象,由 string 類型的鍵索引,關聯的值則 int 型。
map 的構造函數
map<k, v> m; |
創建一個名為 m 的空 map 對象, 其鍵和值的類型分別為 k 和 v |
map<k, v>m(m2); |
創建 m2 的副本 m,m 與 m2 必須有相同的鍵類型和值類型 |
map<k, v>m(b, e); |
創建 map 類型的對象 m, 存儲迭代器 b 和 e 標記的范圍內所有元素的副本。元素的類型必須能轉換為 pair<const k, v> |
鍵類型的約束
在使用關聯容器時, 它的鍵不但有一個類型, 而且還有一個相關的比較函數。默認情況下,標准庫使用鍵類型定義的 < 操作符來實現鍵(key type)的比較。
所用的比較函數必須在鍵類型上定義嚴格弱排序(strict weak ordering)。當用於一個鍵與自身的比較時,肯定會導致 false 結果。此外,在比較兩個鍵時,不能出現相互“小於”的情況,而且,如果 k1“小於”k2,k2“小於”k3,則 k1 必然“小
於”k3。對於兩個鍵,如果它們相互之間都不存在“小於”關系,則容器將之視為相同的鍵。在實際應用中,鍵類型必須定義 < 操作符,而且該操作符應能“正確地工作”,這一點很重要,至於是否支持其他的關系或相等運算,則不作要求。
map 定義的類型
map<K,V>::key_type |
在 map 容器中,用做索引的鍵的類型 |
map<K,V>::mapped_type |
在 map 容器中,鍵所關聯的值的類型 |
map<K,V>::value_type |
一個 pair 類型,它的 first 元素具有 const map<K,V>::key_type 類型,而 second 元素則為 map<K,V>::mapped_type 類型 |
map 對象的元素是鍵-值對,也即每個元素包含兩個部分:鍵以及由鍵關聯的值。
value_type 是存儲元素的鍵以及值的 pair 類型,而且鍵為 const。例如,word_count 數組的 value_type 為 pair<const string,int> 類型。
map 迭代器進行解引用將產生 pair 類型的對象
對迭代器進行解引用時,將獲得一個引用,指向容器中一個 value_type 類型的值。對於 map 容器,其 value_type 是 pair 類型:
// get an iterator to an element in word_count
map<string, int>::iterator map_it = word_count.begin(); // *map_it is a reference to a pair<const string, int> object
cout << map_it->first; // prints the key for this element
cout << " " << map_it->second; // prints the value of the element
map_it->first = "new key"; // error: key is const
++map_it->second; // ok: we can change value through an ierator
對迭代器進行解引用將獲得一個 pair 對象,它的 first 成員存放鍵,為const,而 second 成員則存放值。
map 容器額外定義的類型別名(typedef)
map 類額外定義了兩種類型:key_type 和 mapped_type,以獲得鍵或值的類型。對於 word_count,其 key_type 是 string 類型,而 mapped_type 則是int 型。可使用作用域操作符(scopeoperator)來獲取類型成員,如 map<string, int>::key_type。
給 map 添加元素
該項工作可使用 insert 成員實現;或者,先用下標操作符獲取元素,然后給獲取的元素賦值。
使用下標訪問 map 對象
如下編寫程序時:
map <string, int> word_count; // empty map // insert default initialzed element with key Anna; then assign 1 to its value
word_count["Anna"] = 1;
將發生以下事情:
1. 在 word_count 中查找鍵為 Anna 的元素,沒有找到。
2. 將一個新的鍵-值對插入到 word_count 中。它的鍵是 const string 類型的對象, 保存 Anna。 而它的值則采用值初始化, 這就意味着在本例中值為 0。
3. 將這個新的鍵-值對插入到 word_count 中。
4. 讀取新插入的元素,並將它的值賦為 1。
如同其他下標操作符一樣,map 的下標也使用索引(其實就是鍵)來獲取該鍵所關聯的值。如果該鍵已在容器中,則 map 的下標運算與 vector 的下標運算行為相同:返回該鍵所關聯的值。只有在所查找的鍵不存在時,map 容器才為該鍵創建一個新的元素,並將它插入到此 map 對象中。此時,所關聯的值采用值初始化: 類類型的元素用默認構造函數初始化, 而內置類型的元素初始化為 0。
下標操作符返回值的使用
通常來說,下標操作符返回左值。它返回的左值是特定鍵所關聯的值。可如
下讀或寫元素:
cout << word_count["Anna"]; // fetch element indexed by Anna; prints 1
++word_count["Anna"]; // fetch the element and add one to it
cout << word_count["Anna"]; // fetch the element and print it; prints 2
有別於 vector 或 string 類型,map 下標操作符返回的類型與對 map 迭代器進行解引用獲得的類型不相同。map 迭代器返回 value_type 類型的值——包含 const key_type 和mapped_type 類型成員的 pair 對象;下標操作符則返回一個 mapped_type 類型的值。
下標行為的編程意義
對於 map 容器,如果下標所表示的鍵在容器中不存在,則添加新元素,這一特性可使程序驚人地簡練:
// count number of times each word occurs in the input
map<string, int> word_count; // empty map from string to int
string word; while (cin >> word) ++word_count[word];
這段程序創建一個 map 對象,用來記錄每個單詞出現的次數。while 循環每次從標准輸入讀取一個單詞。如果這是一個新的單詞,則在 word_count 中添加以該單詞為索引的新元素。如果讀入的單詞已在 map 對象中,則將它所對應的值加 1。
其中最有趣的是,在單詞第一次出現時,會在 word_count 中創建並插入一個以該單詞為索引的新元素,同時將它的值初始化為 0。然后其值立即加 1,所以每次在 map 中添加新元素時,所統計的出現次數正好從 1 開始。
map::insert 的使用
m.insert(e) |
e 是一個用在 m 上的 value_type 類型的值。如果鍵(e.first)不在 m 中,則插入一個值為 e.second 的新元素;如果該鍵在 m 中已存在,則保持 m 不變。該函數返回一個pair 類型對象,包含指向鍵為 e.first 的元素的 map 迭代器,以及一個 bool 類型的對象,表示是否插入了該元素 |
m.insert(beg,end) |
beg 和 end 是標記元素范圍的迭代器,其中的元素必須為m.value_type 類型的鍵-值對。對於該范圍內的所有元素,如果它的鍵在 m 中不存在, 則將該鍵及其關聯的值插入到 m。返回 void 類型 |
m.insert(iter,e) |
e 是一個用在 m 上的 value_type 類型的值。如果鍵(e.first)不在 m 中,則創建新元素,並以迭代器 iter 為起點搜索新元素存儲的位置。返回一個迭代器,指向 m 中具有給定鍵的元素 |
map 容器的 insert 成員與順序容器的類似,但有一點要注意:必須考慮鍵的作用。鍵影響了實參的類型:插入單個元素的 insert 版本使用鍵-值 pair類型的參數。類似地,對於參數為一對迭代器的版本,迭代器必須指向鍵-值pair 類型的元素。另一個差別則是:map 容器的接受單個值的 insert 版本的返回類型。本節的后續部分將詳細闡述這一特性。
以 insert 代替下標運算
// if Anna not already in word_count, inserts new element with value 1
word_count.insert(map<string, int>::value_type("Anna", 1));
這個 insert 函數版本的實參:
map<string, int>::value_type(anna, 1)
是一個新創建的 pair 對象,將直接插入到 map 容器中。謹記 value_type是 pair<const K, V> 類型的同義詞,K 為鍵類型,而 V 是鍵所關聯的值的類型。insert 的實參創建了一個適當的 pair 類型新對象,該對象將插入到 map容器。在添加新 map 元素時,使用 insert 成員可避免使用下標操作符所帶來的副作用:不必要的初始化。
傳遞給 insert 的實參相當笨拙。可用兩種方法簡化:使用 make_pair:
word_count.insert(make_pair("Anna", 1));
或使用 typedef
typedef map<string,int>::value_type valType; word_count.insert(valType("Anna", 1));
檢測 insert 的返回值
map 對象中一個給定鍵只對應一個元素。如果試圖插入的元素所對應的鍵已在容器中,則 insert 將不做任何操作。含有一個或一對迭代器形參的 insert函數版本並不說明是否有或有多少個元素插入到容器中。
但是,帶有一個鍵-值 pair 形參的 insert 版本將返回一個值:包含一個迭代器和一個 bool 值的 pair 對象,其中迭代器指向 map 中具有相應鍵的元素,而 bool 值則表示是否插入了該元素。如果該鍵已在容器中,則其關聯的值保持不變,返回的 bool 值為 true。在這兩種情況下,迭代器都將指向具有給定鍵的元素。下面是使用 insert 重寫的單詞統計程序:
// count number of times each word occurs in the input
map<string, int> word_count; // empty map from string to int
string word; while (cin >> word) { // inserts element with key equal to word and value 1; // if word already in word_count, insert does nothing
pair<map<string, int>::iterator, bool> ret = word_count.insert(make_pair(word, 1)); if (!ret.second) // word already in word_count
++ret.first->second; // increment counter
}
對於每個單詞,都嘗試 insert 它,並將它的值賦 1。if 語句檢測 insert函數返回值中的 bool 值。 如果該值為 false, 則表示沒有做插入操作, 按 word索引的元素已在 word_count 中存在。此時,將該元素所關聯的值加 1。
ret 的定義和自增運算可能比較難解釋:
pair<map<string, int>::iterator, bool> ret =word_count.insert(make_pair(word, 1));
首先,應該很容易看出我們定義的是一個 pair 對象,它的 second 成員為bool 類型。而它的 first 成員是 map<string, int> 容器所定義的迭代器類型。
根據操作符的優先級次序,可如下從添加圓括號開始理解自增操作:
++((ret.first)->second); // equivalent expression
下面對這個表達式一步步地展開解釋:
ret 存儲 insert 函數返回的 pair 對象。該 pair 的 first 成員是一個 map 迭代器,指向插入的鍵。
ret.first 從 insert 返回的 pair 對象中獲取 map 迭代器。
ret.first->second 對該迭代器進行解引用, 獲得一個 value_type 類型的對象。這個對象同樣是 pair 類型的,它的 second 成員即為我們所添加的元素的值部分。
++ret.first->second 實現該值的自增運算。
查找並讀取 map 中的元素
下標操作符給出了讀取一個值的最簡單方法:
map<string,int> word_count; int occurs = word_count["foobar"];
但是,使用下標存在一個很危險的副作用:如果該鍵不在 map 容器中,那么下標操作會插入一個具有該鍵的新元素。但若是我們只想知道某元素是否存在,而當該元素不存在時,並不想做做插入運算。對於這種應用,則不能使用下標操作符來判斷元素是否存在。
map 容器提供了兩個操作:count 和 find,用於檢查某個鍵是否存在而不會插入該鍵。
m.count(k) |
返回 m 中 k 的出現次數 |
m.find(k) |
如果 m 容器中存在按 k 索引的元素,則返回指向該元素的迭代器。如果不存在,則返回超出末端迭代器 |
使用 count 檢查 map 對象中某鍵是否存在
對於 map 對象,count 成員的返回值只能是 0 或 1。map 容器只允許一個鍵對應一個實例, 所以 count 可有效地表明一個鍵是否存在。
如果返回值非 0,則可以使用下標操作符來獲取該鍵所關聯的值,而不必擔心這樣做會在 map 中插入新元素:
int occurs = 0; if (word_count.count("foobar")) occurs = word_count["foobar"];
當然,在執行 count 后再使用下標操作符,實際上是對元素作了兩次查找。如果希望當元素存在時就使用它,則應該用 find 操作。
讀取元素而不插入該元素
find 操作返回指向元素的迭代器,如果元素不存在,則返回 end 迭代器:
int occurs = 0; map<string,int>::iterator it = word_count.find("foobar"); if (it != word_count.end()) occurs = it->second;
如果希望當具有指定鍵的元素存在時,就獲取該元素的引用,否則就不在容器中創建新元素,那么應該使用 find。
從 map 對象中刪除元素
m.erase(k) |
刪除 m 中鍵為 k 的元素。返回 size_type 類型的值,表示刪除的元素個數 |
m.erase(p) |
從 m 中刪除迭代器 p 所指向的元素。p 必須指向 m 中確實存在的元素,而且不能等於 m.end()。返回 void |
m.erase(b,e) |
從 m 中刪除一段范圍內的元素, 該范圍由迭代器對 b 和 e 標記。b 和 e 必須標記 m 中的一段有效范圍: 即 b 和 e 都必須指向 m中的元素或最后一個元素的下一個位置。而且,b 和 e 要么相等(此時刪除的范圍為空),要么 b 所指向的元素必須出現在 e 所指向的元素之前。返回 void 類型 |
與順序容器一樣,可向 erase 傳遞一個或一對迭代器,來刪除單個元素或一段范圍內的元素。其刪除功能類似於順序容器,但有一點不同:map 容器的 erase 操作返回 void,而順序容器的 erase 操作則返回一個迭代器,指向被刪除元素后面的元素。
除此之外,map 類型還提供了一種額外的 erase 操作,其參數是 key_type類型的值,如果擁有該鍵的元素存在,則刪除該元素。對於單詞統計程序,可使用這個版本的 erase 函數來刪除 word_count 中指定的單詞,然后輸出被刪除的單詞:
// erase of a key returns number of elements removed
if (word_count.erase(removal_word)) cout << "ok: " << removal_word << " removed\n"; else cout << "oops: " << removal_word << " not found!\n";
erase 函數返回被刪除元素的個數。對於 map 容器,該值必然是 0 或 1。如果返回 0,則表示欲刪除的元素在 map 不存在。
map 對象的迭代遍歷
與其他容器一樣,map 同樣提供 begin 和 end 運算,以生成用於遍歷整個容器的迭代器。例如,可如下將 map 容器 word_count 的內容輸出:
// get iterator positioned on the first element
map<string, int>::const_iterator map_it = word_count.begin(); // for each element in the map
while (map_it != word_count.end()) { // print the element key, value pairs
cout << map_it->first << " occurs "
<< map_it->second << " times" << endl; ++map_it; // increment iterator to denote the next element
}
這個單詞統計程序依據字典順序輸出單詞。在使用迭代器遍歷map 容器時,迭代器指向的元素按鍵的升序排列。
使用實例
“單詞轉換” map 對象:
給出一個 string 對象,把它轉換為另一個 string 對象。本程序的輸入是兩個文件。第一個文件包括了若干單詞對,每對的第一個單詞將出現在輸入的字符串中, 而第二個單詞則是用於輸出。本質上,這個文件提供的是單詞轉換的集合——在遇到第一個單詞時, 應該將之替換為第二個單詞。第二個文件則提供了需要轉換的文本。如果單詞轉換文件的內容是:
'em them
cuz because gratz grateful i I nah no pos supposed sez said tanx thanks wuz was
而要轉換的文本是:
nah i sez tanx cuz i wuz pos to
not cuz i wuz gratz
則程序將產生如下輸出結果:
no I said thanks because I was supposed to
not because I was grateful
程序使用 bool 值 firstword 判斷是否需要輸出空格。如果當前處理的是這一行的第一個單詞,則無須輸出空格。
/* * A program to transform words. * Takes two arguments: The first is name of the word transformation file * The second is name of the input to transform */
int main(int argc, char **argv) { // map to hold the word transformation pairs: // key is the word to look for in the input; value is word to use in the output
map<string, string> trans_map; string key, value; if (argc != 3) throw runtime_error("wrong number of arguments"); // open transformation file and check that open succeeded
ifstream map_file; if (!open_file(map_file, argv[1])) throw runtime_error("no transformation file"); // read the transformation map and build the map
while (map_file >> key >> value) trans_map.insert(make_pair(key, value)); // ok, now we're ready to do the transformations // open the input file and check that the open succeeded
ifstream input; if (!open_file(input, argv[2])) throw runtime_error("no input file"); string line; // hold each line from the input // read the text to transform it a line at a time
while (getline(input, line)) { istringstream stream(line); // read the line a word at a
time string word; bool firstword = true; // controls whether a space is printed
while (stream >> word) { // ok: the actual mapwork, this part is the heart of the program
map<string, string>::const_iterator map_it = trans_map.find(word); // if this word is in the transformation map
if (map_it != trans_map.end()) // replace it by the transformation value in the map
word = map_it->second; if (firstword) firstword = false; else cout << " "; // print space between words
cout << word; } cout << endl; // done with this line of input
} return 0; }