C++ std::map 用法與範例

本篇將介紹如何使用 C++ std map 以及用法,C++ std::map 是一個關聯式容器,關聯式容器把鍵值和一個元素連繫起來,並使用該鍵值來尋找元素、插入元素和刪除元素等操作。

map 是有排序關聯式容器,即 map 容器中所有的元素都會根據元素對應的鍵值來排序,而鍵值 key 是唯一值,並不會出現同樣的鍵值 key,也就是說假設已經有一個 鍵值 key 存在 map 裡,當同樣的鍵值 key 再 insert 資料時,新的資料就會覆蓋掉原本 key 的資料。

map 的實作方式通常是用紅黑樹(red-black tree)實作的,這樣它可以保證可以在O(log n)時間內完成搜尋、插入、刪除,n為元素的數目。

以下內容將分為這幾部分,

  • map 常用功能
  • map 初始化
  • map 容器插入元素與存取元素
  • map 容器的迴圈遍歷
  • 使用 string 當 key 鍵值, int 當 value 資料的 map 範例
  • 使用 string 當 key 鍵值, 自定義類別或自定義結構當 value 資料的 map 範例
  • 刪除 map 指定的元素
  • 清空 map 容器
  • 判斷 map 容器是否為空

要使用 map 容器的話,需要引入的標頭檔<map>

map 常用功能

C++ map 是一種關聯式容器,包含「key鍵值/value資料」成對關係
元素存取
operator[]:存取指定的[i]元素的資料
迭代器
begin():回傳指向map頭部元素的迭代器
end():回傳指向map末尾的迭代器
rbegin():回傳一個指向map尾部的反向迭代器
rend():回傳一個指向map頭部的反向迭代器
容量
empty():檢查容器是否為空,空則回傳true
size():回傳元素數量
max_size():回傳可以容納的最大元素個數
修改器
clear():刪除所有元素
insert():插入元素
erase():刪除一個元素
swap():交換兩個map
查找
count():回傳指定元素出現的次數
find():查找一個元素

map 初始化

接下來說說怎麼初始化 c++ map 容器吧!
先以 int 當 key, string 當 value 的 map 為範例,
以一個班級的學生編號為例,一個編號會對應到一個學生,這邊先以學生姓名作示範,
所以會寫成std::map<int, std::string> studentMap這樣
std::map 宣告時要宣告兩個變數類型,
map.first:第一個稱為(key)鍵值,在 map 裡面,(key)鍵值不會重複
map.second:第二個稱為(key)鍵值對應的數值(value)
map 容器初始化的寫法如下,

1
2
3
4
std::map<int, std::string> studentMap;
studentMap.insert(std::pair<int, std::string>(1, "Tom"));
studentMap.insert(std::pair<int, std::string>(7, "Jack"));
studentMap.insert(std::pair<int, std::string>(15, "John"));

或者像陣列的方式一樣

1
2
3
4
std::map<int, std::string> studentMap;
studentMap[1] = "Tom";
studentMap[7] = "Jack";
studentMap[15] = "John";

或者這樣初始化更專業一點

1
2
3
4
5
std::map<int, std::string> studentMap = {
{1, "Tom"},
{2, "Jack"},
{3, "John"}
};

map 容器插入元素與存取元素

剛剛在初始化部分已經有示範過了,map 容器插入元素有兩種寫法,
第一種是使用中括號 [] 的方式來插入元素,很像陣列的寫法,例如:map[key] = value,如果該 key 值已經存在則 value 會被更新成新的數值,範例如下,

1
2
3
4
std::map<int, std::string> studentMap;
studentMap[1] = "Tom";
...
studentMap[1] = "John";

另一種是使用 map.insert() 成員函式來插入元素,
那這個 map.insert() 方式跟上面中括號 [] 的方式有什麼不同呢?

差別在於如果 key 值已經存在的話,使用中括號 [] 的方式會將新資料覆蓋舊資料,
而使用 map.insert() 的方式會回傳插入的結果,該 key 值存在的話會回傳失敗的結果,
檢查 map.insert() 的插入結果方式如下,

std-map.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// g++ std-map.cpp -o a.out -std=c++11
#include <iostream>
#include <string>
#include <map>

