✨✨欢迎大家来到Celia的博客✨✨
🎉🎉创作不易,请点赞关注,多多支持哦🎉🎉
所属专栏:C++
个人主页:Celia's blog~
目录
编辑
序列式容器和关联式容器
一、set
1.1 set介绍
1.2 set功能
1.3 set成员函数
1.3.1 构造函数
1.3.2 迭代器
1.3.3 其他成员函数
二、map
2.1 map介绍
2.2 map功能
2.3 map成员函数
2.3.1 构造函数
2.3.2 迭代器
2.3.3 其他成员函数
2.3.3.1 insert
2.3.3.2 []运算符重载
三、multiset
四、multimap
序列式容器和关联式容器
- 在之前常用的STL容器中,有vector、list、queue、stack、string等。这些容器被称为序列式容器,它们底层的存储结构都是线性存储的,元素与元素之间没有明确的位置关系,交换序列式容器中的两个元素后,它们还是序列式容器。序列式容器中的元素是按照在容器中的储存位置和顺序进行保存和访问的。
- 关联式容器与序列式容器不同的是,关联式容器中的元素都有明确的非线性逻辑结构,元素与元素间的位置关系也有着明确的标准,交换两个元素,逻辑结构就被破环了。关联式容器中的数据是按照键值来进行保存和访问的。常见的关联式容器有:set系列、map系列、unordered_set系列、unordered_map系列。
- set和map是由红黑树实现的,红黑树是一个平衡二叉搜索树。set是<key>场景的结构,map是<key,value>场景的结构。
一、set
1.1 set介绍
在C++中,map是一种关联性容器,用于存储唯一的值。底层用二叉搜索树(红黑树)来实现。用于对数据的有序存储和快速查找。
1.2 set功能
set是一个类模板,T可以是任意类型,后两个参数一般不需要显式传递,如果想改变比较逻辑,可以传递第二个参数Compare。
- set会按照一定顺序存储元素。使用迭代器能得到有序序列。
- 元素的内容不能直接修改。
- set中的元素都是唯一的,具有去重的特点。
- set是一颗平衡二叉搜索树,查找效率为。
1.3 set成员函数
1.3.1 构造函数
以下是常用的构造方式:
int main()
{
//无参默认构造
set<int> s1;
//迭代器区间构造
string str = "celia";
set<int> s2(str.begin(), str.end());
//拷贝构造
set<int> s3 = s2;
return 0;
}
1.3.2 迭代器
- begin:获取容器中第一个元素的正向迭代器。
- end:获取容器中最后一个元素下一个位置的正向迭代器。
- rbegin:获取容器中最后一个元素的反向迭代器。
- rend:获取容器中第一个元素前一个位置的反向迭代器。
int main()
{
vector<int> v = { 9,6,4,7,2,1,3,5,0,8 };
//迭代器区间构造
set<int> s1(v.begin(), v.end());
//正向迭代器遍历
set<int>::iterator it = s1.begin();
while (it != s1.end())
{
cout << *it << ' ';
it++;
}
cout << endl;
//反向迭代器遍历
set<int>::reverse_iterator rit = s1.rbegin();
while (rit != s1.rend())
{
cout << *rit << ' ';
rit++;
}
cout << endl;
return 0;
}
1.3.3 其他成员函数
insert | 插入一个元素 |
erase | 删除一个元素 |
find | 查找一个元素,返回一个迭代器 |
size | 返回容器元素个数 |
empty | 判断容器是否为空 |
swap | 交换两个容器中的数据 |
clear | 清空容器 |
count | 查找容器中指定元素的个数 |
int main()
{
set<int> s1;
//插入元素
s1.insert(8);
s1.insert(5);
s1.insert(3);
s1.insert(9);
s1.insert(4);
//插入返回一个pair<set<int>::iterator, bool>
auto pair = s1.insert(8);
if (!pair.second) cout << "容器中已有该元素,插入失败" << endl;
//删除元素
for (auto x : s1) { cout << x << ' '; }cout << endl;
s1.erase(s1.begin());//迭代器
for (auto x : s1) { cout << x << ' '; }cout << endl;
s1.erase(8);//指定元素
for (auto x : s1) { cout << x << ' '; }cout << endl;
s1.erase(s1.begin(), s1.end());//迭代器区间
for (auto x : s1) { cout << x << ' '; }cout << endl;
return 0;
}
insert插入元素时,会返回一个pair键值对(pair<set<T>::iterator, bool>),pair是一个类模板,其中有两个成员。insert返回的pair中,第二个成员表示插入是否成功,如果成功,第一个成员为新插入元素的迭代器,如果失败,第一个成员为与插入元素值相同的等效元素迭代器。
int main()
{
set<int> s1;
s1.insert(8);
s1.insert(5);
s1.insert(3);
s1.insert(7);
s1.insert(6);
//元素个数
cout << s1.size() << endl;
//查找元素,返回一个迭代器
set<int>::iterator find = s1.find(3);
cout << *find << endl;
//判空
cout << s1.empty() << endl;
return 0;
}
int main()
{
set<int> s1;
s1.insert(8);
s1.insert(5);
s1.insert(3);
s1.insert(7);
s1.insert(6);
set<int> s2;
s2.insert(0);
s2.insert(1);
s2.insert(2);
//交换
s1.swap(s2);
for (auto x : s1)
cout << x << ' ';
cout << endl;
//清空
s1.clear();
cout << s1.empty() << endl;
return 0;
}
二、map
2.1 map介绍
map与set的底层实现几乎一模一样,不同的地方在于,map中存储的是一个键值对pair。
pair中有两个成员,分别为T1类型的key,和T2类型的value。在map中,value的值可以改变,key的值不能直接改变。而二叉树中大小比较的规则是按照key的值进行比较的。
2.2 map功能
- 按照特定次序(基于键值
key
比较)存储由键值key
和值value
组成的元素,使用迭代器遍历可得到有序序列。- 键值
key
用于排序和唯一标识元素,值value
存储与键值关联的内容,键值和值的类型可不同。在内部,通过成员类型value_type
将键值和值绑定并取别名为pair
。- 元素的键值
key
不能修改,而值value
可以修改,因为底层的二叉搜索树基于键值构建,而非值。- 内部按照键值进行比较排序,默认键值按小于比较,若未传入内部比较对象。
- 支持下标访问符,在
[]
中放入键值key
,可获取对应的value
。- map是一颗平衡二叉搜索树,查找效率为。
2.3 map成员函数
2.3.1 构造函数
map提供了以上方式的构造函数。
int main()
{
int arr[] = { 0,1,2,3,4,5 };
//默认无参构造
map<int, int> m1;
//迭代器构造
map<int, int> m2(m1.begin(), m1.end());
//拷贝构造
map<int, int> m3(m2);
return 0;
}
2.3.2 迭代器
- begin:获取容器中第一个元素的正向迭代器。
- end:获取容器中最后一个元素下一个位置的正向迭代器。
- rbegin:获取容器中最后一个元素的反向迭代器。
- rend:获取容器中第一个元素前一个位置的反向迭代器。
int main()
{
map<int, string> m1;
//插入
m1.insert(pair<int, string>(1, "one"));
m1.insert(pair<int, string>(2, "two"));
m1.insert(pair<int, string>(3, "three"));
//另一种方式
m1.insert({4, "four"});
m1.insert({5, "five"});
//正向迭代器
map<int, string>::iterator it = m1.begin();
while (it != m1.end())
{
cout << it->first << " " << it->second << endl;
it++;
}
cout << "==============================" << endl;
//反向迭代器
map<int, string>::reverse_iterator rit = m1.rbegin();
while (rit != m1.rend())
{
cout << rit->first << " " << rit->second << endl;
rit++;
}
return 0;
}
2.3.3 其他成员函数
insert | 插入元素 |
erase | 删除指定key值元素 |
find | 查找元素 |
size | 返回容器中元素数量 |
empty | 判空 |
clear | 清空容器 |
count | 返回容器中指定元素值的元素个数 |
swap | 交换两个容器中的数据 |
[]运算符重载 | 根据对应的key获取其val的值 |
有一部分成员函数的用法相同,这里着重介绍两个成员函数:insert和[]运算符重载。
2.3.3.1 insert
int main()
{
map<int, string> m1;
//插入
m1.insert(pair<int, string>(1, "one"));
m1.insert(pair<int, string>(2, "two"));
m1.insert(pair<int, string>(3, "three"));
m1.insert({ 4, "four" });
m1.insert({ 5, "five" });
map<int, string>::iterator it = m1.begin();
while (it != m1.end())
{
cout << it->first << " " << it->second << endl;
it++;
}
cout << "=============================" << endl;
//删除
m1.erase(3);
it = m1.begin();
while (it != m1.end())
{
cout << it->first << " " << it->second << endl;
it++;
}
cout << "=============================" << endl;
m1.erase(4);
it = m1.begin();
while (it != m1.end())
{
cout << it->first << " " << it->second << endl;
it++;
}
return 0;
}
- 由于map中存放的是键值对pair<T1, T2>,所以插入的时候需要构造一个pair来插入:
//构造匿名对象 m1.insert(pair<int, string>(1, "one")); //也可以这样写 m1.insert({ 4, "four" }); //等同pair<int, string>(4, "four") //也可以通过make_pair函数的返回值进行构造 m1.insert(make_pair(1, "one"));
- 利用迭代器访问时,不能直接解引用it,而是需要通过it->来访问key和value。
- insert函数的返回值也是一个pair<iterator, bool>。
若插入元素的key在map中不存在,则返回新插入元素的迭代器和true。
若插入元素的key在map中已经存在,则返回map中与key值相等元素的迭代器和false。
2.3.3.2 []运算符重载
map的[]运算符重载比较特殊,兼具插入、查找、修改为一体。
函数原型:
mapped_type& operator[] (const key_type& k);
mpped_type是value的类型, key_type是key的类型。
函数底层实现方式:
(*((this->insert(make_pair(k, mapped_type()))).first)).second
这个地方可以分为三步:
- 在函数内用this指针调用insert函数,插入一个键值对。
- 接收insert函数的返回值(pair<iterator, bool>),并且取得这个pair的first(iterator)。
- 对iterator解引用,取到其中的value值。
如果插入的元素在map中不存在,[]就相当于插入新元素,如果插入的元素在map中存在,[]就相当于拿到map中与待插入key值相同的元素的value。
有了这样的实现方式,我们就可以简化map函数的插入操作:
int main()
{
map<int, string> m;
m[0] = "zero";
m[1] = "one";
m[2] = "two";
map<int, string>::iterator it = m.begin();
while (it != m.end())
{
cout << it->first << " " << it->second << endl;
it++;
}
return 0;
}
三、multiset
multiset与set的使用方法基本一致,唯一的区别是multiset允许存储重复元素。
int main()
{
multiset<int> s;
s.insert(0);
s.insert(0);
s.insert(1);
s.insert(1);
s.insert(2);
s.insert(2);
for (auto x : s) { cout << x << ' '; }cout << endl;
return 0;
}
在这里需要注意, multiset的find函数返回的是二叉树中序遍历的第一个值为key元素的迭代器。
四、multimap
multimap与map是使用方式基本一致,唯一的区别是multimap允许存储重复元素。
int main()
{
multimap<int, string> m;
m.insert({ 0, "zero"});
m.insert({ 0, "zero"});
m.insert({ 1, "one" });
m.insert({ 1, "one" });
m.insert({ 2, "two" });
m.insert({ 2, "two" });
map<int, string>::iterator it = m.begin();
while (it != m.end())
{
cout << it->first << " " << it->second << endl;
it++;
}
return 0;
}
这里需要注意 ,multimap的find函数返回的是二叉树中序遍历的第一个值为key元素的迭代器。并且multimap并没有重载[]运算符,因为允许重复元素的存在,不能确定返回哪一个节点的value。