一.关联式容器和序列式容器
关联式容器和序列容器都是C++ STL中的容器,它们主要的区别在于其存储内部元素的方式不同。
1.1 序列容器
序列容器是按照元素插入的顺序来存储元素的,也就是说,元素在容器中的位置与其被插入的先后顺序是一致的。例如,vector、list 和deque都是序列容器。
1.2 关联式容器
而关联式容器不同,它们是通过比较关键字来对元素进行有序存储的。关键字可以是一个简单数据类型,例如整数或字符串,也可以是一个自定义的结构体或类,元素是按照关键字大小的顺序进行存储的。例如,set、map、multiset和multimap都是关联式容器。
1.3 区别
因此,序列容器和关联式容器的最大区别在于内部元素的顺序。
一般而言,关联式容器的检索效率比序列容器效率更高。
此外,在实现上,关联式容器一般采用红黑树或哈希表进行元素的存储和访问,而序列容器只需要链表或数组即可实现。
两种容器的插入、删除和查找操作的时间复杂度也有所不同,建议根据实际问题的需求来选择使用何种容器。
二 键值对
2.1 键值对的概念
用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代 表键值,value表示与key对应的信息,也称key-value模型。
比如:现在要创建一个英汉翻译的字典,那该字典中必然 有英文单词与其对应的中文含义,而且,英文单词与其中文含义是有些必定联系的,即通过该应 该单词,在词典中就可以找到与其对应的一种或多种中文含义。
2.2 STL对键值对的表示
在STL中 ,也存在键值对:
其在内部大概是这样定义的:
#include<iostream>
using namespace std;
template <class T1, class T2>
struct pair //差不多就是有两个元素的结构体
{
typedef T1 first_type;
typedef T2 second_type;
T1 first; //通过first和second寻找到俩个值
T2 second;
pair()
: first(T1())
,second(T2())
{}
pair(const T1& a, const T2& b)
: first(a)
, second(b)
{}
};
定义在iostream库中
#include<iostream>
using namespace std;
int main()
{
pair<int, int> p1;
p1.first = 1;
p1.second = 2;
cout << p1.first <<endl<< p1.second << endl;
return 0;
}
2.3 键值对的作用
在程序中,我们大多数在另一种数据结构中使用键值对,也就是说 我们在使用时大多将键值对作为一个类的元素,从而进行某种操作,但很少独自使用。
三.关联式容器的类型
根据应用场景的不同,STL总共实现了两种不同结构的管理式容器:树型结构与哈希结构。树型结构的关联式容器主要有四种:map、set、multimap、multiset。
这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结果,容器中的元素是一个有序的序列。我们这篇博客将介绍map和set树形结构关联式容器。
3.1 set
3.1.1 set的特点
包含在#include<set> 库中
1. set是按照一定次序存储元素的容器。
2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。 set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格弱排序准则进行 排序。
4. set容器通过key访问单个元素的速度通常比unordered_set容器慢,但它们允许根据顺序对 子集进行直接迭代。
注意:
1. 与map/multimap不同,map/multimap中存储的是真正的键值对,set中只放 value,但在底层实际存放的是由构成的键值对,因此set只能只需插入value即可。
2. set中的元素不可以重复(因此可以使用set进行去重)。
3. 使用set的迭代器遍历set中的元素,可以得到有序序列
4. set中的元素默认按照小于来比较
3.1.2 set的模板参数列表
T: set中存放元素的类型,实际在底层存储的键值对。
Compare:set中元素默认按照小于来比较。
Alloc:set中元素空间的管理方式,使用STL提供的空间配置器管理。
3.1.3 set的构造
函数声明 | 功能介绍 |
set (const Compare& comp = Compare(), const Allocator& = Allocator() ); | 构造空的set |
set (InputIterator first, InputIterator last, const Compare& comp = Compare(), const Allocator& = Allocator() ); | 用[first, last)区 间中的元素构造 set |
set ( const set& x); | set的拷贝构造 |
代码示例
#include<iostream>
#include<set>
#include<vector>
using namespace std;
int main()
{
//默认构造
set<int> s1;
//使用vector迭代器构造
vector<int> v = { 1,4,5,1231,21 };
set<int> s2(v.begin(), v.end());
//使用拷贝构造
set<int> s3(s2);
return 0;
}
3.1.4 set的迭代器
函数声明 | 功能介绍 |
iterator begin() | 返回set中起始位置元素的迭代器 |
iterator end() | 返回set中最后一个元素后面的迭代器 |
const_iterator cbegin() const 返回set中起始位置元素的const迭代器 | 返回set中起始位置元素的const迭代器 |
const_iterator cend() const | 返回set中最后一个元素后面的const迭代器 |
reverse_iterator rbegin() | 返回set第一个元素的反向迭代器,即end |
reverse_iterator rend() | 返回set最后一个元素下一个位置的反向迭代器,即rbegin |
const_reverse_iterator crbegin() const | 返回set第一个元素的反向const迭代器,即cend |
const_reverse_iterator crend() const | 返回set最后一个元素下一个位置的反向const迭代器,即crbegin |
这个就没什么好介绍的了,老生常谈了,但给大家看一下这个。
#include<iostream>
#include<set>
#include<vector>
using namespace std;
int main()
{
vector<int> v = { 1,4,5,1231,21 ,2,3, 100,9};
set<int> s(v.begin(), v.end());
for (auto& x : s)
{
cout << x << ' ';
}
cout << endl;
return 0;
}
我们可以证明 set在内部的存储是有序的,其内置元素类型会在其自动排序,自定义类型我们可以手动传入函数排序(一会细讲)。
算了 示例一下吧:
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> s = { 1, 2, 3, 4, 5 };
// 使用正向迭代器遍历set
cout << "正向遍历: ";
for (auto it = s.begin(); it != s.end(); ++it)
{
cout << *it << " ";
}
cout <<endl;
// 使用反向迭代器遍历set
cout << "反向遍历: ";
for (auto it = s.rbegin(); it != s.rend(); ++it)
{
cout << *it << " ";
}
cout << endl;
// 使用const迭代器遍历set
cout << "const遍历: ";
for (auto it = s.cbegin(); it != s.cend(); ++it)
{
cout << *it << " ";
}
cout << std::endl;
// 使用const反向迭代器遍历set
cout << "const反向遍历: ";
for (auto it = s.crbegin(); it != s.crend(); ++it)
{
cout << *it << " ";
}
cout << endl;
return 0;
}
结果为:
3.1.5 set的容量函数
函数声明 | 功能介绍 |
---|---|
bool empty ( ) const | 检测set是否为空,空返回true,否则返回true |
size_type size() const | 返回set中有效元素的个数 |
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> s1;
if (s1.empty())
{
cout << "set为空" << endl;
}
else
{
cout << "set不为空" << endl;
}
s1.insert(1);
s1.insert(2);
s1.insert(3);
if (s1.empty())
{
cout << "set为空" << endl;
}
else
{
cout << "set不为空" << endl;
}
cout << "set中元素的个数为: " << s1.size() << endl;
return 0;
}
代码结果为:
3.1.6 set的增删查改函数
函数声明 | 功能介绍 |
pair<iterator,bool> insert ( const value_type& x ) | 在set中插入元素x,实际插入的是<x, x>构成的键值对,如果插入成功,返回<该元素在set中的位置,true>,如果插入失败,说明x在set中已经存在,返回<x在set中的位置,false> |
void erase ( iterator position ) | 删除set中position位置上的元素 |
size_type erase ( const key_type& x ) | 删除set中值为x的元素,返回删除的元素的个数 |
void erase ( iterator first, iterator last ) | 删除set中[first, last)区间中的元素 |
void swap ( set<Key,Compare,Allocator>& st ); | 交换set中的元素 |
void clear ( ) | 将set中的元素清空 |
iterator find ( const key_type& x ) const | 返回set中值为x的元素的位置 |
size_type count ( const key_type& x ) const | 返回set中值为x的元素的个数 |
代码示例:
insert 有多种重载,这里只选择最常用的几种重载。
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> s;
// 插入单个元素
s.insert(1);
for (auto& x : s)
{
cout << x << ' ';
}
cout << endl;
// 插入多个元素
s.insert({ 2, 3, 4 });
for (auto& x : s)
{
cout << x << ' ';
}
cout << endl;
// 插入范围内的元素
set<int> anotherSet = { 5, 6, 7 };
s.insert(anotherSet.begin(), anotherSet.end());
// 输出set中的元素
for (auto& x : s)
{
cout << x << ' ';
}
cout << endl;
return 0;
}
set同样
#include <iostream>
#include <set>
using namespace std;
int main()
{
std::set<int> s = { 1, 2, 3, 4, 5 };
// 删除指定元素
s.erase(3);
for (auto& x : s)
{
cout << x << ' ';
}
cout << endl;
// 删除指定范围内的元素 从开始一直到4之前结束
s.erase(s.begin(), s.find(4));
// 输出set中的元素
for (auto& x : s)
{
cout << x << ' ';
}
cout << endl;
return 0;
}
代码结果为:
3.2 map
其中 Key和T 是一个键值对
Compare:map中元素默认按照小于来比较。
Alloc:map中元素空间的管理方式,使用STL提供的空间配置器管理。
3.2.1 map的概念和特点
1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元 素。
2. 在map中,键值key通常用于排序和惟一地标识元素,而值value中存储与此键值key关联的 内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型 value_type绑定在一起,为其取别名称为pair: typedef pair value_type,也就是说,在内部两个变量将合为一个pair类型
3. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序 对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
4. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
3.2.2 map的模板参数列表
其中 Key和T 是一个键值对
Compare:map中元素默认按照小于来比较。
Alloc:map中元素空间的管理方式,使用STL提供的空间配置器管理。
3.2.3 map的构造
函数声明 | 功能介绍 |
map (const Compare& comp = Compare(), const Allocator& = Allocator() ); | 构造空的map |
map(InputIterator first, InputIterator last, const Compare& comp = Compare(), const Allocator& = Allocator(()); | 用[first, last)区间中的元素构造map |
map( const map<Key,Compare,Allocator>& x); | map的拷贝构造 |
如:
#include<iostream>
#include<map>
#include<vector>
using namespace std;
int main()
{
//1.默认构造函数:创建一个空的map对象。
map<int, string> myMap;
//通过传入键值对来创建map对象。
map<int, string> myMap = { {1, "apple"}, {2, "banana"}, {3, "orange"} };
//2.用迭代器构造:通过使用迭代器范围来构造map对象。
vector<pair<int, string>> vec = { {1, "apple"}, {2, "banana"}, {3, "orange"} };
map<int, string> myMap(vec.begin(), vec.end());
//3.拷贝构造函数:通过拷贝另一个map对象来创建一个新的map对象。
map<int, string> originalMap = { {1, "apple"}, {2, "banana"}, {3, "orange"} };
map<int, string> copiedMap(originalMap);
return 0;
}
3.2.4 map的迭代器
函数声明 | 功能介绍 |
iterator begin() | 返回map中起始位置元素的迭代器 |
iterator end() | 返回map中最后一个元素后面的迭代器 |
const_iterator cbegin() const 返回set中起始位置元素的const迭代器 | 返回map中起始位置元素的const迭代器 |
const_iterator cend() const | 返回map中最后一个元素后面的const迭代器 |
reverse_iterator rbegin() | 返回map第一个元素的反向迭代器,即end |
reverse_iterator rend() | 返回map最后一个元素下一个位置的反向迭代器,即rbegin |
const_reverse_iterator crbegin() const | 返回map第一个元素的反向const迭代器,即cend |
const_reverse_iterator crend() const | 返回map最后一个元素下一个位置的反向const迭代器,即crbegin |
这真老生常谈了,我真不想给大家示例了:
#include <iostream>
#include <map>
#include<string>
using namespace std;
int main()
{
map<int, std::string> Map = { {1, "apple"}, {2, "banana"}, {3, "orange"} };
// 正向迭代器
map<int, string>::iterator it;
cout << "正向迭代器:" << endl;
for (it = Map.begin(); it != Map.end(); ++it)
{
cout << it->first << ": " << it->second << endl;
}
// 逆向迭代器
map<int, string>::reverse_iterator rit;
cout << "逆向迭代器:" << endl;
for (rit =Map.rbegin(); rit != Map.rend(); ++rit) {
cout << rit->first << ": " << rit->second << endl;
}
// 常量迭代器
map<int, string>::const_iterator cit;
cout << "常量迭代器:" << endl;
for (cit = Map.cbegin(); cit != Map.cend(); ++cit)
{
cout << cit->first << ": " << cit->second <<endl;
}
return 0;
}
代码结果为:
3.2.5 map的容量和访问函数
函数声明 | 功能简介 |
bool empty ( ) const | 检测map中的元素是否为空,是返回 true,否则返回false |
size_type size() const | 返回map中有效元素的个数 |
mapped_type& operator[] (const key_type& k) | 返回去key对应的value |
前两个我们不做解答,大家都懂,直接上代码。
#include<iostream>
#include <map>
#include<string>
using namespace std;
int main()
{
map<int, string> Map = { {1, "apple"}, {2, "banana"}, {3, "orange"} };
map<int, int> Map2;
if (Map.empty())
{
cout << "Map is empty" << endl;
}
else {
cout << "Map is not empty" << endl;
}
if (Map2.empty())
{
cout << "Map2 is empty" << endl;
}
else {
cout << "Map2 is not empty" << endl;
}
cout << "Map size: " << Map.size() <<endl;
cout << "Map2 size: " << Map2.size() << endl;
return 0;
}
代码结果为:
我们都知道,在Vector中,[]只是单纯的进行一个下标访问,同时还需要注意下表越界。
但在map中,我们不仅不用注意下标访问题,还能用[]直接插入键值对
#include <iostream>
#include <map>
#include<string>
using namespace std;
int main()
{
map<int, string> Map;
// 使用operator[]函数插入键值对
Map[1] = "apple";
Map[2] = "banana";
Map[3] = "orange";
// 使用size函数获取map的大小
cout << "Map size: " <<Map.size() << endl;
Map[4] = "pear";
cout << "Map size: " << Map.size() << endl;
// 使用operator[]函数访问map中的值
cout << "Value at key 2: " << Map[4] << endl;
return 0;
}
3.2.6 map的增删查改函数
函数声明 | 功能简介 |
pair<iterator,bool> insert ( const value_type& x ) | 在map中插入键值对x,注意x是一个键值 对,返回值也是键值对:iterator代表新插入 元素的位置,bool代表释放插入成功 |
void erase ( iterator position ) | 删除position位置上的元素 |
size_type erase ( const key_type& x ) | 删除键值为x的元素 |
void erase ( iterator first, iterator last ) | 删除[first, last)区间中的元素 |
void swap ( map& mp ) | 交换两个map中的元素 |
void clear ( ) | 将map中的元素清空 |
iterator find ( const key_type& x ) | 在map中插入key为x的元素,找到返回该元 素的位置的迭代器,否则返回end |
const_iterator find ( const key_type& x ) const | 在map中插入key为x的元素,找到返回该元 素的位置的const迭代器,否则返回cend |
size_type count ( const key_type& x ) const | 返回key为x的键值在map中的个数,注意 map中key是唯一的,因此该函数的返回值 要么为0,要么为1,因此也可以用该函数来 检测一个key是否在map中 |
#include <iostream>
#include <map>
#include<string>
using namespace std;
int main()
{
map<int, string> Map;
// 使用insert函数插入键值对
Map.insert(make_pair(1, "apple"));
Map.insert(make_pair(2, "banana"));
Map.insert(make_pair(3, "orange"));
// 打印插入后的map
cout << "Map after insert:" <<endl;
for (const auto& pair : Map)
{
cout << pair.first << ": " << pair.second << endl;
}
// 使用erase函数删除键值对
Map.erase(2);
// 打印删除后的map
cout << "Map after erase:" << endl;
for (const auto& pair : Map)
{
cout << pair.first << ": " << pair.second << endl;
}
return 0;
}