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本身就是有序的,有序容器的操作可以用于无序容器
  • 无序容器访问元素时,首先计算元素的哈希值,它指出应该搜索哪个桶,因为容器将哈希值相同的元素放在一个桶中
posted @   hugeYlh  阅读(29)  评论(0编辑  收藏  举报  
编辑推荐:
· 从 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)
点击右上角即可分享
微信分享提示