Cpp浅析系列-STL之set_cpp strtok
zhezhongyun 2025-09-06 16:13 43 浏览
前言
CPP
集合(Set)t是一种元素唯一的、包含已排序对象的数据容器。
C++ STL中标准关联容器set, multiset, map, multimap内部采用的就是一种非常高效的平衡检索二叉树:红黑树,也称为RB树(Red-Black Tree)。RB树的统计性能要好于一般平衡二叉树,所以被STL选择作为了关联容器的内部结构。
对于map和set这种关联容器来说,不需要做内存拷贝和内存移动。以节点的方式来存储,其节点结构和链表差不多。
博主当前的CPP版本为c++14,所以相关源码内容就是以这个版本的为准。
Set定义
#include<set>
using namespace std;
set<int> s1;//空对象
set<int> s2{3, 4, 2, 1};//列表清单,默认less递增 ,输出为{1,2,3,4}
set<int, greater<int> > s3{6, 5, 7, 8};//列表清单 ,输出为{8.7.6.5}
Set常规操作
支持正向和反向迭代器,但是不支持随机迭代器访问元素。
C++中文在线手册:
https://zh.cppreference.com/
begin()返回指向第一个元素的迭代器clear()清除所有元素count()返回某个值元素的个数,0或1,可以用来判断是否存在某数empty()如果集合为空,返回trueend()返回指向最后一个元素的迭代器equal_range()返回集合中与给定值相等的上下限的两个迭代器erase()删除集合中的元素
set集合中的删除操作是不进行任何的错误检查的,比如定位器的是否合法等等,所以用的时候自己一定要注意。find()返回一个指向被查找到元素的迭代器;如果没有找到,返回指向集合最后一个元素的迭代器。get_allocator()返回集合的分配器insert()在集合中插入元素
可以在集合中插入其他数组中指定个数的值lower_bound()返回指向大于(或等于)某值的第一个元素的迭代器key_comp()返回一个用于元素间值比较的函数max_size()返回集合能容纳的元素的最大限值rbegin()返回指向集合中最后一个元素的反向迭代器rend()返回指向集合中第一个元素的反向迭代器size()集合中元素的数目swap()交换两个集合变量upper_bound()返回大于某个值元素的迭代器value_comp()返回一个用于比较元素间的值的函数
增加元素
insert插入
允许多个元素的插入,使用迭代器指定位置。
/*
* insert能插入多个,慢但是实用
* */
void add1() {
set<int> demo{1, 2};
//在第一个元素后面插入3
demo.insert(demo.begin()++, 3);//{1,2,3},结果遵循递增规则
//直接插入元素,也是按照规则排列
demo.insert(-1);//{-1,1,2,3}
//C++11之后,可以用emplace_hint或者emplace替代
//插入其他容器的部分序列
vector<int> test{7, 8, 9};
demo.insert(++test.begin(), --test.end()); //{-1,1,2,3,8}
//插入初始化列表
demo.insert({10, 11}); //{-1,1,2,3,8,10,11}
set<int> s = demo;
set<int>::iterator iter;
for (iter = s.begin(); iter != s.end(); ++iter) {
cout << *iter << " ";
}
}
emplace插入
注意是每次只能插入一个,而且是只有构造函数,效率高!
/*
* emplace,只能插入一个
* 而且如果元素已经存在,是不会再次插入的
* */
void add2() {
set<int> demo{1, 2};
demo.emplace(4);//{1,2,4}
demo.emplace(4);//还是{1,2,4}
set<int> s = demo;
set<int>::iterator iter;
for (iter = s.begin(); iter != s.end(); ++iter) {
cout << *iter << " ";
}
}
遍历元素
/*
* 直接用迭代器,注意const_iterator还是iterator
* */
void search() {
set<int> demo{1, 2};
// 如果参数为const vector<int> 需要用const_iterator
// vector<int>::const_iterator iter=v.begin();
set<int> s = demo;
set<int>::iterator iter;
for (iter = s.begin(); iter != s.end(); ++iter) {
cout << *iter << " ";
}
}
删除元素
/*
* 删除有两种方式,
* clear是直接清空
* erase是删除指定迭代器范围内的数字
* 也可以用来删除指定的单个元素
* */
void del() {
set<int> demo{1, 2, 3, 4, 5};
//清空
demo.clear();//{}
if (demo.empty()) {//判断Vector为空则返回true
demo.insert({6, 7, 8, 9, 10, 11});//{ 6, 7, 8, 9, 10, 11 }
//删除第一个元素
demo.erase(demo.begin());//{7, 8, 9, 10, 11 }
// 删除指定元素
demo.erase(11);
//删除前三个
demo.erase(demo.begin(), next(demo.begin(), 3)); //{ 10, 11 }
// 只保留最后一个
demo.erase(demo.begin(), ++demo.end()); //{11}
}
set<int> s = demo;
set<int>::iterator iter;
for (iter = s.begin(); iter != s.end(); ++iter) {
cout << *iter << " ";
}
}
比较函数
元素为基本类型
使用函数对象
当元素是基本类型,一般是变动规则为递增或者递减。
推荐使用自带的less或者greater函数比较。
// 基础类型,变动规则为递增或者递减
void BasicCompare() {
set<int, less<int> > s2{6, 5, 7, 8};//小数靠前。递增,输出为{5,6,7,8}
set<int, greater<int> > s3{6, 5, 7, 8};//大数靠前。递减,输出为{8,7,6,5}
// 遍历查看内容
set<int> s = s2;
set<int>::iterator iter;
for (iter = s.begin(); iter != s.end(); ++iter) {
cout << *iter << " ";
}
}
类外重载括号
当元素是基本类型,变动规则后不能用自带的less或者greater函数比较,就只能新建一个结构体来描述规则。这种方式是适用性最广的。
// 基础类型,但是有特殊的比较规则
// 此处以递增递减为例
// 当然也可以指定为其他自定义类型
struct Special {
bool operator()(const int &a, const int &b) {
// return a > b;//等价greater<int>
return a < b;//等价less<int>
}
};
// 基础类型需要类外重载
void SpecialCompare() {
set<int, Special> s2;
s2.emplace(3);
s2.emplace(2);
s2.emplace(1);
s2.emplace(4);
// 遍历查看内容
set<int, Special> s = s2;
set<int>::iterator iter;
for (iter = s.begin(); iter != s.end(); ++iter) {
cout << *iter << " ";
}
}
/*1 2 3 4 */
元素为自定义类型
当元素是自定义结构体,可以在类外重载括号();也可以在类内重载小于号<,例子如下。
类内重载小于号
// 自定义结构体
struct Info {
string name;
float score;
Info() {
name = "a";
score = 60;
}
Info(string sname, float fscore) {
name = sname;
score = fscore;
}
// 类内重载方法
bool operator<(const Info &a) const {
/*名字递减;若名字相同,则分数递减*/
if (a.name < name)
return true;
else if (a.name == name) {
return a.score < score;
} else
return false;
}
};
void InfoCompare() {
pair<set<Info>::iterator, bool> result;//获取插入的结果
set<Info> s;
// 插入默认对象和指定对象
s.insert(Info());
s.insert(Info("a", 53));
s.insert(Info("keen", 68));
result = s.insert(Info("keen", 60));
// 遍历查看内容
for (auto iter = s.begin(); iter != s.end(); ++iter) {
cout << iter->name << " " << iter->score << endl;
}
cout << "插入元素的信息:" << result.first->name << " " << result.first->score << endl;
cout << "插入是否成功" << boolalpha << result.second << endl;
// 再次插入相同元素
result = s.insert(Info("keen", 60));
cout << "插入元素的信息:" << result.first->name << " " << result.first->score << endl;
cout << "插入是否成功" << boolalpha << result.second << endl;
}
/*
keen 68
keen 60
a 60
a 53
插入元素的信息:keen 60
插入是否成功true
插入元素的信息:keen 60
插入是否成功false
*/
类外重载括号
#include <iostream>
#include <set>
using namespace std;
/*Student结构体*/
struct Student {
string name;
int age;
string sex;
Student() {}
Student(const string &name, int age, const string &sex) : name(name), age(age), sex(sex) {}
};
/*
* 为Student指定排序准则
* 先比较名字;若名字相同,则比较年龄。小的返回true
* 如果想要部分参数匹配,可以用正则表达式来规定
* */
class StudentCompare {
public:
bool operator()(const Student &a, const Student &b) const {
if (a.name < b.name)
return true;
else if (a.name == b.name) {
if (a.age < b.age)
return true;
else
return false;
} else
return false;
}
};
int main() {
set<Student, StudentCompare> stuSet;
stuSet.insert(Student("张三", 13, "male"));
stuSet.insert(Student("李四", 23, "female"));
/*构造一个用来查找的临时对象,可以看到,即使stuTemp与stu1实际上并不是同一个对象,
*但当在set中查找时,仍能够查找到集合中的元素成功。
*这是因为已定义的StudentCompare的缘故。
*/
Student stuTemp;
stuTemp.name = "张三";
stuTemp.age = 13;
set<Student, StudentCompare>::iterator iter;
iter = stuSet.find(stuTemp);
if (iter != stuSet.end()) {
cout << (*iter).name << " " << (*iter).age << " " << (*iter).sex << endl;
} else {
cout << "Cannot fine the student!" << endl;
}
return 0;
}
查找函数
count统计元素个数
count函数是用来统计一个元素在当前容器内的个数。由于Set的特性,所以只能返回1或者0。
/*
* 用count函数寻找元素,
* */
void find1(set<int> s ){
if (s.count(4) == 1) {
cout << "元素4存在"<<endl;
}
if (s.count(8) == 0) {
cout << "元素8不存在";
}
}
追查源码,我发现他是用的红黑树的__count_unique方法,从根节点开始比对,如果要查找的元素是比当前节点的数值要小就去左子树找,如果要查找的元素是比当前节点的数值要大就去右子树找,当匹配到节点值相等时,就返回1;当到最后的叶子结点仍然没有找到就返回0。
底层是使用的红黑树,所以查找起来还是很方便的。
源码如下。
size_type __tree<_Tp, _Compare, _Allocator>::__count_unique(const _Key& __k) const
{
__node_pointer __rt = __root();
while (__rt != nullptr)
{
if (value_comp()(__k, __rt->__value_))
{
__rt = static_cast<__node_pointer>(__rt->__left_);
}
else if (value_comp()(__rt->__value_, __k))
__rt = static_cast<__node_pointer>(__rt->__right_);
else
return 1;
}
return 0;
}
find获取元素迭代器
/*
* 用find函数寻找元素,
* */
void find2(set<int> s ){
if (s.find(4)!= s.end() ) {
cout << "元素4存在"<<endl;
}else{
cout << "元素4不存在";
}
if (s.find(8)!= s.end() ) {
cout << "元素8存在"<<endl;
}else{
cout << "元素8不存在";
}
}
追查源码,我发现他是用的红黑树的find方法,从根节点开始比对,如果要查找的元素是比当前节点的数值要大就去右子树找;如果查找的元素比当前节点的数值要小于等于匹配到节点值时,就赋值一次结果节点并去左子树找。一直到最后所查找的节点为空再结束。最终返回结果节点。
源码如下。
iterator __lower_bound(const _Key& __v,__node_pointer __root,__iter_pointer __result)
{
while (__root != nullptr)
{
if (!value_comp()(__root->__value_, __v))
{
__result = static_cast<__iter_pointer>(__root);
__root = static_cast<__node_pointer>(__root->__left_);
}
else
__root = static_cast<__node_pointer>(__root->__right_);
}
return iterator(__result);
}
暂时看来,两个方法的底层实现逻辑是相似的,都是用平衡二叉树的方式去寻找节点。
区别是count返回1或0来标明元素是否存在;find函数是返回指针可以方便下一步修改或者取用。
感谢
谨以此文,送给未来笨兮兮又回来看的博主自己
哦,我看到了,过去那个很努力但是很皮的我哦
感谢现在努力的自己。
感谢现在的好奇,为了能成为更好的自己。
离线下载链接
C++中set用法详解
C++ STL set::find的用法
C++ STL set容器完全攻略
相关推荐
- Python入门学习记录之一:变量_python怎么用变量
-
写这个,主要是对自己学习python知识的一个总结,也是加深自己的印象。变量(英文:variable),也叫标识符。在python中,变量的命名规则有以下三点:>变量名只能包含字母、数字和下划线...
- python变量命名规则——来自小白的总结
-
python是一个动态编译类编程语言,所以程序在运行前不需要如C语言的先行编译动作,因此也只有在程序运行过程中才能发现程序的问题。基于此,python的变量就有一定的命名规范。python作为当前热门...
- Python入门学习教程:第 2 章 变量与数据类型
-
2.1什么是变量?在编程中,变量就像一个存放数据的容器,它可以存储各种信息,并且这些信息可以被读取和修改。想象一下,变量就如同我们生活中的盒子,你可以把东西放进去,也可以随时拿出来看看,甚至可以换成...
- 绘制学术论文中的“三线表”具体指导
-
在科研过程中,大家用到最多的可能就是“三线表”。“三线表”,一般主要由三条横线构成,当然在变量名栏里也可以拆分单元格,出现更多的线。更重要的是,“三线表”也是一种数据记录规范,以“三线表”形式记录的数...
- Python基础语法知识--变量和数据类型
-
学习Python中的变量和数据类型至关重要,因为它们构成了Python编程的基石。以下是帮助您了解Python中的变量和数据类型的分步指南:1.变量:变量在Python中用于存储数据值。它们充...
- 一文搞懂 Python 中的所有标点符号
-
反引号`无任何作用。传说Python3中它被移除是因为和单引号字符'太相似。波浪号~(按位取反符号)~被称为取反或补码运算符。它放在我们想要取反的对象前面。如果放在一个整数n...
- Python变量类型和运算符_python中变量的含义
-
别再被小名词坑哭了:Python新手常犯的那些隐蔽错误,我用同事的真实bug拆给你看我记得有一次和同事张姐一起追查一个看似随机崩溃的脚本,最后发现罪魁祸首竟然是她把变量命名成了list。说实话...
- 从零开始:深入剖析 Spring Boot3 中配置文件的加载顺序
-
在当今的互联网软件开发领域,SpringBoot无疑是最为热门和广泛应用的框架之一。它以其强大的功能、便捷的开发体验,极大地提升了开发效率,成为众多开发者构建Web应用程序的首选。而在Spr...
- Python中下划线 ‘_’ 的用法,你知道几种
-
Python中下划线()是一个有特殊含义和用途的符号,它可以用来表示以下几种情况:1在解释器中,下划线(_)表示上一个表达式的值,可以用来进行快速计算或测试。例如:>>>2+...
- 解锁Shell编程:变量_shell $变量
-
引言:开启Shell编程大门Shell作为用户与Linux内核之间的桥梁,为我们提供了强大的命令行交互方式。它不仅能执行简单的文件操作、进程管理,还能通过编写脚本实现复杂的自动化任务。无论是...
- 一文学会Python的变量命名规则!_python的变量命名有哪些要求
-
目录1.变量的命名原则3.内置函数尽量不要做变量4.删除变量和垃圾回收机制5.结语1.变量的命名原则①由英文字母、_(下划线)、或中文开头②变量名称只能由英文字母、数字、下画线或中文字所组成。③英文字...
- 更可靠的Rust-语法篇-区分语句/表达式,略览if/loop/while/for
-
src/main.rs://函数定义fnadd(a:i32,b:i32)->i32{a+b//末尾表达式}fnmain(){leta:i3...
- C++第五课:变量的命名规则_c++中变量的命名规则
-
变量的命名不是想怎么起就怎么起的,而是有一套固定的规则的。具体规则:1.名字要合法:变量名必须是由字母、数字或下划线组成。例如:a,a1,a_1。2.开头不能是数字。例如:可以a1,但不能起1a。3....
- Rust编程-核心篇-不安全编程_rust安全性
-
Unsafe的必要性Rust的所有权系统和类型系统为我们提供了强大的安全保障,但在某些情况下,我们需要突破这些限制来:与C代码交互实现底层系统编程优化性能关键代码实现某些编译器无法验证的安全操作Rus...
- 探秘 Python 内存管理:背后的神奇机制
-
在编程的世界里,内存管理就如同幕后的精密操控者,确保程序的高效运行。Python作为一种广泛使用的编程语言,其内存管理机制既巧妙又复杂,为开发者们提供了便利的同时,也展现了强大的底层控制能力。一、P...
- 一周热门
- 最近发表
- 标签列表
-
- HTML 教程 (33)
- HTML 简介 (35)
- HTML 实例/测验 (32)
- HTML 测验 (32)
- JavaScript 和 HTML DOM 参考手册 (32)
- HTML 拓展阅读 (30)
- HTML文本框样式 (31)
- HTML滚动条样式 (34)
- HTML5 浏览器支持 (33)
- HTML5 新元素 (33)
- HTML5 WebSocket (30)
- HTML5 代码规范 (32)
- HTML5 标签 (717)
- HTML5 标签 (已废弃) (75)
- HTML5电子书 (32)
- HTML5开发工具 (34)
- HTML5小游戏源码 (34)
- HTML5模板下载 (30)
- HTTP 状态消息 (33)
- HTTP 方法:GET 对比 POST (33)
- 键盘快捷键 (35)
- 标签 (226)
- opacity 属性 (32)
- transition 属性 (33)
- 1-1. 变量声明 (31)