int main() {
std::map<int, std::string> studentMap;
studentMap.insert(std::pair<int, std::string>(1, "Tom"));

std::pair<std::map<int, std::string>::iterator, bool> retPair;
retPair = studentMap.insert(std::pair<int, std::string>(1, "Tom"));

if (retPair.second == true)
std::cout << "Insert Successfully\n";
else
std::cout << "Insert Failure\n";

return 0;
}

要取得某 key 鍵值元素的話可以這樣寫,或者看下節遍歷整個 map 容器

1
std::cout << "name: " << studentMap[1] << "\n";

map 容器的迴圈遍歷

迴圈遍歷 map 容器的方式有幾種,以下先介紹使用 range-based for loop 來遍歷 map 容器,
這邊故意將 id 不按順序初始化或者插入,先初始化 132 key 鍵值的元素,
之後再插入 54 key 鍵值的元素,然後我們再來觀察看看是不是 map 會將其排序,

std-map2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// g++ std-map2.cpp -o a.out -std=c++11
#include <iostream>
#include <string>
#include <map>

int main() {
std::map<int, std::string> studentMap = {
{1, "Tom"},
{3, "John"},
{2, "Jack"}
};
studentMap[5] = "Tiffany";
studentMap[4] = "Ann";

for (const auto& s : studentMap) {
std::cout << "id: " << s.first << ", name: " << s.second << "\n";
}
return 0;
}

輸出內容如下,從這個輸出結果發現是 key 鍵值由小到大排列,所以 map 容器裡面真的是會幫你排序的,
在插入元素的同時會根據鍵值來進行排序,

1
2
3
4
5
id: 1, name: Tom
id: 2, name: Jack
id: 3, name: John
id: 4, name: Ann
id: 5, name: Tiffany

使用前向迭代器,輸出結果跟上列相同,

1
2
3
4
5
for (std::map<int, std::string>::iterator it = studentMap.begin(); it != studentMap.end(); it++) {
// or
// for (auto it = studentMap.begin(); it != studentMap.end(); it++) {
std::cout << "id: " << (*it).first << ", name: " << (*it).second << "\n";
}

使用反向迭代器的例子,如果嫌 iterator 迭代器名稱太長的話可以善用 auto 關鍵字讓編譯器去推導該變數類型,

1
2
3
4
5
// for (std::map<int, std::string>::reverse_iterator it = studentMap.rbegin(); it != studentMap.rend(); it++) {
// or
for (auto it = studentMap.rbegin(); it != studentMap.rend(); it++) {
std::cout << "id: " << (*it).first << ", name: " << (*it).second << "\n";
}

反向迭代器的輸出結果如下,反著印出來,

1
2
3
4
5
id: 5, name: Tiffany
id: 4, name: Ann
id: 3, name: John
id: 2, name: Jack
id: 1, name: Tom

使用 string 當 key 鍵值, int 當 value 資料的 map 範例

這個範例跟之前的範例相反,我們試著用 string 當作 key 鍵值,int 當 value,看看是不是也可以這樣使用,
所以會寫成 std::map<std::string, int> 這樣,完整範例如下,

std-map3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// g++ std-map3.cpp -o a.out -std=c++11
#include <iostream>
#include <string>
#include <map>

int main() {
std::map<std::string, int> map = {
{"Tom", 1},
{"Jack", 2},
{"Ann", 4}
};

for (const auto& n : map) {
std::cout << "key: " << n.first << " value: " << n.second << "\n";
}

map["Tiffany"] = 5;
map["John"] = 3;

std::cout << map["John"] << "\n";
std::cout << map["Tiffany"] << "\n";

for (const auto& n : map) {
std::cout << "key: " << n.first << " value: " << n.second << "\n";
}

return 0;
}

輸出內容如下,可以發現印出來的順序是按照學生姓名的順序,

1
2
3
4
5
6
7
8
9
10
key: Ann value: 4
key: Jack value: 2
key: Tom value: 1
3
5
key: Ann value: 4
key: Jack value: 2
key: John value: 3
key: Tiffany value: 5
key: Tom value: 1

使用 string 當 key 鍵值, 自定義類別或自定義結構當 value 資料的 map 範例

如果要在 value 欄位放入自定義 struct 或 自定義 class 的話,可以參考下面這個範例,
那個宣告就會是 std::map<std::string, Class/Struct> 這樣子寫,

std-map4.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// g++ std-map4.cpp -o a.out -std=c++11
#include <iostream>
#include <string>
#include <map>

