C++关联容器操作详解
文章目录
1.关联容器
按关键字有序保存元素:
- map:关联数组,保存关键字-值对
- set:关键字即值,即只保存关键字的容器
- multimap:关键字可重复出现的map
- multiset:关键字可重复出现的set
无序集合:
- unordered_map:哈希函数组织的map
- unordered_set:哈希函数组织的set
- unordered_multimap:哈希函数组织的map,关键字可以重复出现
- unordered_multiset:哈希函数组织的set,关键字可以重复出现
2.使用关联容器
使用map
map是关键字-值对的集合。被称为无序数组,即通过关键字即可查找到对应的值。
通过map来统计单词在输入中出现的次数:
- 定义map:必须指定关键字和值的类型,分别是string类型和size_t类型
- 以单词名字作为关键字下标,相当于数组。
- 在map中的元素以pair类型存储,它具有first和second成员,分别表示关键字和值。
map<string, size_t> word_count; string word; while (cin >> word) { ++word_count[word]; } for (const auto& x : word_count) { cout << x.first << " occurs " << x.second << ((x.second > 1) ? " times" : " time") << endl; }
使用set
set存储关键字,是关键字的集合。
使用set来忽略常见的单词:
- 定义set:指定元素类型,string类型
- find返回迭代器,没有找到则指向尾后迭代器。
map<string, size_t> word_count; set<string> exclude{ "the","woaini","666","hhh","adaasa","dawdadadas","ylh","dwadada"}; string word; while (cin >> word) { //找到返回word,没找到返回尾后迭代器 if (exclude.find(word) == exclude.end()) { ++word_count[word]; } } for (const auto& x : word_count) { cout << x.first << " occurs " << x.second << ((x.second > 1) ? " times" : " time") << endl; }
3.关联容器概述
定义关联容器
初始化map与set
-
使用一个关键字类型和值类型来定义map,只需一个关键字来定义set。
-
默认构造函数:创建一个空容器。
-
初始化同类型的拷贝。
-
使用值范围来初始化关联容器。
-
也可以进行值初始化,列表初始化。
map<string, string> a{ {"ylh","da"}, {"dad","oid"}, {"oda","cece"} }; set<string> b{ "wad","Da","grg","plda" };
初始化multimap与multiset
允许有重复关键字:
map和set如果有多个关键字,则会跳过;multimap和multiset会继续存储
·vector<int> a; for (size_t i = 0; i != 10; ++i) { a.push_back(i); a.push_back(i); } set<int> b1{ a.cbegin(),a.cend() }; //set.size()= 10 multiset<int> b2{ a.cbegin(),a.cend() }; //multiset.size()= 20
示例
map存储姓氏和家庭成员的名字: 支持添加新成员和插入:
map<string,vector<string>> family; string first_name,child_name; while ( [&]()->bool {cout<<"请输入家庭的姓:"; return cin>>first_name && (first_name != "end");}() ) { cout<<"请输入孩子的名字:"; while (cin>>child_name && (child_name != "end")) { family[first_name].push_back(child_name); } } for (auto it = peo.cbegin(); it != peo.cend(); ++it) { cout << (*it).first << ": "; for_each((*it).second.begin(), (*it).second.end(), [](const string& str) {cout << str << " "; }); cout << endl; }
pair类型
include < utility > 头文件
一个pair保存两个数据成员。
pair用来生成特定类型的模板,创建一个pair:提供两个类型名。pair的数据成员将具有对应的类型。
pair<string, string> a; pair<string, size_t> b; pair<string, list<size_t>> c;
当然也可以进行值初始化。
pair的成员被命名为first和second,是共有的。普通的成员访问符号可以直接访问。
pair类型的函数返回值:
pair<string, int> process(vector<string>& v) { if (!v.empty()) { return { v.back(),v.back().size() }; //显式列表初始化 } else { return pair<string, int>(); //隐式构造返回值 } }
pair被其他容器包含的情况:
vector的元素为pair,pair再隐式或显式构造对象
vector<pair<string, int>> a; string temp; int num; while (cin >> temp >> num) { a.push_back({ temp,num }); //隐式构造 }
补充:利用vector构造pair元素对象的几种方式:
a.push_back({ temp,num }); //隐式构造 a.push_back(pair<string, int>{temp, num}); //显式构造 a.push_back(make_pair(temp, num)); //make_pair构造 a.emplace_back(temp, num); //emplace_back构造函数
示例:在用map存储姓氏和名字的基础上,使用pair存储名字和生日
-
使用lamdba表达式更快捷
-
lamdba表达式采用&捕获引用,指明返回类型**,最后有一个小括号表示要传递的形参数值,此题中不用传递形参,所以此处为空。**
-
打印的方式:可以采用二维数组的方式打印:
首先由map得到 string和 vector容器,打印string;在由vector容器得到pair类型,再有pair的first和second得到结果。
map<string, vector<pair<string,string>>> peo; //姓: 名字和生日 string fname, name, birth; int choice = 0; while ([&]()->bool {cout << "请输入家庭姓氏: "; return (cin >> fname) && (fname != "quit"); }()) { while ([&]()->bool {cout << "请输入姓名: "; return (cin >> name) && (name != "quit"); }()) { if ([&]()->bool {cout << "请输入生日: "; return (cin >> birth) && (birth != "quit"); }()) { peo[fname].emplace_back(name, birth); } } } cout << endl; for (const auto& i : peo) { cout << i.first << ": " << endl; for (const auto& y : i.second) { cout << y.first << ", " << y.second << endl; } }
4.关联容器操作
set和map关键字和值的类型:
- key_type:容器类型的关键字类型
- mapped_type:适用于map,表示值类型。
- value_type:对于set:关键字类型。对于map:表示pair<const key_type,mapped_type>
关联容器的迭代器
迭代器map_it指向map的第一个元素,解引用迭代器得到一个pair的引用,pair的first为const,second可以修改。
map<string, int> a{ {"ylh",666} }; auto map_it = a.begin(); //map的迭代器 cout << map_it->first << " " << map_it->second << endl; map_it->first = "woaini"; //错误,关键字是const ++map_it->second; //可以递增迭代器改变元素
set: set关键字只读,无法修改。
set<int> a{ 1 }; auto set_it = a.begin(); ++(* set_it) //错误,set关键字只读
map和set的遍历:
for (auto it=a.cbegin();a!=a.cend();++a) { cout<<(*it).first<<" "<<it->second<<endl; }
注意:使用set,map,multiset,multimap遍历时,迭代器按关键字升序遍历元素。
ps:通常不对关联容器使用泛型算法。
添加元素
inset
set中使用insert:
vector<int> a{ 2,4,6,8,10 }; set<int> set2; set2.insert(a.cbegin(), a.cend()); //5个 set2.insert({ 1,3,5,7,1,3,5,7 }); //4个 //总共9个
map中使用insert:(和创建pair类型的对象一样)
map<string, int> a; a.insert(pair<string, int>{"ylh", 666}); //显式构造pair a.insert({ "word",111 }); //初始化列表 a.insert(make_pair("word", 222)); //make_pair构造 a.insert(map<string, int>::value_type{ "word",333 }); //构造一个pair,并且构造一个对象
insert的返回值
map和set的insert或者emplace返回一个 pair类型。
pair的first成员是一个迭代器,指向已经具有的关键字的元素;
- first指向一个map的iterator,可以再次解引用迭代器得到pair,并使用first得到关键字,second得到值。
pair的second成员是一个bool值,指出插入成功,还是已经存在于容器。
- 如果关键字已经存在,则second成员返回false,插入失败。
- 如果关键字不存,则second成员返回true,插入成功。
map<string,int> a; a.insert({ "word",111 }); auto it=a.insert(make_pair("word", 222)); //it的first指向已经具有的关键字的元素。 second表示bool。 cout << (it.first)->first << " " << (it.first)->second << " " << it.second; //分别打印: word 111 0
insert返回值的完整类型:pair包含一个map的迭代器与bool
pair<map<string,int>::iterator,bool> it=a.insert(make_pair(word,1));
multimap和multiset的insert
允许一个关键字有多个对应的值。
multimap<string, int> a; a.insert(pair<string, int>{"ylh", 666}); a.insert({ "word",111 }); a.insert(make_pair("word", 222)); a.insert(map<string, int>::value_type{ "word",333 });
示例
单词计数,利用insert的返回值来递增关键字的值的计数。
++word_count.insert({ word,0 }).first->second; /*auto it = word_count.insert(make_pair(word, 1)); if (!it.second) { ++it.first->second; }*/
一个程序:思考这个程序打印能否使用for_each或者copy算法???
vector<size_t> temp{ 1,2,3,4,5,6 }; map<string, vector<size_t>> a; string word = "word"; pair<map<string, vector<size_t>>::iterator, bool> it = a.insert(make_pair(word,temp)); for (auto it = a.cbegin(); it != a.cend(); ++it) { cout << it->first << " "; //得到string for (auto it2 = it->second.cbegin(); it2 != it->second.cend(); ++it2) { cout << *it2 << " "; } cout << endl; } //for_each(a.cbegin()->first, a.cbegin()->first, [](const string& str) {cout << str << " :"; }); //for_each(a.cbegin()->second.cbegin(), a.cbegin()->second.cend(), [](const size_t& num) {cout << num << " "; });
erase
删除元素,接受一个key_type,即关键字,删除关键字匹配的所有元素。
对于map和set:由于他们必须是不重复的关键字,所以erase的结果要么为0,要么为1。0表示删除的元素不存在
对于multimap和multiset:他们接受重复关键字,所以erase的结果可以是大于1,表示删除的个数。
multimap<string, int> a{ {"wo",1},{"ar",2},{"wo",2} }; cout<<a.erase("wo"); //结果为2
同时erase也接受迭代器的指定的范围删除。
下标操作
下标运算符和at函数。
set类型不支持下标;multimap和unordered_multimap不能进行下标操作(可以会有多个值对应关键字)。
对map执行下标操作: 如果关键字在map中,会找到对应的值;如果不在map中,会创建一个元素,默认值为0
map<string, int> a; cout << a["wda"] << endl; //使用默认的字-值对:wda - 0
at访问,如果关键字不存在,则会报错。
对map进行下标操作,会获得一个mapped_type;对map进行解引用,会获得一个value_type。
map的下标操作返回一个左值,既可以读也可以写。
访问元素
find和count:find查找元素出现的位置,没找到则返回尾后迭代器;count返回元素出现的次数。
find代替下标操作
下标操作如果关键字不在map中,则会创建一个默认的字-值对;但我们不希望改变map的结构。
find没有找到会返回 end。
map<string, int> a{ {"wo",1},{"ao",2},{"po",3} }; if (a.find("ylh") == a.end()) { cout << "没有找到!\n"; }
multimap和multiset的查找
multimap<string, int> a{ {"ylh",666},{"ylh",999},{"ylh",888},{"asd",555} }; auto a_count = a.count("ylh"); //查找出指定字出现的次数 auto it_beg = a.find("ylh"); //查找第一个出现的位置 while (a_count) { cout << it_beg->second << " "; //打印目标 ++it_beg; //递增到下一目标 --a_count; //计数递减 }
使用面向迭代器版本的查找方式
lower_bound:返回的迭代器指向第一个具有给定关键字的元素。
upper_bound:返回的迭代器指向最后一个匹配元素之后的位置
multimap<string, int> a{ {"ylh",666},{"ylh",999},{"ylh",888},{"asd",555},{"zdwa",555},{"zoef",565} }; // beg指向第一个符合条件的元素,end指向最后一个符合条件的元素的下一个位置 for (auto beg = a.lower_bound("ylh"), end = a.upper_bound("ylh"); beg != end; ++beg) { cout << beg->second << endl; }
ps:他们的返回值可以作为一个迭代器范围。
如果没有元素与给定关键字匹配,则lower_bound和upper_bound返回相等位置的迭代器。
equal_range
接受一个关键字,返回一个迭代器pair。
beg为a第一个出现关键字的位置的一个迭代器pair:
pair的first成员为找到的第一个位置的迭代器,second成员为找到的最后一个符合关键字的下一个位置迭代器。
pair<multimap<string,int>::iterator,multimap<string,int>::iterator>
其实就是lower_bound和upper_bound的简写版:
multimap<string, int> a{ {"ylh",666},{"ylh",999},{"ylh",888},{"asd",555},{"zdwa",555},{"zoef",565} }; for (auto beg=a.equal_range("ylh");beg.first!=beg.second;++beg.first) { cout << beg.first->second << endl; }
beg的first为找到的第一个符合条件的迭代器,他是一个pair类型对象,解引用或者->运算符得到pair的first或者second。
示例
在multimap<string,string>中查找一个string值元素,并删除它:
multimap<string, string> a{ {"ylh","iot"} ,{"wad","dwapd"},{"ylh","posa"},{"ylh","zbcd"},{"xsd","podv"}}; auto fin_it = a.find("ylh"); //查找关键字出现的位置 auto count = a.count("ylh"); //统计关键字出现的次数 while (count) { if (fin_it->second == "zbcd") //如果找到了值为zbcd的元素位置 { a.erase(fin_it); //删除元素 break; } ++fin_it; --count; } for (auto beg = a.cbegin(); beg != a.cend(); ++beg) { cout << beg->first << ": " << beg->second << endl; }
删除指定的所有关键字和值:
multimap<string, string> a{ {"ylh","iot"} ,{"wad","dwapd"},{"ylh","posa"},{"ylh","zbcd"},{"xsd","podv"}}; auto beg = a.equal_range("ylh"); //找到关键字的位置 a.erase(beg.first,beg.second); //删除全部此关键字
5.无序容器
无序容器不使用比较运算符来组织元素,而是使用一个哈希函数,和关键字的==运算符。
使用无序容器来统计单词,但是不会以字典序的形式输出
unordered_map<string, size_t> word_count; string word; while (cin >> word) { ++word_count.insert({ word,0 }).first->second; } for (const auto& x : word_count) { cout << x.first << " occurs " << x.second << ((x.second > 1) ? " times" : " time") << endl; }
无序容器的桶操作
- 无序版本优势:当容器中key没有明显的顺序关系时更有用,且不需要耗费多余的时间来维护容器中的key序列
- 有序版本优势:当容器中key有明显的顺序关系时更有用,且我们不需要考虑排序问题,容器自动维护序列(字典序)
- map的key_value是有序的,set本身就是有序的,有序容器的操作可以用于无序容器
- 无序容器访问元素时,首先计算元素的哈希值,它指出应该搜索哪个桶,因为容器将哈希值相同的元素放在一个桶中。
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209733.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)