C++ Primer 第11章:关联容器
·
C++ Primer 第11章:关联容器
11.1 使用关联容器
11.1.1 关联容器概述
关联容器:元素按键(key)存储和访问,而非位置
┌──────────────────┬──────────────┬──────────────────────────────────┐
│ 容器类型 │ 底层结构 │ 特点 │
├──────────────────┼──────────────┼──────────────────────────────────┤
│ map │ 红黑树 │ 键值对,键唯一,按键有序 │
│ set │ 红黑树 │ 只有键,键唯一,有序 │
│ multimap │ 红黑树 │ 键值对,键可重复,有序 │
│ multiset │ 红黑树 │ 只有键,键可重复,有序 │
│ unordered_map │ 哈希表 │ 键值对,键唯一,无序,O(1)查找 │
│ unordered_set │ 哈希表 │ 只有键,键唯一,无序,O(1)查找 │
│ unordered_multimap│ 哈希表 │ 键值对,键可重复,无序 │
│ unordered_multiset│ 哈希表 │ 只有键,键可重复,无序 │
└──────────────────┴──────────────┴──────────────────────────────────┘
头文件:
map, multimap → <map>
set, multiset → <set>
unordered_map, ... → <unordered_map>
unordered_set, ... → <unordered_set>
11.1.2 map 和 set 的基本使用
// map_set_basic.cpp -- map和set基本使用
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <vector>
int main()
{
using namespace std;
// ===== map:统计单词出现次数 =====
map<string, int> wordCount;
vector<string> words = {"the", "quick", "red", "fox",
"jumps", "over", "the", "slow",
"red", "turtle", "the"};
for (const auto& w : words)
++wordCount[w]; // 如果键不存在,自动创建并初始化为0
cout << "单词频率:" << endl;
for (const auto& [word, count] : wordCount) // C++17结构化绑定
cout << " " << word << ": " << count << endl;
// ===== set:存储不重复的单词 =====
set<string> uniqueWords(words.begin(), words.end());
cout << "\n不重复单词(" << uniqueWords.size() << "个):";
for (const auto& w : uniqueWords)
cout << w << " ";
cout << endl;
// ===== 关联容器的有序性 =====
// map 和 set 按键的字典序自动排序
map<int, string> m = {{3, "three"}, {1, "one"}, {4, "four"}, {2, "two"}};
cout << "\nmap按键排序:" << endl;
for (const auto& [k, v] : m)
cout << " " << k << " → " << v << endl;
return 0;
}
11.2 关联容器概述
11.2.1 定义关联容器
// define_containers.cpp -- 定义关联容器
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <vector>
int main()
{
using namespace std;
// ===== map 的初始化 =====
map<string, int> m1; // 空map
map<string, int> m2 = { // 列表初始化
{"Alice", 90},
{"Bob", 85},
{"Carol", 92}
};
map<string, int> m3(m2); // 拷贝构造
// ===== set 的初始化 =====
set<int> s1; // 空set
set<int> s2 = {3, 1, 4, 1, 5, 9, 2}; // 列表初始化(自动去重排序)
set<int> s3(s2.begin(), s2.end()); // 范围构造
cout << "s2(自动去重排序):";
for (int x : s2) cout << x << " ";
cout << endl; // 1 2 3 4 5 9
// ===== 自定义比较函数 =====
// 默认使用 < 运算符,可以自定义
auto cmp = [](const string& a, const string& b) {
return a.size() < b.size(); // 按字符串长度排序
};
// 注意:自定义比较必须是严格弱序
map<string, int, decltype(cmp)> byLength(cmp);
byLength["hello"] = 1;
byLength["hi"] = 2;
byLength["hey"] = 3;
byLength["world"] = 4;
cout << "\n按长度排序的map:" << endl;
for (const auto& [k, v] : byLength)
cout << " " << k << "(" << k.size() << "): " << v << endl;
return 0;
}
11.2.2 pair 类型
// pair_demo.cpp -- pair类型
#include <iostream>
#include <map>
#include <string>
#include <utility> // pair 定义在这里
int main()
{
using namespace std;
// ===== pair 的创建 =====
pair<string, int> p1("Alice", 90);
pair<string, int> p2 = {"Bob", 85};
auto p3 = make_pair("Carol", 92); // 自动推断类型
cout << "p1: " << p1.first << " = " << p1.second << endl;
cout << "p2: " << p2.first << " = " << p2.second << endl;
cout << "p3: " << p3.first << " = " << p3.second << endl;
// ===== pair 的比较 =====
// 先比较 first,first 相等再比较 second
pair<int, int> a = {1, 2};
pair<int, int> b = {1, 3};
pair<int, int> c = {2, 1};
cout << boolalpha;
cout << "a < b: " << (a < b) << endl; // true(first相等,比second)
cout << "a < c: " << (a < c) << endl; // true(first不同,比first)
// ===== map 的元素类型是 pair =====
map<string, int> scores = {{"Alice", 90}, {"Bob", 85}};
// 遍历时,每个元素是 pair<const string, int>
for (const pair<const string, int>& p : scores)
cout << p.first << ": " << p.second << endl;
// 使用 auto 更简洁
for (const auto& [name, score] : scores)
cout << name << ": " << score << endl;
// ===== 函数返回 pair =====
auto minMax = [](const vector<int>& v) -> pair<int, int> {
return {*min_element(v.begin(), v.end()),
*max_element(v.begin(), v.end())};
};
vector<int> nums = {3, 1, 4, 1, 5, 9, 2, 6};
auto [minVal, maxVal] = minMax(nums);
cout << "min=" << minVal << " max=" << maxVal << endl;
return 0;
}
11.3 关联容器操作
11.3.1 关联容器的类型别名
// container_types.cpp -- 关联容器类型别名
#include <iostream>
#include <map>
#include <set>
#include <string>
int main()
{
using namespace std;
// map 的类型别名
map<string, int> m;
// key_type:键类型
map<string, int>::key_type k = "hello";
// mapped_type:值类型(只有map有)
map<string, int>::mapped_type v = 42;
// value_type:元素类型(pair<const key_type, mapped_type>)
map<string, int>::value_type elem = {"world", 100};
cout << "key: " << k << endl;
cout << "value: " << v << endl;
cout << "elem: " << elem.first << "=" << elem.second << endl;
// set 的类型别名
set<int> s;
// set 的 key_type 和 value_type 相同
set<int>::key_type sk = 42;
set<int>::value_type sv = 42;
return 0;
}
11.3.2 添加元素
// add_elements.cpp -- 关联容器添加元素
#include <iostream>
#include <map>
#include <set>
#include <string>
int main()
{
using namespace std;
// ===== set 的插入 =====
set<int> s;
// insert 单个元素,返回 pair<iterator, bool>
auto [it1, ok1] = s.insert(42);
auto [it2, ok2] = s.insert(42); // 重复插入
cout << boolalpha;
cout << "第一次插入42:" << ok1 << endl; // true
cout << "第二次插入42:" << ok2 << endl; // false(已存在)
// 插入范围
s.insert({1, 2, 3, 4, 5});
cout << "set:";
for (int x : s) cout << x << " ";
cout << endl;
// ===== map 的插入 =====
map<string, int> m;
// 方式1:insert + pair
m.insert({"Alice", 90});
m.insert(make_pair("Bob", 85));
m.insert(pair<string, int>("Carol", 92));
// 方式2:下标运算符(如果键不存在,创建并值初始化)
m["David"] = 88;
m["Eve"]; // 创建键"Eve",值初始化为0
// 方式3:emplace(直接构造,更高效)
m.emplace("Frank", 95);
cout << "\nmap:" << endl;
for (const auto& [k, v] : m)
cout << " " << k << ": " << v << endl;
// ===== insert 的返回值 =====
auto result = m.insert({"Alice", 100}); // Alice已存在
cout << "\n插入Alice(已存在):" << result.second << endl; // false
cout << "Alice的值:" << result.first->second << endl; // 90(未改变)
return 0;
}
11.3.3 删除元素
// delete_elements.cpp -- 关联容器删除元素
#include <iostream>
#include <map>
#include <set>
#include <string>
int main()
{
using namespace std;
map<string, int> m = {
{"Alice", 90}, {"Bob", 85}, {"Carol", 92},
{"David", 88}, {"Eve", 75}
};
// ===== erase(key):按键删除,返回删除的元素数量 =====
size_t n = m.erase("Bob");
cout << "删除Bob:" << n << " 个" << endl; // 1
n = m.erase("Unknown");
cout << "删除Unknown:" << n << " 个" << endl; // 0
// ===== erase(iterator):按迭代器删除 =====
auto it = m.find("Carol");
if (it != m.end())
m.erase(it);
// ===== erase(first, last):删除范围 =====
// 删除所有键在 [Alice, David) 之间的元素
auto first = m.lower_bound("Alice");
auto last = m.lower_bound("David");
m.erase(first, last);
cout << "剩余元素:" << endl;
for (const auto& [k, v] : m)
cout << " " << k << ": " << v << endl;
// ===== set 的删除 =====
set<int> s = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
s.erase(5); // 删除值为5的元素
s.erase(s.begin()); // 删除第一个元素
cout << "\nset:";
for (int x : s) cout << x << " ";
cout << endl;
return 0;
}
11.3.4 map 的下标操作
// map_subscript.cpp -- map的下标操作
#include <iostream>
#include <map>
#include <string>
int main()
{
using namespace std;
map<string, int> m;
// ===== 下标运算符 [] =====
// 如果键不存在,自动创建并值初始化
m["Alice"] = 90; // 创建键"Alice",赋值90
m["Bob"]; // 创建键"Bob",值初始化为0
cout << "m[\"Alice\"] = " << m["Alice"] << endl; // 90
cout << "m[\"Bob\"] = " << m["Bob"] << endl; // 0
// ⚠️ 下标操作会插入元素!
// 对 const map 不能使用下标
// const map<string, int> cm = m;
// cm["Alice"]; // ❌ 错误!
// ===== at() 方法(C++11)=====
// 如果键不存在,抛出 out_of_range 异常
try
{
cout << "m.at(\"Alice\") = " << m.at("Alice") << endl;
cout << "m.at(\"Unknown\") = " << m.at("Unknown") << endl;
}
catch (const out_of_range& e)
{
cout << "键不存在:" << e.what() << endl;
}
// ===== 下标 vs at() vs find() =====
// 下标:不存在时创建,适合写操作
// at():不存在时抛异常,适合读操作(需要键存在)
// find():不存在时返回end(),适合检查键是否存在
// 安全读取的推荐方式
auto it = m.find("Carol");
if (it != m.end())
cout << "Carol: " << it->second << endl;
else
cout << "Carol不存在" << endl;
return 0;
}
11.3.5 访问元素
// access_elements.cpp -- 关联容器访问元素
#include <iostream>
#include <map>
#include <set>
#include <string>
#include <multimap>
int main()
{
using namespace std;
map<string, int> m = {
{"Alice", 90}, {"Bob", 85}, {"Carol", 92}
};
// ===== find:查找键,返回迭代器 =====
auto it = m.find("Bob");
if (it != m.end())
cout << "找到Bob:" << it->second << endl;
// ===== count:统计键的出现次数 =====
// 对于 map/set,count 只返回 0 或 1
cout << "Alice存在:" << boolalpha << (m.count("Alice") > 0) << endl;
cout << "David存在:" << (m.count("David") > 0) << endl;
// ===== contains(C++20)=====
// cout << m.contains("Alice") << endl;
// ===== lower_bound / upper_bound / equal_range =====
// 这些函数对有序关联容器特别有用
map<int, string> scores = {
{60, "D"}, {70, "C"}, {80, "B"}, {90, "A"}
};
// lower_bound(k):第一个键 >= k 的迭代器
auto lb = scores.lower_bound(75);
cout << "\nlower_bound(75):" << lb->first << "→" << lb->second << endl;
// upper_bound(k):第一个键 > k 的迭代器
auto ub = scores.upper_bound(75);
cout << "upper_bound(75):" << ub->first << "→" << ub->second << endl;
// equal_range(k):返回 [lower_bound, upper_bound) 的 pair
auto range = scores.equal_range(80);
cout << "equal_range(80):";
for (auto it = range.first; it != range.second; ++it)
cout << it->first << "→" << it->second << " ";
cout << endl;
// ===== multimap 中查找所有相同键的元素 =====
multimap<string, string> authors = {
{"Barth", "Sot-Weed Factor"},
{"Barth", "Lost in the Funhouse"},
{"Alain", "Tipping Point"},
{"Barth", "Chimera"}
};
string author = "Barth";
cout << "\n" << author << "的作品:" << endl;
// 方式1:equal_range
auto [first, last] = authors.equal_range(author);
for (auto it = first; it != last; ++it)
cout << " " << it->second << endl;
// 方式2:lower_bound + upper_bound
auto beg = authors.lower_bound(author);
auto end = authors.upper_bound(author);
for (auto it = beg; it != end; ++it)
cout << " " << it->second << endl;
return 0;
}
11.4 无序容器
11.4.1 unordered_map 和 unordered_set
// unordered_containers.cpp -- 无序容器
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <vector>
int main()
{
using namespace std;
// ===== unordered_map:哈希表实现,O(1)平均查找 =====
unordered_map<string, int> umap;
// 操作与 map 基本相同
umap["Alice"] = 90;
umap["Bob"] = 85;
umap.insert({"Carol", 92});
umap.emplace("David", 88);
cout << "unordered_map(无序):" << endl;
for (const auto& [k, v] : umap)
cout << " " << k << ": " << v << endl;
// ===== unordered_set =====
unordered_set<int> uset = {3, 1, 4, 1, 5, 9, 2, 6};
cout << "\nunordered_set(无序,去重):";
for (int x : uset) cout << x << " ";
cout << endl;
// ===== 性能对比 =====
// map:O(log n) 查找,有序
// unordered_map:O(1) 平均查找,无序
// 大量数据时,unordered_map 通常更快
unordered_map<int, int> fastMap;
for (int i = 0; i < 1000000; i++)
fastMap[i] = i * i;
// O(1) 查找
auto it = fastMap.find(999999);
if (it != fastMap.end())
cout << "\n999999^2 = " << it->second << endl;
// ===== 桶操作 =====
cout << "\n桶信息:" << endl;
cout << "桶数量:" << umap.bucket_count() << endl;
cout << "负载因子:" << umap.load_factor() << endl;
cout << "最大负载因子:" << umap.max_load_factor() << endl;
return 0;
}
11.4.2 自定义哈希函数
// custom_hash.cpp -- 自定义哈希函数
#include <iostream>
#include <unordered_map>
#include <unordered_set>
#include <string>
// 自定义类型
struct Point
{
int x, y;
bool operator==(const Point& other) const
{
return x == other.x && y == other.y;
}
};
// 自定义哈希函数
struct PointHash
{
size_t operator()(const Point& p) const
{
// 组合两个哈希值
size_t h1 = hash<int>{}(p.x);
size_t h2 = hash<int>{}(p.y);
return h1 ^ (h2 << 1); // 简单的哈希组合
}
};
// 使用 lambda 作为哈希函数
auto strHash = [](const string& s) -> size_t {
size_t h = 0;
for (char c : s)
h = h * 31 + c;
return h;
};
auto strEqual = [](const string& a, const string& b) {
return a == b;
};
int main()
{
using namespace std;
// 使用自定义哈希的 unordered_set
unordered_set<Point, PointHash> points;
points.insert({1, 2});
points.insert({3, 4});
points.insert({1, 2}); // 重复,不插入
cout << "点集合大小:" << points.size() << endl; // 2
// 使用 lambda 哈希
unordered_map<string, int,
decltype(strHash),
decltype(strEqual)> customMap(0, strHash, strEqual);
customMap["hello"] = 1;
customMap["world"] = 2;
cout << "customMap[\"hello\"] = " << customMap["hello"] << endl;
return 0;
}
11.5 综合示例:电话簿系统
// phonebook.cpp -- 综合示例:电话簿系统
#include <iostream>
#include <map>
#include <set>
#include <unordered_map>
#include <string>
#include <vector>
#include <algorithm>
#include <iomanip>
#include <stdexcept>
class PhoneBook
{
private:
// 姓名 → 电话号码集合(一个人可以有多个号码)
map<string, set<string>> contacts_;
// 电话号码 → 姓名(反向查找)
unordered_map<string, string> phoneToName_;
// 验证电话号码格式(简化)
bool isValidPhone(const string& phone) const
{
if (phone.empty()) return false;
for (char c : phone)
if (!isdigit(c) && c != '-' && c != '+' && c != ' ')
return false;
return true;
}
public:
// 添加联系人
bool addContact(const string& name, const string& phone)
{
if (name.empty())
throw invalid_argument("姓名不能为空");
if (!isValidPhone(phone))
throw invalid_argument("无效的电话号码:" + phone);
// 检查号码是否已被其他人使用
auto it = phoneToName_.find(phone);
if (it != phoneToName_.end() && it->second != name)
{
cout << "警告:号码 " << phone
<< " 已被 " << it->second << " 使用" << endl;
return false;
}
contacts_[name].insert(phone);
phoneToName_[phone] = name;
return true;
}
// 删除联系人
bool removeContact(const string& name)
{
auto it = contacts_.find(name);
if (it == contacts_.end()) return false;
// 删除反向索引
for (const auto& phone : it->second)
phoneToName_.erase(phone);
contacts_.erase(it);
return true;
}
// 删除特定号码
bool removePhone(const string& name, const string& phone)
{
auto it = contacts_.find(name);
if (it == contacts_.end()) return false;
it->second.erase(phone);
phoneToName_.erase(phone);
// 如果没有号码了,删除联系人
if (it->second.empty())
contacts_.erase(it);
return true;
}
// 按姓名查找
const set<string>* findByName(const string& name) const
{
auto it = contacts_.find(name);
if (it == contacts_.end()) return nullptr;
return &it->second;
}
// 按号码查找
string findByPhone(const string& phone) const
{
auto it = phoneToName_.find(phone);
if (it == phoneToName_.end()) return "";
return it->second;
}
// 搜索(前缀匹配)
vector<string> search(const string& prefix) const
{
vector<string> results;
// lower_bound 找到第一个 >= prefix 的键
auto it = contacts_.lower_bound(prefix);
while (it != contacts_.end() &&
it->first.substr(0, prefix.size()) == prefix)
{
results.push_back(it->first);
++it;
}
return results;
}
// 显示所有联系人
void display() const
{
if (contacts_.empty())
{
cout << "电话簿为空" << endl;
return;
}
cout << "\n" << string(50, '=') << endl;
cout << left << setw(15) << "姓名"
<< "电话号码" << endl;
cout << string(50, '-') << endl;
for (const auto& [name, phones] : contacts_)
{
cout << left << setw(15) << name;
bool first = true;
for (const auto& phone : phones)
{
if (!first) cout << string(15, ' ');
cout << phone << endl;
first = false;
}
}
cout << string(50, '=') << endl;
cout << "共 " << contacts_.size() << " 个联系人" << endl;
}
// 统计信息
void statistics() const
{
int totalPhones = 0;
for (const auto& [name, phones] : contacts_)
totalPhones += phones.size();
cout << "\n===== 统计信息 =====" << endl;
cout << "联系人数:" << contacts_.size() << endl;
cout << "电话号码总数:" << totalPhones << endl;
if (!contacts_.empty())
cout << "平均每人号码数:" << (double)totalPhones / contacts_.size() << endl;
}
};
int main()
{
using namespace std;
PhoneBook pb;
// 添加联系人
try
{
pb.addContact("张三", "138-1234-5678");
pb.addContact("张三", "010-12345678"); // 同一人多个号码
pb.addContact("李四", "139-8765-4321");
pb.addContact("王五", "021-87654321");
pb.addContact("张伟", "186-5555-6666");
pb.addContact("张明", "187-7777-8888");
}
catch (const exception& e)
{
cerr << "错误:" << e.what() << endl;
}
// 显示所有联系人
pb.display();
// 按姓名查找
cout << "\n===== 查找张三 =====" << endl;
if (auto phones = pb.findByName("张三"))
{
cout << "张三的电话:";
for (const auto& p : *phones) cout << p << " ";
cout << endl;
}
// 按号码查找
cout << "\n===== 按号码查找 =====" << endl;
string name = pb.findByPhone("139-8765-4321");
cout << "139-8765-4321 → " << (name.empty() ? "未找到" : name) << endl;
// 前缀搜索
cout << "\n===== 搜索\"张\" =====" << endl;
auto results = pb.search("张");
for (const auto& r : results)
cout << " " << r << endl;
// 删除号码
pb.removePhone("张三", "010-12345678");
cout << "\n删除张三的010号码后:";
if (auto phones = pb.findByName("张三"))
for (const auto& p : *phones) cout << p << " ";
cout << endl;
// 统计
pb.statistics();
return 0;
}
📝 第11章知识点总结
| 知识点 | 核心要点 |
|---|---|
| 关联容器类型 | map/set(有序唯一)、multimap/multiset(有序可重复)、unordered_*(无序哈希) |
| map vs set | map存键值对,set只存键;都按键有序,键唯一 |
| pair | first/second 访问,make_pair 创建,map的元素类型是 pair<const K, V> |
| insert 返回值 | map/set的insert返回 pair<iterator, bool>,bool表示是否插入成功 |
| 下标 vs at() | [] 不存在时创建(不能用于const map),at() 不存在时抛异常 |
| find vs count | find 返回迭代器,count 返回数量(map/set只返回0或1) |
| lower_bound | 第一个键 >= k 的迭代器 |
| upper_bound | 第一个键 > k 的迭代器 |
| equal_range | 返回 [lower_bound, upper_bound) 的 pair,用于multimap查找所有相同键 |
| multimap 遍历 | 用 equal_range 或 lower_bound+upper_bound 遍历相同键的所有元素 |
| unordered_map | 哈希表,O(1)平均查找,无序,操作接口与map基本相同 |
| 自定义哈希 | 提供哈希函数对象和相等比较函数,用于自定义类型的无序容器 |
| 有序容器排序 | 默认用 < 运算符,可以提供自定义比较函数(严格弱序) |
| map 的键是 const | value_type 是 pair<const K, V>,不能修改键 |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)