struct Student {
int id;
std::string name;
int age;
};

int main() {
std::map<std::string, Student> studentMap;
studentMap["Tom"] = {1, "Tom", 18};
studentMap["Ann"] = {4, "Ann", 20};
studentMap["Jack"] = {2, "Jack", 16};

for (const auto& m : studentMap) {
std::cout << "name: " << m.first << " id: " << m.second.id << " age: " << m.second.age << "\n";
}

return 0;
}

輸出內容如下:

1
2
3
name: Ann id: 4 age: 20
name: Jack id: 2 age: 16
name: Tom id: 1 age: 18

刪除 map 指定的元素

刪除 map 指定的元素要使用 erase()

std-map5.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// g++ std-map5.cpp -o a.out -std=c++11
#include <iostream>
#include <string>
#include <map>

int main() {
std::map<int, std::string> studentMap;
studentMap[1] = "Tom";
studentMap[7] = "Jack";
studentMap[15] = "John";

studentMap.erase(1);
for (const auto& m : studentMap) {
std::cout << m.first << " " << m.second << "\n";
}

return 0;
}

結果如下,

1
2
7 Jack
15 John

那如果 map 刪除不存在的元素會發生什麼事呢?

std-map6.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// g++ std-map6.cpp -o a.out -std=c++11
#include <iostream>
#include <string>
#include <map>

int main() {
std::map<int, std::string> studentMap;
studentMap[1] = "Tom";
studentMap[7] = "Jack";
studentMap[15] = "John";

auto ret = studentMap.erase(1);
std::cout << ret << "\n";
for (const auto& m : studentMap) {
std::cout << m.first << " " << m.second << "\n";
}

ret = studentMap.erase(2);
std::cout << ret << "\n";
for (const auto& m : studentMap) {
std::cout << m.first << " " << m.second << "\n";
}

return 0;
}

map 刪除不存在的元素並不會造成什麼 crash 這種嚴重問題,他反而會回傳一個數量告訴你它刪除了多少個元素,以這個例子來說 erase(1) 是刪除了 1 個元素,erase(2) 是刪除了 0 個元素,結果如下,

1
2
3
4
5
6
1
7 Jack
15 John
0
7 Jack
15 John

map erase() 刪除元素還有另外兩種用法,有興趣的可以看這一篇

清空 map 容器

要清空 map 容器的的話,要使用 clear()

1
2
3
4
5
6
std::map<int, std::string> studentMap;
studentMap.insert(std::pair<int, std::string>(1, "Tom"));
studentMap.insert(std::pair<int, std::string>(7, "Jack"));
studentMap.insert(std::pair<int, std::string>(15, "John"));

studentMap.clear();

判斷 map 容器是否為空

要判斷 map 是否為空或是裡面有沒有元素的話,可以用 empty(),寫法如下,

1
2
3
4
5
6
7
8
std::map<int, std::string> studentMap;
studentMap.clear();

if (studentMap.empty()) {
std::cout << "empty\n";
} else {
std::cout << "not empty, size is "<< studentMap.size() <<"\n";
}

參考
std::map - cppreference.com
https://en.cppreference.com/w/cpp/container/map
map - C++ Reference
http://www.cplusplus.com/reference/map/map/
Map in C++ Standard Template Library (STL) - GeeksforGeeks
https://www.geeksforgeeks.org/map-associative-containers-the-c-standard-template-library-stl//
C/C++ - Map (STL) 用法與心得完全攻略 | Mr. Opengate
https://mropengate.blogspot.com/2015/12/cc-map-stl.html
[教學]C++ Map(STL)詳細用法 @ 一個小小工程師的心情抒發天地
http://dangerlover9403.pixnet.net/blog/post/216833943
C++ STL map 的小筆記 @ 伊卡洛斯之翼
http://kamory0931.pixnet.net/blog/post/119251820
C++中的STL中map用法详解 - Boblim - 博客园
https://www.cnblogs.com/fnlingnzb-learner/p/5833051.html
程式扎記: [C++ 範例代碼] 使用 STL 的 map 操作範例
http://puremonkey2010.blogspot.com/2010/08/c-stl-map.html

其它相關文章推薦
如果你想學習 C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
std::multimap 用法與範例
std::unordered_map 用法與範例
std::vector 用法與範例
std::deque 介紹與用法
std::queue 用法與範例