C++-setmap详解
C++set&map
1. 序列式容器和关联式容器
1.1 序列式容器
序列式容器按照线性顺序存储元素,元素的位置取决于插入的时间和位置,与元素的值无关。
主要特点:
-
元素按插入顺序存储
-
可以通过位置(索引)直接访问元素
-
不自动排序
-
允许重复元素
常见的序列式容器:
-
array(C++11)
-
固定大小的数组
-
内存连续分配
-
大小在编译时确定
-
快速随机访问
-
-
vector
-
动态数组
-
内存连续分配
-
可动态扩展
-
在尾部插入/删除高效
-
支持快速随机访问
-
-
deque(双端队列)
-
双端可扩展
-
内存分段连续
-
在头尾插入/删除高效
-
支持快速随机访问(比vector稍慢)
-
-
list
-
双向链表
-
内存非连续分配
-
在任何位置插入/删除高效
-
不支持随机访问
-
-
forward_list(C++11)
-
单向链表
-
内存非连续分配
-
更节省空间
-
只支持单向遍历
-
1.2 关联式容器
关联式容器按照特定顺序存储元素,元素的顺序取决于元素的键(key),而不是插入的顺序。
主要特点:
-
元素按特定顺序(平衡二叉搜索树或哈希)存储
-
通过键(key)快速查找元素
-
通常实现为平衡二叉搜索树或哈希表
-
有些版本不允许重复元素
常见的关联式容器:
-
有序关联容器(基于红黑树实现)
-
set:唯一键的集合,按键排序
-
map:键值对集合,按键排序,键唯一
-
multiset:键可重复的set
-
multimap:键可重复的map
-
-
无序关联容器(C++11引入,基于哈希表实现)
-
unordered_set:唯一键的集合,基于哈希
-
unordered_map:键值对集合,基于哈希,键唯一
-
unordered_multiset:键可重复的unordered_set
-
unordered_multimap:键可重复的unordered_map
-
2. 认识pair类型
2.1 概念
pair
是C++标准模板库(STL)中的一个实用模板类,用于将两个值组合成一个单一对象,可以存储两个不同类型的元素,称为first
和second
。这两个值可以是相同类型,也可以是不同类型。它定义在<utility>
头文件中,是许多STL容器(如map
)的基础构建块。
2.2 pair实现
template <class T1, class T2>
struct pair
{typedef T1 first_type;typedef T2 second_type;first_type first;second_type second;pair(): first(T1()), second(T2()) {} // 默认构造pair(const T1& a, const T2& b): first(a), second(b) {}template<class U, class V>pair (const pair<U,V>& pr): first(pr.first), second(pr.second) {} // 拷贝构造
};
为什么拷贝构造需要使用类中内嵌模板?
template <typename U1, typename U2> pair(const pair<U1, U2>& p);
这个构造函数的存在是为了支持跨类型的拷贝构造,而不仅仅是同类型的拷贝构造。这是 C++ 模板类设计中的一个重要特性,称为转换构造函数。
-
类型转换支持:
-
允许从
pair<U1, U2>
构造pair<T1, T2>
-
只要
U1
可以转换为T1
,U2
可以转换为T2
-
-
场景:
std::pair<int, double> p1(42, 3.14); std::pair<long, float> p2(p1); // 需要这个模板构造函数
-
与普通拷贝构造的区别:
-
普通拷贝构造:
pair(const pair<T1, T2>& p)
-
模板拷贝构造:
template <typename U1, typename U2> pair(const pair<U1, U2>& p)
-
-
如果没有这个模板拷贝构造函数会怎么样?
std::pair<int, std::string> p1(1, "hello"); std::pair<double, const char*> p2(p1); // 编译错误 // 因为没有从 pair<int,string> 到 pair<double,const char*> 的转换路径
2.3 创建和初始化pair
2.3.1 构造函数
std::pair<int, std::string> p1(1, "apple");
2.3.2 使用make_pair函数(自动推导类型)
make_pair函数模板原型:
template <class T1,class T2>
inline pair<T1,T2> make_pair (T1 x, T2 y)
{return ( pair<T1,T2>(x,y) );
}
auto p2 = std::make_pair(2, "banana");
2.3.3 C++11-initializer_list初始化
std::pair<int, std::string> p3 = {3, "cherry"};
2.4 访问pair成员
通过 first
和 second
访问成员。
2.4.1 普通访问
std::pair<int, std::string> p(1, "apple");std::cout << "First: " << p.first << std::endl; // 输出: First: 1
std::cout << "Second: " << p.second << std::endl; // 输出: Second: apple
2.4.2 结构化绑定(C++17)
auto p = std::make_pair(3.14, "pi");
auto [value, name] = p; // value=3.14, name="pi"
3. set
set
是C++ STL中的关联式容器,它存储唯一元素(key)并自动排序去重。
set原型
template < class T, class Compare = less<T>, class Alloc = allocator<T>> class set;
-
T就是set底层关键字(key)的类型
-
set默认要求T⽀持⼩于⽐较(仿函数less支持搜索树大于往右走,小于往左走,greater则相反),若比较的类型和方式比较复杂,可以自己实现仿函数
-
set底层是⽤红⿊树实现,增删查效率是O(logN) ,迭代器遍历是⾛的搜索树的中序,根据搜索树的性质,遍历是有序的
3.1 成员函数
3.1.1 成员类型
成员类型 | 解释 |
---|---|
key_type | 第一个模板参数T |
value_type | 第一个模板参数T |
key_compare | 第二个模板参数(less仿函数) |
allocator_type | 第三个模板参数,STL空间配置器(内存池) |
3.1.2 构造函数
序号 | 函数原型 | 说明 |
---|---|---|
1️⃣ | explicit set (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()) | 默认构造 |
2️⃣ | set (const set& x) | 拷贝构造 |
3️⃣ | set (initializer_list<value_type> il, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()) | 使用 initializer_list 初始化 |
4️⃣ | template <class InputIterator> set (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& = allocator_type()) | 使用一段迭代器区间初始化 |
3.1.3 赋值重载
序号 | 函数原型 | 说明 |
---|---|---|
1️⃣ | set& operator= (const set& x) | 两个已存在的 set 对象的赋值 |
2️⃣ | set& operator= (initializer_list<value_type> il) | 使用 initializer_list 赋值 |
3.1.4 迭代器
序号 | 函数原型 | 说明 |
---|---|---|
1️⃣ | iterator begin() | 返回指向 set 对象中第一个元素的迭代器 |
2️⃣ | const_iterator begin() const | 返回指向 set 对象中第一个元素的 const 迭代器 |
3️⃣ | iterator end() | 返回指向 set 对象末尾元素之后位置的迭代器 |
4️⃣ | const_iterator end() const | 返回指向 set 对象末尾元素之后位置的 const 迭代器 |
5️⃣ | reverse_iterator rbegin() | 返回指向 set 对象末尾元素的反向迭代器 |
6️⃣ | const_reverse_iterator rbegin() const | 返回指向 set 对象末尾元素的 const 反向迭代器 |
7️⃣ | reverse_iterator() rend() | 返回指向 set 对象起始元素之前位置的反向迭代器 |
8️⃣ | const_reverse_iterator() rend() const | 返回指向 set 对象起始元素之前位置的 const 反向迭代器 |
注意:set迭代器是按中序的方式遍历的。
3.1.5 容量相关的接口
序号 | 函数原型 | 说明 |
---|---|---|
1️⃣ | bool empty() const | 判断 set 对象是否为空 |
2️⃣ | size_type size() const | 返回 set 对象中元素的数量 |
3.1.6 修改相关的接口
序号 | 函数原型 | 说明 |
---|---|---|
1️⃣ | pair<iterator,bool> insert (const value_type& val) | 向 set 对象中插入 val 元素 |
2️⃣ | iterator erase (const_iterator position) | 删除 set 对象中 position 迭代器位置元素,返回删除元素的下一个有效迭代器 |
3️⃣ | size_type erase (const value_type& val) | 删除 set 对象中 val 元素,返回值返回为删除的 val 元素(0或1) |
4️⃣ | void clear() | 清空 set 对象 |
pair<iterator,bool> insert (const value_type& val)
返回值解析返回值是一个
std::pair
,包含两个部分:
iterator:指向插入元素的迭代器
如果插入成功:指向新插入的元素
如果插入失败(元素已存在):指向已存在的元素
bool:表示插入是否成功
true
:元素被成功插入
false
:元素已存在,未插入新元素
3.1.7 其他
序号 | 函数原型 | 说明 |
---|---|---|
1️⃣ | iterator find (const value_type& val) | 在 set 对象中查找 val 元素,成功返回该位置迭代器,失败返回迭代器end() |
2️⃣ | size_type count (const value_type& val) const | 返回 set 对象中 val 元素的数量(0或1) |
3.2 set与multiset的区别
set和multiset都是C++ STL中的关联容器,它们的主要区别在于元素的唯一性。
主要区别
-
元素唯一性
-
set
:存储唯一元素,不允许重复 -
multiset
:允许存储重复元素
-
-
插入操作
-
向
set
插入已存在元素时,插入操作会失败 -
向
multiset
可以重复插入相同元素
-
set与multiset接口一致。
对于包含重复元素的multiset,find接口会返回按照容器排序顺序(默认是中序遍历顺序)出现的第一个与
key
相等的元素的迭代器。
4. map
map
是C++标准模板库(STL)中的一个关联容器,它存储的元素是pair类型,并且根据键(key)自动排序去重。
map原型
template < class Key, class T, class Compare = less<Key>, class Alloc = allocator<pair<const Key,T>> > class map;
map中存储的pair类型
typedef pair<const Key, T> value_type;
-
Key就是map底层关键字的类型,T是map底层value的类型
-
map默认要求Key⽀持⼩于⽐较(仿函数less支持搜索树大于往右走,小于往左走,greater则相反),若比较的类型和方式比较复杂,可以自己实现仿函数
-
map底层是⽤红⿊树实现,增删查效率是O(logN) ,迭代器遍历是⾛的搜索树的中序,根据搜索树的性质,遍历是有序的
4.1 成员函数
4.1.1 成员类型
成员类型 | 解释 |
---|---|
key_type | 第一个模板参数Key |
mapped_type | 第二个模板参数T |
value_type | map 中实际存储的元素 pair<const key_type,mapped_type> |
key_compare | 第三个模板参数(less仿函数) |
allocator_type | 第死个模板参数,STL空间配置器(内存池) |
4.1.2 构造函数
序号 | 函数原型 | 说明 |
---|---|---|
1️⃣ | explicit map (const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()) | 默认构造 |
2️⃣ | map(const map& x) | 拷贝构造 |
3️⃣ | map(initializer_list<value_type> il, const key_compare& comp = key_compare(), const allocator_type& alloc = allocator_type()) | 使用 initializer_list 初始化 |
4️⃣ | template <class InputIterator> map (InputIterator first, InputIterator last, const key_compare& comp = key_compare(), const allocator_type& = allocator_type()) | 使用一段迭代器区间初始化 |
4.1.3 赋值重载
序号 | 函数原型 | 说明 |
---|---|---|
1️⃣ | map& operator= (const map& x) | 两个已存在的 map 对象的赋值 |
2️⃣ | map& operator= (initializer_list<value_type> il) | 使用 initializer_list 赋值 |
4.1.4 迭代器
序号 | 函数原型 | 说明 |
---|---|---|
1️⃣ | iterator begin() | 返回指向 map 对象中第一个元素的迭代器 |
2️⃣ | const_iterator begin() const | 返回指向 map 对象中第一个元素的 const 迭代器 |
3️⃣ | iterator end() | 返回指向 map 对象末尾元素之后位置的迭代器 |
4️⃣ | const_iterator end() const | 返回指向 map 对象末尾元素之后位置的 const 迭代器 |
5️⃣ | reverse_iterator rbegin() | 返回指向 map 对象末尾元素的反向迭代器 |
6️⃣ | const_reverse_iterator rbegin() const | 返回指向 map 对象末尾元素的 const 反向迭代器 |
7️⃣ | reverse_iterator() rend() | 返回指向 map 对象起始元素之前位置的反向迭代器 |
8️⃣ | const_reverse_iterator() rend() const | 返回指向 map 对象起始元素之前位置的 const 反向迭代器 |
注意:map迭代器是按中序的方式遍历的。
4.1.5 容量相关的接口
序号 | 函数原型 | 说明 |
---|---|---|
1️⃣ | bool empty() const | 判断 map 对象是否为空 |
2️⃣ | size_type size() const | 返回 map 对象中元素的数量 |
4.1.6 元素的访问
序号 | 函数原型 |
---|---|
1️⃣ | mapped_type& operator[] (const key_type& k) |
map
的 operator[]
是一个非常有用的成员函数,它提供了对映射值的快速访问和修改能力。
功能说明
-
查找与修改:如果键
k
存在于 map 中,返回对应的映射值(value)的引用 -
插入与修改:如果键
k
不存在,会自动插入一个新的键值对,键为k
,值为mapped_type
的默认构造值,然后返回这个新值(value)的引用
operator[]实现
(*( (insert(make_pair(k,mapped_type()))).first).second)
解析
mapped_type& operator[] (const key_type& k) {pair<iterator, bool> ret = insert({ k, mapped_type() });iterator it = ret.first;return it->second;
}
4.1.7 修改相关的接口
序号 | 函数原型 | 说明 |
---|---|---|
1️⃣ | pair<iterator,bool> insert (const value_type& val) | 向 map 对象中插入 val 元素 |
2️⃣ | iterator erase (const_iterator position) | 删除 map 对象中 position 迭代器位置元素,返回删除元素的下一个有效迭代器 |
3️⃣ | size_type erase (const key_type& val) | 删除 map 对象中 val 元素,返回值返回为删除的 val 元素(0或1) |
4️⃣ | void clear() | 清空 map 对象 |
pair<iterator,bool> insert (const value_type& val)
返回值解析返回值是一个
std::pair
,包含两个部分:
iterator:指向插入元素的迭代器
如果插入成功:指向新插入的元素
如果插入失败(元素已存在):指向已存在的元素
bool:表示插入是否成功
true
:元素被成功插入
false
:元素已存在,未插入新元素
4.1.8 其他
序号 | 函数原型 | 说明 |
---|---|---|
1️⃣ | iterator find (const value_type& val) | 在 map 对象中查找 val 元素,成功返回该位置迭代器,失败返回迭代器end() |
2️⃣ | size_type count (const value_type& val) const | 返回 map 对象中 val 元素的数量(0或1) |
4.2 map与multimap的区别
map和multimap都是C++ STL中的关联容器,它们的主要区别在于元素的唯一性。
主要区别
-
元素唯一性
-
map
:存储唯一元素,不允许重复 -
multimap
:允许存储重复元素
-
-
插入操作
-
向
map
插入已存在元素时,插入操作会失败 -
向
multimap
可以重复插入相同元素
-
-
operator[]
-
map
:支持operator[]
访问 -
multimap
:不支持operator[]
,因为键不唯一
-
map与multimap接口一致。
对于包含重复元素的multimap,find接口会返回按照容器排序顺序(默认是中序遍历顺序)出现的第一个与
key
相等的元素的迭代器。
5. set与map迭代器失效问题
- 只有指向被删除元素的迭代器会失效
循环中删除元素
std::set<int> s = {1, 2, 3, 4, 5};
for (auto it = s.begin(); it != s.end(); ) {if (*it % 2 == 0) {it = s.erase(it); // erase返回下一个有效迭代器} else {++it;}
}
今天的看不懂,会成为明天的太简单。