目录
1.引言
在C++的标准模板库(STL)中,set
和map
是两种常用的容器,它们提供了不同的功能和使用场景。尽管它们底层都基于红黑树实现,有许多相似之处,但在具体用途和接口上却有明显的区别。本文将详细探讨set
和map
的差异,帮助大家在实际编程中更好地选择和使用它们。
2.set
set
是一种关联容器,主要用于存储唯一的、已排序的元素。在set
中,每个元素都是唯一的,且set
会自动根据元素的值进行排序(默认按升序)。试图插入重复元素将被忽略,这是set
设计的初衷。确保插入前元素的唯一性,或利用此特性进行去重或排序。set
内部实现通常采用红黑树,因此其插入、删除和查找操作的时间复杂度为O(log n)。
#include <set>
#include <iostream>
int main() {
std::set<int> mySet = {3, 1, 4, 1, 5, 9}; // 自动排序并去重
for (int num : mySet) {
std::cout << num << " ";
}
mySet.insert(10); // 成功插入
mySet.insert(10); // 重复,不会插入
return 0;
}
3.map
map
也是一种关联容器,但它存储的是键值对(key-value pairs)。在map
中,每个键都是唯一的,且map
会根据键的值自动排序(默认按升序)。值可以重复。键用于排序和查找,值则存储实际数据。尝试插入已存在的键会导致插入失败,而不是覆盖原有值。若需覆盖,请先检查键是否存在,再决定插入或更新。与set
类似,map
的内部实现也基于红黑树,因此其插入、删除和查找操作的时间复杂度同样为O(log n)。
#include <iostream>
#include <map>
using namespace std;
#include <string>
void TestMap()
{
map<string, string> m;
// 向map中插入元素的方式:
// 将键值对<"peach","桃子">插入map中,用pair直接来构造键值对
m.insert(pair<string, string>("peach", "桃子"));
// 将键值对<"peach","桃子">插入map中,用make_pair函数来构造键值对
m.insert(make_pair("banan", "香蕉"));
// 借用operator[]向map中插入元素
/*
operator[]的原理是:
用<key, T()>构造一个键值对,然后调用insert()函数将该键值对插入到map中
如果key已经存在,插入失败,insert函数返回该key所在位置的迭代器
如果key不存在,插入成功,insert函数返回新插入元素所在位置的迭代器
operator[]函数最后将insert返回值键值对中的value返回
*/
// 将<"apple", "">插入map中,插入成功,返回value的引用,将“苹果”赋值给该引用结果,
m["apple"] = "苹果";
// key不存在时抛异常
//m.at("waterme") = "水蜜桃";
cout << m.size() << endl;
// 用迭代器去遍历map中的元素,可以得到一个按照key排序的序列
for (auto& e : m)
cout << e.first << "--->" << e.second << endl; // 遍历键值对
cout << endl;
// map中的键值对key一定是唯一的,如果key存在将插入失败
auto ret = m.insert(make_pair("peach", "桃色"));
if (ret.second)
cout << "<peach, 桃色>不在map中, 已经插入" << endl;
else
cout << "键值为peach的元素已经存在:" << ret.first->first << "--->" <<
ret.first->second << " 插入失败" << endl;
// 删除key为"apple"的元素
m.erase("apple");
if (1 == m.count("apple"))
cout << "apple还在" << endl;
else
cout << "apple被吃了" << endl;
}
int main()
{
TestMap();
return 0;
}
4.共同点
- 有序性:
set
和map
都是有序的容器,它们的元素会根据一定的排序准则(通常是<
运算符,但也可以是自定义的比较函数或函数对象)自动排序。 - 唯一性:
set
和map
中的元素都是唯一的,不允许有重复的元素。 - 自动平衡:它们的底层实现通常基于红黑树(Red-Black Tree),因此插入、删除和查找操作的时间复杂度都是 O(log n)。
5.不同点
- 存储的元素类型:
set
存储的是单个元素,每个元素都是唯一的。map
存储的是键值对(key-value pairs),其中键(key)是唯一的,但值(value)可以重复。
- 元素访问方式:
set
中的元素只能通过迭代器访问,或者通过find
方法查找。map
中的元素可以通过键(key)直接访问,例如map[key]
,或者通过find
方法查找。
- 迭代器类型:
set
的迭代器指向的是单个元素。map
的迭代器指向的是键值对(pair<const Key, T>
)。
- 适用场景:
set
适用于需要存储唯一元素并且需要排序的场景,比如去重和排序后的集合。map
适用于需要存储键值对并且需要根据键进行快速查找、插入和删除的场景,比如字典或缓存。
-
额外功能
1.set
:提供如lower_bound
和upper_bound
等成员函数,方便进行范围查找;
支持一些集合运算,如并集、交集、差集等(通过STL算法库实现)。
2.map
:map
的键可以是任意类型,只要支持<
操作符(或提供自定义比较函数)。
map
的值可以是任意类型,甚至是另一个map
或set
。
6.性能对比
在大多数情况下,set
和map
的性能是相近的,因为它们的底层实现都是红黑树。然而,由于map
需要额外存储键和值,因此在内存占用上可能会比set
更高一些。
6.高级用法与注意事项
1. 自定义排序
我们可以通过提供自定义的比较函数或函数对象来改变set
和map
的默认排序行为。
set:
#include <set>
#include <iostream>
#include <functional>
struct MyStruct {
int id;
std::string name;
};
// 自定义比较函数
bool compareMyStruct(const MyStruct& a, const MyStruct& b) {
return a.id < b.id;
}
int main() {
std::set<MyStruct, std::function<bool(const MyStruct&, const MyStruct&)>> mySet(compareMyStruct);
mySet.insert({1, "Alice"});
mySet.insert({2, "Bob"});
mySet.insert({3, "Charlie"});
for (const auto& item : mySet) {
std::cout << item.id << ": " << item.name << std::endl;
}
return 0;
}
map:
#include <map>
#include <iostream>
#include <string>
#include <functional>
// 自定义比较函数
struct CustomCompare {
bool operator()(const std::string& a, const std::string& b) const {
return a > b; // 降序排序
}
};
int main() {
std::map<std::string, int, CustomCompare> myMap;
myMap["apple"] = 1;
myMap["banana"] = 2;
myMap["cherry"] = 3;
for (const auto& pair : myMap) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
2.多线程安全
set
和map
都不是线程安全的,如果在多线程环境中使用,需要手动进行同步。
3.迭代器失效
在set
和map
中,插入不会使迭代器失效,但删除操作会使指向删除元素的迭代器失效。因此,在进行删除操作时,需要特别注意迭代器的有效性。
7.总结
set
和map
是C++ STL中两种重要的关联容器,它们各自有不同的特点和适用场景。set
主要用于存储唯一且排序的元素,而map
则用于存储键值对,并根据键进行排序。尽管它们在底层实现上有许多相似之处,但在接口和使用上却有明显的区别。通过理解这些区别,我们可以更好地选择和使用它们,以满足不同的编程需求。