基本概念
set与multiset 容器定义于头文件,并位于 std 命名空间中。
set 集合容器,属于关联式容器,内部数据结构为二叉树,有以下几个特性:
- set 容器专门用于存储键和值相等的键值对,因此该容器中真正存储的是各个键值对的值(value)
- set 容器在存储数据时,会根据各元素值的大小对存储的元素进行排序(默认做升序排序);
- 存储到 set 容器中的元素,虽然其类型没有明确用 const 修饰,但正常情况下它们的值是无法被修改的;
- set 容器存储的元素不能产重复。
multiset 容器,与set容器唯一的区别,就是 multiset 允许元素重复。
构造函数
功能描述:创建set容器以及赋值
构造:
set<T> st;
//默认构造函数:set(const set &st);
//拷贝构造函数
赋值:
set& operator=(const set &st);
//重载等号操作符
#include <iostream>
#include<string>
#include<set>
using namespace std;
template<class T>
void print_set(const T& myset) {
for (set<int>::const_iterator p = myset.begin(); p != myset.end(); p++) {
cout << *p << " ";
}
cout << endl;
}
/*构造函数*/
void test41_1() {
/*默认无参构造函数*/
// set 容器,数据插入自动排序,元素不能有重复
// set 容器插入数据只有 insert 方法
set<int> s1;
s1.insert(5);
s1.insert(1);
s1.insert(3);
s1.insert(2);
s1.insert(5);
print_set(s1); // 1 2 3 5
// multiset 容器,数据插入自动排序,元素可以重复
multiset<int> ms1;
ms1.insert(5);
ms1.insert(1);
ms1.insert(3);
ms1.insert(2);
ms1.insert(5);
print_set(ms1); // 1 2 3 5 5
/*拷贝构造函数*/
set<int> s2(s1);
print_set(s2); // 1 2 3 5
multiset<int> ms2(ms1);
print_set(ms2); // 1 2 3 5 5
}
int main() {
test41_1();
return 0;
}
容器大小与元素交换操作
功能描述:
- 统计set容器大小以及交换set容器
函数原型:
size();
//返回容器中元素的数目empty();
//判断容器是否为空swap(st);
//交换两个集合容器
set容器中不能重置(resize)容器大小。
/*set容器的大小与元素交换*/
void test41_2() {
set<int> s1;
s1.insert(5);
s1.insert(7);
s1.insert(4);
s1.insert(1);
print_set(s1); // 1 4 5 7
set<int> s2;
s2.insert(10);
s2.insert(14);
s2.insert(8);
print_set(s2); // 8 10 14
/*获取set容器中的元素个数*/
cout << "s1大小:" << s1.size() << endl; // s1大小:4
/*判断set容器是否为空*/
cout << "s1是否为空:" << (s1.empty() ? "为空" : "不为空") << endl; // s1是否为空:不为空
/*交换s1与s2中的元素*/
s1.swap(s2);
print_set(s1); // 8 10 14
print_set(s2); // 1 4 5 7
}
元素插入与删除
功能描述:
- set容器进行插入数据和删除数据
函数原型:
insert(elem);
//在容器中插入元素。clear();
//清除所有元素erase(pos);
//删除pos迭代器所指的元素,返回下一个元素的迭代器。erase(beg, end);
//删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。erase(elem);
//删除容器中值为elem的元素。
void test41_3() {
set<int> s;
/*只能使用insert函数插入元素*/
s.insert(7);
s.insert(1);
s.insert(3);
s.insert(2);
s.insert(4);
print_set(s); // 1 2 3 4 7
/*删除迭代器指向位置的元素*/
// 只读迭代器:指向第一个元素 1
set<int>::const_iterator p = s.begin();
p++; // 向后移动一个位置,指向元素 2
cout << *p << endl; // 2
// 删除当前执行的元素 2,返回指向下一个元素 3 的迭代器
set<int>::const_iterator p_next = s.erase(p);
print_set(s); // 1 3 4 7
cout << *p_next << endl; // 3
// 原本p指向的元素被删除后,对应的迭代器p也就失效,不能再使用
// cout << *p << endl; // 运行报错
cout << "********************" << endl;
/*删除迭代器指向区间内的元素*/
set<int> s2;
s2.insert(7);
s2.insert(1);
s2.insert(3);
s2.insert(2);
s2.insert(4);
print_set(s2); // 1 2 3 4 7
set<int>::const_iterator p_start = s2.begin(); // 指向第1个元素 1
cout << *p_start << endl; // 1
p_start++; // 指向第二个元素 2
cout << *p_start << endl; // 2
set<int>::const_iterator p_end = s2.end(); // 指向最后一个元素7的下一个位置
p_end--; // 指向最后一个元素 7
cout << *p_end << endl; // 7
// 删除 [p_start,p_end) 区间内的元素:2 3 4
s2.erase(p_start, p_end);
print_set(s2); // 1 7
/*根据值删除元素*/
s2.erase(1); // 删除值为 1 的元素
print_set(s2); // 7
/*清空容器*/
s2.clear();
print_set(s2);
}
元素查找与统计
功能描述:
- 对set容器进行查找数据以及统计数据
函数原型:
find(key);
//查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();count(key);
//统计key的元素个数
void test41_4() {
set<int> s;
s.insert(7);
s.insert(1);
s.insert(3);
s.insert(2);
s.insert(4);
print_set(s); // 1 2 3 4 7
/*查找某个元素,找到返回指向元素的迭代器,没有找到就返回指向容器最后一个元素下一个位置的迭代器*/
// 查找 4
set<int>::iterator p1 = s.find(4);
if (p1 == s.end()) {
cout << "未找到" << endl;
}
else {
cout << "找到了:" << *p1 << endl; // 找到了:4
}
// 查找20
set<int>::iterator p2 = s.find(20);
if (p2 == s.end()) {
cout << "未找到" << endl; // 未找到
}
else {
cout << "找到了:" << *p2 << endl;
}
/*统计某个元素出现的次数 set 集合的统计结果只能是 0或者 1*/
cout << s.count(4) << endl; // 1
cout << s.count(20) << endl; // 0
multiset<int> ms;
ms.insert(2);
ms.insert(1);
ms.insert(2);
ms.insert(5);
ms.insert(1);
ms.insert(4);
ms.insert(3);
print_set(ms); // 1 1 2 2 3 4 5
cout << ms.count(2) << endl; // 2
cout << ms.count(1) << endl; // 2
cout << ms.count(3) << endl; // 1
}
set 和 multiset 的区别
区别:
- set不可以插入重复数据,而multiset可以
- set插入数据的同时会返回插入结果,表示插入是否成功
- multiset不会检测数据,因此可以插入重复数据
void test41_5(){
set<int> s1;
pair<set<int>::iterator,bool> result = s1.insert(1);
// 判断是否插入成功
if (result.second) {
// 通过返回的迭代器获取插入成功的元素
cout << "插入成功:" << *result.first << endl;
}
else {
cout << "插入失败" << endl;
}
pair<set<int>::iterator,bool> result2 = s1.insert(1);
if (result2.second) {
cout << "插入成功:" << *result2.first << endl;
}
else {
cout << "插入失败" << endl;
}
}
pair 对组
功能描述:
- 成对出现的数据,利用对组可以返回两个数据
两种创建方式:
pair<type, type> p ( value1, value2 );
pair<type, type> p = make_pair( value1, value2 );
void test41_6() {
/*创建对组 方式1*/
pair<string, int> p1("张三", 25);
// 获取对组中的第一个值
cout << "第一个值,姓名:" << p1.first << endl; // 第一个值,姓名:张三
// 获取对组中的第二个值
cout << "第二个值,年龄:" << p1.second << endl; // 第二个值,年龄:25
/*创建对组 方式2*/
pair<string, int> p2 = make_pair("李四",26);
// 获取对组中的第一个值
cout << "第一个值,姓名:" << p2.first << endl; // 第一个值,姓名:李四
// 获取对组中的第二个值
cout << "第二个值,年龄:" << p2.second << endl; // 第二个值,年龄:26
}
set 容器排序规则
set容器默认排序规则为从小到大,利用仿函数,可以改变排序规则。
set 容器存放内置基本数据类型
利用仿函数可以指定set容器的排序规则。
自定义一个比较器 MyIntCompare 类,重载 operator()
操作符,函数体返回两个 int 值比较的结果用于排序。如下:
#include <iostream>
#include <string>
#include <set>
using namespace std;
class MyIntCompare {
public:
// 重载小括号操作符
bool operator()(int v1, int v2){
// 降序
return v1 > v2;
}
};
int main() {
set<int> s1;
s1.insert(5);
s1.insert(1);
s1.insert(4);
s1.insert(2);
s1.insert(3);
// int 类型 默认升序排序
for (set<int>::const_iterator p = s1.begin(); p != s1.end(); p++) {
cout << *p << " ";
}
cout << endl; // 1 2 3 4 5
// 构建set时,指定排序规则
set<int,MyIntCompare> s2;
s2.insert(5);
s2.insert(1);
s2.insert(4);
s2.insert(2);
s2.insert(3);
for (set<int, MyIntCompare>::const_iterator p2 = s2.begin(); p2 != s2.end(); p2++) {
cout << *p2 << " ";
}
cout << endl;
return 0;
}
程序编译失败,报错:
1>C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.29.30133\include\xutility(1455,1):
error C3848: 具有类型“const MyIntCompare”的表达式会丢失一些 const-volatile 限定符以调用“bool MyIntCompare::operator ()(int,int)”
参考
volatile影响编译器编译的结果,指volatile 变量是随时可能发生变化的,与volatile变量有关的运算,不要进行编译优化,以免出错,(VC++ 在产生release版可执行码时会进行编译优化,加volatile关键字的变量有关的运算,将不进行编译优化。)
volatile 告诉编译器i是随时可能发生变化的,每次使用它的时候必须从i的地址中读取,因而编译器生成的可执行码会重新从i的地址读取数据放在k中。而优化做法是,由于编译器发现两次从i读数据的代码之间的代码没有对i进行过操作,它会自动把上次读的数据放在k中。而不是重新从i里面读
报错内容大概意思为:传入的参数表达式应具有“const myCompare”类型,而你调用的“bool myCompare::operator ()(int,int)”不具备const属性,会丢失const限定,所以无法通过编译。
故只要将operator()具有const属性便可,如下
#include <iostream>
#include <string>
#include <set>
using namespace std;
class MyIntCompare {
public:
// 重载小括号操作符
//
bool operator()(int v1, int v2) const{
// 降序
return v1 > v2;
}
};
int main() {
set<int> s1;
s1.insert(5);
s1.insert(1);
s1.insert(4);
s1.insert(2);
s1.insert(3);
// int 类型 默认升序排序
for (set<int>::const_iterator p = s1.begin(); p != s1.end(); p++) {
cout << *p << " ";
}
cout << endl; // 1 2 3 4 5
// 构建set时,指定排序规则
set<int,MyIntCompare> s2;
s2.insert(5);
s2.insert(1);
s2.insert(4);
s2.insert(2);
s2.insert(3);
for (set<int, MyIntCompare>::const_iterator p2 = s2.begin(); p2 != s2.end(); p2++) {
cout << *p2 << " ";
}
cout << endl; // 5 4 3 2 1
return 0;
}
set容器存放自定义数据类型
对于自定义数据类型,set构建时必须指定排序规则才可以插入数据。
#include <iostream>
#include <string>
#include <set>
using namespace std;
class Cat {
public:
string name;
int age;
Cat(string name,int age) {
this->name = name;
this->age = age;
}
};
// Cat 比较器
class CatCompare {
public:
bool operator()(const Cat& c1, const Cat& c2) const {
// 根据年龄升序排序
return c1.age < c2.age;
}
};
// 重载左移运算符
ostream& operator<<(ostream& out, const Cat& cat) {
out << "姓名:" << cat.name << ",年龄:" << cat.age;
return out;
}
int main() {
Cat c1("红猫", 13);
Cat c2("蓝猫", 9);
Cat c3("黑猫", 8);
Cat c4("橘猫", 11);
// 构建set容器时,没有传入对应的比较规则,无法插入数据
set<Cat> s1;
// 报错: error C2676: 二进制“<”:“const _Ty”不定义该运算符或到预定义运算符可接收的类型的转换
s1.insert(c1);
return 0;
}
在构建 set 容器时,传入指定的排序规则
int main() {
Cat c1("红猫", 13);
Cat c2("蓝猫", 9);
Cat c3("黑猫", 8);
Cat c4("橘猫", 11);
// 构建set容器时,传入对应的比较规则
set<Cat,CatCompare> s1;
s1.insert(c1);
s1.insert(c2);
s1.insert(c3);
s1.insert(c4);
for (set<Cat>::const_iterator p = s1.begin(); p != s1.end(); p++) {
cout << *p << endl;
}
/*
姓名:黑猫,年龄:8
姓名:蓝猫,年龄:9
姓名:橘猫,年龄:11
姓名:红猫,年龄:13
*/
return 0;
}