> 文档中心 > <C++>快速掌握set 容器|去重的底层原因|使用仿函数定义排序规则

<C++>快速掌握set 容器|去重的底层原因|使用仿函数定义排序规则


✅作者简介:热爱后端语言的大学生,CSDN内容合伙人
✨精品专栏:C++面向对象
🔥系列专栏:C++泛型编程

文章目录

  • 1、set 容器基本操作,从构造到查找统计
    • 1.1、set/ multiset 基本概念
    • 1.2、set构造和赋值
    • 1.3、set 大小和交换
    • 1.4、set插入和删除
    • 1.5、set查找和统计
  • 2、set 和 multiset 插入数据特点不同的原因
    • 2.1、set 中的insert 源码分析
    • 2.2、pair 对组的创建及使用
    • 2.3、multiset 中的insert 源码分析
  • 3、内置与自定义数据类型的排序规则
    • 3.1、内置数据类型的排序
    • 3.2、自定义数据类型的排序

🔥前言

set 容器的底层实现是二叉树,在插入元素的时候会自动进行升序的排序操作,set 容器有去重的功能,而 multiset容器允许插入相同元素… set容器在STL编程里常常用到,那么我就总结一下它的用法,抓住源码分析去重排序的原理

1、set 容器基本操作,从构造到查找统计

1.1、set/ multiset 基本概念

特点:

  • 所有元素都会在插入时自动被升序排序

本质:

  • set/multiset属于关联式容器,底层结构是用二叉树实现

二者区别:

  • set不允许容器中有重复的元素
  • multiset允许容器中有重复的元素

1.2、set构造和赋值

功能描述:

  • 创建set容器以及赋值

函数原型:

  • set st; 默认构造函数
  • set(const set &st); 拷贝构造函数
  • set& operator=(const set &st); 重载等号操作符

代码示例:

//遍历set集合void printSet(set<int>& s) {for (set<int>::iterator it = s.begin(); it != s.end(); it++) {cout << *it << " ";}cout << endl;}// 构造操作void test1(){// 默认构造set<int> s1;// 赋值操作s1.insert(4);s1.insert(2);s1.insert(2);s1.insert(1);s1.insert(0);// 拷贝构造set<int> s2(s1);// 重载赋值运算符set<int> s3 = s2;// 调用函数,遍历操作printSet(s1);printSet(s2);printSet(s3);// set 容器插入值使用inset方法}

1.3、set 大小和交换

功能描述:

  • 统计set容器大小以及交换set容器

函数原型:

  • size(); 返回容器中元素的数目
  • empty(); 判断容器是否为空
  • swap(st); 交换两个集合容器

代码示例:

// 大小操作void test2() {set<int> s1;s1.insert(1);s1.insert(0);s1.insert(2);s1.insert(4);if (s1.empty()) {cout << "set 为空" << endl;}else {cout << "set 不为空" << endl;cout << "输出集合内元素:";printSet(s1);cout << "大小为:" << s1.size();}}// 交换操作void test3() {set<int> s1,s2;s1.insert(1);s1.insert(0);s1.insert(2);s1.insert(4);s2.insert(10);s2.insert(20);s2.insert(40);s2.insert(0);cout << "交换前:" << endl;printSet(s1);printSet(s2);cout << "交换后:" << endl;s1.swap(s2);printSet(s1);printSet(s2);}

1.4、set插入和删除

功能描述:

  • set容器进行插入数据和删除数据

函数原型:

  • insert(elem); 在容器中插入元素。
  • clear(); 清除所有元素
  • erase(pos); 删除pos迭代器所指的元素,返回下一个元素的迭代器。
  • erase(beg, end); 删除区间[beg,end)的所有元素 ,返回下一个元素的迭代器。
  • erase(elem); 删除容器中值为elem的元素。

代码示例:

// 插入删除操作void test4() {set<int> s1,s2;s1.insert(1);s1.insert(0);s1.insert(2);s1.insert(20);s1.insert(24);s1.insert(4);s2 = s1;// 遍历printSet(s1);// 按照迭代器删除s1.erase(s1.begin());s1.erase(--s1.end());printSet(s1);// 按照值删除s1.erase(4);printSet(s1);// 清空set ,两种方法s1.erase(s1.begin(), s1.end());printSet(s1);s2.clear();}

1.5、set查找和统计

功能描述:

  • 对set容器进行查找数据以及统计数据

函数原型:

  • find(key); 查找key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回set.end();
  • count(key); 统计key的元素个数

