目录
1.引言
2.set
3.map
4.共同点
5.不同点
6.高级用法与注意事项
7.总结
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;elsecout << "键值为peach的元素已经存在:" << ret.first->first << "--->" <<ret.first->second << " 插入失败" << endl;// 删除key为"apple"的元素m.erase("apple");if (1 == m.count("apple"))cout << "apple还在" << endl;elsecout << "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
则用于存储键值对,并根据键进行排序。尽管它们在底层实现上有许多相似之处,但在接口和使用上却有明显的区别。通过理解这些区别,我们可以更好地选择和使用它们,以满足不同的编程需求。