代码示例:

// 查找和统计操作void test5() {set<int> s1;s1.insert(1);s1.insert(0);s1.insert(2);s1.insert(4);//auto pos = s1.find(2);auto pos = s1.find(3);if (pos != s1.end()) {cout << "找到元素,值为:" << *pos << endl;}else {cout << "元素未找到" << endl;}// 统计操作,对于set 而言要么是零要么是一int ans = s1.count(1);int ansl = s1.count(3);cout << ans << " " << ansl;}

auto关键字可用于数据类型的自动推导,可以替换某些情况下迭代器的写法,比较方便

2、set 和 multiset 插入数据特点不同的原因

上面提到二者的不同点在于是否可以插入不同的数据,那么就来看看二者insert插入方法的源码

2.1、set 中的insert 源码分析

查看set 中的insert 源码:

<C++>快速掌握set 容器|去重的底层原因|使用仿函数定义排序规则

  • 可以看到这里的insert返回值类型是一个pair二元组,包含迭代器类型和布尔类型
  • 那么再进行插入操作的时候,如果检查到容器中已经有相同元素,就会返回false,不进行插入
  • 有关二元组的创建将在下面解释

2.2、pair 对组的创建及使用

功能描述:

  • 成对出现的数据,利用对组可以返回两个数据

两种创建方式:

  • pair p ( value1, value2 );
  • pair p = make_pair( value1, value2 );

代码示例:

// pair 对组的使用 make_pair()void testPair() {// 默认构造pair<string, int> pre("微凉", 10);pair<string, int> ptr = make_pair("秋意", 24);// 获取值cout << pre.first << ptr.first << "\n" << pre.second << ptr.second << endl;}

<C++>快速掌握set 容器|去重的底层原因|使用仿函数定义排序规则

  • 推荐使用make_pair的方式创建二元组,这样看起来比较清晰
  • 二元组通过调用firstsecond来访问对应的属性值

2.3、multiset 中的insert 源码分析

查看multiset 中的insert 源码:

<C++>快速掌握set 容器|去重的底层原因|使用仿函数定义排序规则

  • multiset 中的insert 返回值类型是一个迭代器,并没有布尔类型
  • 所以可以插入重复数据

3、内置与自定义数据类型的排序规则

  • set 容器插入时默认自动升序排序,那么怎么修改排序规则呢:
    • 使用仿函数来指定排序规则

3.1、内置数据类型的排序

写一个类,重载类内的函数运算符()

// 修改 set 容器的排序规则class Cmp {public:bool operator()(int v1,int v2) const{return v1 > v2;}};void test7() {// 默认是升序set<int> s1;s1.insert(1);s1.insert(0);s1.insert(2);s1.insert(4);printSet(s1);// 修改为降序,借助仿函数set<int, Cmp> s2;s2.insert(1);s2.insert(0);s2.insert(2);s2.insert(4);for (auto it = s2.begin(); it != s2.end(); it++) {cout << *it << " ";}}
  • 要注意的是重载的函数需要是常函数,要加上const关键字
    • 重载的细节可以参考我这篇博客:详解重载函数调用运算符
  • 创建set 容器的时候就要指定排序规则,在尖括号中这样表示:set

3.2、自定义数据类型的排序

set 容器插入自定义数据的时候如果不指定排序规则,默认的升序排序也无法进行,因此需要事先指定排序规则:

// 定义Person 类class Person {int age;string name;public:Person(string name, int age) {this->name = name;this->age = age;}int getAge() const {return this->age;}string getName() const {return this->name;}};// 自定义排序规则class Compare {public:bool operator()(const Person &p1, const Person &p2)const {return p1.getAge() > p2.getAge();}};void test8() {set<Person, Compare> s;Person p1("叶落", 20);Person p2("微凉", 24);Person p3("秋意", 22);Person p4("秋白", 18);s.insert(p1);s.insert(p2);s.insert(p3);s.insert(p4);for (auto it = s.begin(); it != s.end(); it++) {cout << "姓名:" << it->getName() << "\t年龄:" << it->getAge()<<endl;}}

运行效果:

<C++>快速掌握set 容器|去重的底层原因|使用仿函数定义排序规则

  • 自定义数据类型重载时的要求比较严格:
    • 不仅函数需要加const限制,参数列表中也都要加const
  • 由于Person类中的属性采用了封装,那么在对应的get 方法中也要是常函数

希望以此文章帮助大家快速学会、复习set容器的使用,创作不易,希望大家能够点赞支持!