文章目录
- 1.set和multiset容器
- 1.基本特性
- 2.multiset容器
- 3.set的构造和赋值
- 1.构造函数
- 2.赋值操作
- 3.注意事项
- 4.set的大小和交换
- 1.获取大小
- 2.检查是否为空
- 3.交换两个set
- 4.示例综合
- 5.set容器插入和删除
- 1.插入元素
- 2.删除元素
- 3.示例
- 6.set容器的查找和统计
- 1.查找元素
- 2.统计元素个数
- 3.注意事项
- 9.set和multiset的区别
- 9.pair对组的创建
- 1. 直接构造
- 2. 使用make_pair函数
- 3. 初始化列表
- 4. 结构化绑定(C++17起)
- 5.示例
- 10.set内置数据类型排序
- 11.set对自定义数据类型排序
1.set和multiset容器
C++的std::set
容器是STL(标准模板库)中的一个关联容器,它用于存储唯一对象的集合。每个元素的位置都是由它的值决定的,这意味着std::set
中的元素会自动排序。std::set
底层通常通过红黑树实现,提供了对数时间复杂度的插入、删除和查找操作。
1.基本特性
- 有序性:
std::set
中的元素默认按照升序排序。也可以通过传入自定义的比较函数来改变排序方式。 - 唯一性:不允许有重复的元素,根据元素的排序准则,如果有重复的元素尝试插入,它们会被自动忽略。
- 动态大小:容器的大小会根据需要自动调整。
- 迭代器:提供了向前和向后遍历的迭代器,但不支持随机访问迭代器,因为元素在内存中不是连续存储的。
常用操作
-
构造:
std::set<int> mySet; // 创建一个空的int型set std::set<std::string, std::greater<std::string>> mySetStr; // 使用自定义排序(降序)
-
插入:
mySet.insert(42); // 插入一个元素 mySet.insert({1, 2, 3}); // 插入多个元素
-
查找:
if (mySet.find(42) != mySet.end()) { std::cout << "Found 42 in set.\n"; }
-
删除:
mySet.erase(42); // 删除一个特定值的所有实例 mySet.erase(it); // 删除迭代器it指向的元素 mySet.clear(); // 清空整个集合
-
检查存在性:
if (mySet.count(42)) { std::cout << "42 is in the set.\n"; }
-
大小与空判断:
std::cout << "Size: " << mySet.size() << "\n"; if (mySet.empty()) { std::cout << "Set is empty.\n"; }
-
遍历:
for (const auto& elem : mySet) { std::cout << elem << " "; }
自定义比较函数
可以通过传递比较函数对象作为第二个模板参数来自定义元素的排序规则。例如,如果要创建一个按字符串长度排序的set
,可以这样做:
struct CompareByLength {
bool operator()(const std::string& s1, const std::string& s2) const {
return s1.size() < s2.size();
}
};
std::set<std::string, CompareByLength> mySetByLength;
总结
std::set
容器非常适合需要维护唯一且有序数据集合的场景。它的主要优点在于自动排序和保证元素唯一性,但这也意味着插入和删除操作相比无序容器(如std::unordered_set
)可能更耗时。选择使用std::set
还是其他容器应基于具体的应用需求。
2.multiset容器
在C++的STL(标准模板库)中,std::set
和std::multiset
都是关联容器,主要用于存储元素的集合,它们的主要区别在于对元素唯一性的处理上:
std::set
- 唯一性:
std::set
不允许容器中有重复的元素。这意味着如果你尝试插入一个已经存在于set
中的元素,插入操作将被忽略,容器不会发生任何变化。 - 排序:所有元素都会在插入时自动被排序。排序是基于元素的比较操作符(默认为
<
运算符),或者你可以在创建set
时提供一个自定义的比较函数。 - 用途:适用于需要存储唯一且有序数据的场景,如索引、去重等。
std::multiset
- 重复性:与
std::set
不同,std::multiset
允许容器中有重复的元素。它可以存储多个具有相同值的元素,并且这些相同值的元素在multiset
中的顺序取决于它们的插入顺序。 - 排序:虽然允许重复,但元素仍然是排序的,同样基于比较操作符或自定义比较函数。
- 用途:适合那些需要记录元素出现次数或者保持元素插入顺序的同时还需要维持元素排序的场景,如统计词频、事件日志等。
共同点
- 底层结构:两者通常都采用红黑树实现,因此提供了对数时间复杂度的高效插入、删除和查找操作。
- 迭代器:都提供了迭代器支持,可以用来遍历容器中的元素,但不支持随机访问迭代器,因为它们是基于节点的链式存储结构。
- 动态大小:容器大小会根据元素的插入和删除动态调整。
应用决策
选择使用std::set
还是std::multiset
取决于你的具体需求:
- 如果你的应用逻辑要求集合中的元素必须唯一,那么你应该使用
std::set
。 - 如果你需要保留重复元素并能对它们进行排序和计数,则
std::multiset
是更好的选择。
3.set的构造和赋值
std::set
容器提供了多种构造方法和赋值操作。
1.构造函数
-
默认构造函数:
std::set<T> mySet; // T 是元素类型,例如 int, std::string 等
创建一个空的
set
,元素类型为T,按照T的默认排序方式进行排序。 -
拷贝构造函数:
std::set<T> mySetCopy(mySet);
创建一个新的
set
,它是已有set
(mySet
)的一个拷贝。 -
带有比较函数的构造函数:
struct MyComparator { bool operator()(const T& lhs, const T& rhs) const { /* 自定义比较逻辑 */ } }; std::set<T, MyComparator> mySetWithComparator;
创建一个空的
set
,使用自定义的比较函数MyComparator
。 -
区间构造函数:
std::set<T> mySet(begin(iterable), end(iterable));
从一个迭代器范围(如另一个容器或数组)构造
set
,只包含范围内唯一且排序后的元素。 -
初始化列表构造(C++11起):
std::set<int> mySet = {1, 3, 2}; // 注意自动排序为{1, 2, 3}
2.赋值操作
-
拷贝赋值:
std::set<T> mySet; std::set<T> anotherSet; // ...填充anotherSet mySet = anotherSet; // mySet 现在是 anotherSet 的拷贝
-
区间赋值:
std::set<T> mySet; // ...填充mySet std::set<T> anotherSet; anotherSet.assign(begin(iterable), end(iterable)); // 用 iterable 中的元素替换 anotherSet 的内容
-
初始化列表赋值(C++11起):
std::set<int> mySet = {4, 2, 5, 1}; // 初始化后自动排序为{1, 2, 4, 5}
3.注意事项
- 在赋值和构造时,如果源集合中有重复元素(对于
std::set
而言,这是不可能的情况,因为std::set
不允许重复),目标集合将只保留唯一的元素。 - 所有这些操作都不会影响原集合的迭代器、引用和指针的有效性,除非进行的是自赋值操作(即将一个容器赋值给自己),此时容器保持不变。
- 当涉及到大型集合时,上述操作的性能需考虑,尤其是拷贝构造和赋值可能会涉及大量元素的复制。C++11引入了右值引用和移动语义,可以更高效地处理这类情况。
// 包含标准输入输出库和集合库
#include <iostream>
#include <set>
using namespace std;
/**
* 打印集合中的所有元素
* @param S 需要打印的集合
*/
void printSet(const set<int>& S){
// 遍历集合中的每个元素并打印
for(auto &it : S){
cout << it << " ";
}
cout << endl;
}
int main(){
// 初始化一个集合S并填充元素
set<int> S = {1,2,3,4};
// 调用函数打印集合S
printSet(S);
// 引用集合S,创建常量引用S1
const set<int>& S1(S);
// 调用函数打印引用S1
printSet(S1);
// 初始化一个空集合S2
set<int> S2;
// 将集合S的值赋给集合S2
S2 = S;
// 调用函数打印集合S2
printSet(S2);
return 0;
}
4.set的大小和交换
在C++的std::set
容器中,获取容器的大小以及交换两个容器的内容是常见的操作。
1.获取大小
使用size()
成员函数可以获取std::set
容器中当前元素的数量。
std::set<int> mySet = {1, 2, 3, 4, 5};
std::cout << "Size of mySet: " << mySet.size() << std::endl;
2.检查是否为空
使用empty()
成员函数可以检查std::set
容器是否为空。
if (mySet.empty()) {
std::cout << "mySet is empty." << std::endl;
} else {
std::cout << "mySet is not empty." << std::endl;
}
3.交换两个set
std::set
提供了swap()
成员函数,可以高效地交换两个集合的所有元素,不会抛出异常。
std::set<int> anotherSet = {6, 7, 8, 9, 10};
mySet.swap(anotherSet); // 交换mySet和anotherSet的内容
4.示例综合
// 包含标准输入输出库和集合库
#include <iostream>
#include <set>
// 使用标准命名空间,简化代码中标准库函数的调用
using namespace std;
/**
* 打印集合中的所有元素
* @param S 需要打印的集合,以引用传递,避免复制集合导致的性能损失
*/
void printSet(const set<int>& S){
// 遍历集合中的每个元素,并打印它们
for(auto &it : S){
cout << it << " ";
}
// 换行,以便后续输出开始于新行
cout << endl;
}
int main(){
// 初始化一个集合S,包含整数5, 4, 6, 8, 9
set<int>S = {5,4,6,8,9};
// 调用函数打印集合S的内容
printSet(S);
// 输出集合S的元素数量
cout << S.size() << endl;
// 检查集合S是否为空,如果不为空,则再次打印集合S的内容
if(!S.empty()){
printSet(S);
}
// 初始化另一个空集合S1
set<int>S1;
// 交换集合S和S1的内容,将S的内容转移到S1中,S变为空集合
S.swap(S1);
// 分别打印交换后的集合S和S1的内容
printSet(S);
printSet(S1);
return 0;
}
这段代码首先展示了如何获取两个集合的大小以及检查它们是否为空,然后使用swap()
函数交换这两个集合的内容,并再次打印交换后的大小,展示了容器大小的查询和元素交换的基本操作。
5.set容器插入和删除
在C++的std::set
容器中,插入和删除元素是非常基本且常用的操作。
1.插入元素
-
单个元素插入:使用
insert()
函数可以向集合中插入一个元素。如果元素已经存在(根据排序准则判断),则不会插入重复项。std::set<int> mySet; mySet.insert(42); // 尝试插入数字42
-
批量插入:可以通过迭代器范围或初始化列表一次性插入多个元素。重复元素会被自动忽略。
mySet.insert({1, 2, 3, 4}); // 使用初始化列表批量插入 std::vector<int> vec = {5, 6, 7}; mySet.insert(vec.begin(), vec.end()); // 使用迭代器范围插入
2.删除元素
-
删除指定值:使用
erase()
函数并提供待删除元素的值。如果该值在集合中存在,则会删除所有匹配的元素。mySet.erase(42); // 删除值为42的所有元素(如果有)
-
通过迭代器删除:可以删除特定迭代器指向的元素。
auto it = mySet.find(42); // 查找值为42的元素 if (it != mySet.end()) { mySet.erase(it); // 删除找到的元素 }
-
清空集合:使用
clear()
函数可以删除集合中的所有元素。mySet.clear(); // 清空整个集合
3.示例
下面是一个综合示例,展示了如何在std::set
中插入和删除元素:
// 包含标准输入输出库和集合库
#include <iostream>
#include <set>
using namespace std;
/**
* 打印集合中的元素
* @param S 集合,其元素将被打印
*/
void printSet(const set<int>& S){
// 遍历集合并打印每个元素
for(auto &it : S){
cout << it << " ";
}
// 换行以分隔不同集合的打印输出
cout << endl;
}
int main(){
// 初始化一个整数集合S,包含一组预定义的元素
set<int>S = {1,8,9,6,3,4};
// 打印集合S的初始状态
printSet(S);
// 向集合S中插入新元素11
S.insert(11);
// 打印插入11后的集合S
printSet(S);
// 从集合S中删除元素11
S.erase(11);
// 打印删除11后的集合S
printSet(S);
// 从集合S中删除第一个元素(即1)
S.erase(S.begin());
// 打印删除第一个元素后的集合S
printSet(S);
// 从集合S中删除第一个元素到第二个元素之间的所有元素(即删除1和3)
S.erase(S.begin(),++S.begin());
// 打印删除部分元素后的集合S
printSet(S);
// 清空集合S的所有元素
S.clear();
// 打印清空后的集合S
printSet(S);
return 0;
}
这段代码演示了如何插入单个和多个元素、尝试插入已存在的元素、删除特定值以及清空整个集合。
6.set容器的查找和统计
在C++的std::set
容器中,查找和统计元素是通过几个核心成员函数完成的,这些操作利用了std::set
的有序特性,提供了高效的实现。
1.查找元素
-
find(): 使用此函数可以查找集合中是否存在某个特定值的元素。如果找到,它会返回一个指向该元素的迭代器;如果没有找到,则返回
end()
迭代器。std::set<int>::iterator it = mySet.find(30); if (it != mySet.end()) { std::cout << "Found element: " << *it << std::endl; } else { std::cout << "Element not found." << std::endl; }
2.统计元素个数
-
count(): 对于
std::set
容器,由于它不存储重复元素,因此count()
函数的结果只有两种可能:如果元素存在,返回1;如果不存在,返回0。这与std::multiset
不同,后者可能返回大于1的计数。int numElements = mySet.count(30); std::cout << "Number of occurrences of 30: " << numElements << std::endl;
3.注意事项
- 查找效率:由于
std::set
底层实现通常为红黑树,查找操作的平均时间复杂度为O(log n),其中n是容器中元素的数量,因此非常高效。 - 统计意义:在
std::set
中,由于每个元素都是唯一的,count()
函数主要用于逻辑上的确认某个值是否存在,而不用于计算重复次数,因为不会有重复。
结合查找和统计,可以有效地管理并检索std::set
中的元素,进行条件检查、元素确认等操作。
// 包含输入输出流和集合的头文件
#include <iostream>
#include <set>
// 使用标准命名空间,简化代码中标准库函数的调用
using namespace std;
/**
* 打印集合中的所有元素
* @param S 一个整数集合,作为函数的输入
*/
void printSet(const set<int>& S){
// 遍历集合中的每个元素,并打印它们
for(auto &it : S){
cout << it << " ";
}
// 换行,以便后续输出开始于新行
cout << endl;
}
int main(){
// 初始化一个整数集合S,包含一组有序的不重复整数
set<int>S = {1,2,3,6,5,4,8,9};
// 调用函数打印集合S的内容
printSet(S);
// 寻找集合中值为11的元素的位置
auto pos = S.find(11);
// 根据查找结果判断是否找到值为11的元素,并输出相应信息
cout << (pos != S.end() ? "找到该数据" : "未找到") << endl;
// 输出集合中值为1的元素的数量
cout << S.count(1) << endl;
// 程序正常结束
return 0;
}
9.set和multiset的区别
// 包含标准输入输出库和集合库
#include <iostream>
#include <set>
using namespace std;
// 主函数,程序的入口点
int main(){
// 创建一个集合对象S,集合内的元素保证唯一
set<int>S;
// 创建一个pair对象ret,用于存储插入操作的结果:迭代器和插入标志
pair<set<int>::iterator ,bool>ret;
// 尝试向集合S中插入元素10
ret = S.insert(10);
// 检查插入操作是否成功,如果成功则输出"success"
if (ret.second){
cout << "success" << endl;
} else{
cout << "fail" << endl;
}
// 再次尝试向集合S中插入元素10,由于集合内已存在,此次插入应失败
ret = S.insert(10);
// 检查插入操作是否成功,输出相应的信息
if (ret.second){
cout << "success" << endl;
} else{
cout << "fail" << endl;
}
// 创建一个多重集合对象MS,允许包含重复元素
multiset<int>MS;
// 向多重集合MS中插入元素100
MS.insert(100);
// 再次插入元素100,多重集合允许重复元素
MS.insert(100);
// 遍历多重集合MS,输出其中的所有元素
for(auto &it : MS){
cout << it << endl;
}
// 程序正常结束
return 0;
}
9.pair对组的创建
在C++中,std::pair
是一种方便的工具,用于将两个相关联的数据作为一个单元存储。这对处理映射关系、返回多值函数结果等场景非常有用。创建std::pair
的方式有几种,以下是几种常见的方法:
1. 直接构造
可以直接通过构造函数创建std::pair
对象,指定两个元素的类型和值。
std::pair<int, std::string> myPair(42, "Hello");
2. 使用make_pair函数
std::make_pair
是一个辅助函数,可以根据提供的两个参数自动推导类型并创建std::pair
对象。
auto anotherPair = std::make_pair(3.14, "Pi");
3. 初始化列表
C++11起,可以使用初始化列表语法创建std::pair
,这在类型已知的情况下特别简洁。
std::pair<char, double> pairWithInitList{'A', 3.14159};
4. 结构化绑定(C++17起)
在C++17中引入了结构化绑定,可以直接解包std::pair
(或tuple等)到单独的变量中,但这不是创建pair
的方式,而是使用它的一种方式。
auto [key, value] = std::make_pair('Z', 26.0);
// key 现在是 'Z',value 是 26.0
5.示例
下面是一个综合示例,展示了不同方式创建std::pair
并对它们进行操作:
// 包含标准输入输出库
#include <iostream>
// 使用标准命名空间,简化IO操作
using namespace std;
// 程序入口函数
int main(){
// 创建一个pair对象,用于表示一个人的名字和年龄
// string类型的first成员存储名字,int类型的second成员存储年龄
pair<string,int>p("Jack",17);
// 使用make_pair函数创建另一个pair对象,同样表示一个人的名字和年龄
pair<string ,int>p1 = make_pair("Li",19);
// 输出第一个pair对象的名字和年龄
cout << p.first << " " << p.second << endl;
// 输出第二个pair对象的名字和年龄
cout << p1.first << " " << p1.second << endl;
// 程序正常结束
return 0;
}
这段代码演示了如何使用不同方法创建std::pair
实例,并打印它们的值。注意,根据上下文和偏好,可以选择最适合的创建方式。
10.set内置数据类型排序
/**
* @file main.cpp
* @brief 此程序演示了如何使用自定义比较函数对象在set中存储整数并按降序遍历。
*
* Created by 86189 on 2024/7/2.
*/
#include <iostream>
#include <set>
using namespace std;
/**
* @class Compare
* @brief 自定义比较函数对象,用于比较两个整数,使得set按降序排列。
*/
class Compare {
public:
/**
* @brief 比较运算符重载,返回true当且仅当第一个整数大于第二个整数。
*
* @param a 第一个整数引用。
* @param b 第二个整数引用。
* @return 返回布尔值,表示a是否大于b。
*/
bool operator()(const int &a, const int &b) const {
return a > b;
}
};
int main() {
/**
* @var S
* @brief 使用自定义比较函数对象Compare的set容器,存储降序排列的整数。
*/
set<int, Compare> S;
// 向set中插入整数
S.insert(10);
S.insert(20);
S.insert(15);
/**
* @brief 遍历set并打印其内容,展示降序排列的效果。
*/
for (auto &it : S) {
std::cout << it << std::endl; // 输出降序排列的整数
}
return 0; // 程序正常结束
}
11.set对自定义数据类型排序
// 包含必要的头文件
#include <iostream>
#include <set>
#include <utility>
// 使用标准命名空间,简化代码中标准库的调用
using namespace std;
/**
* @brief 学生类定义
*
* 该类用于表示学生的信息,包括姓名、性别和年龄。
*/
class Student{
public:
// 使用移动语义优化构造函数参数传递,提高效率
string name;
string sex;
int age{};
/**
* @brief 构造函数
*
* @param name 学生的姓名
* @param sex 学生的性别
* @param age 学生的年龄
*/
Student(string name,string sex,int age): name(std::move(name)),sex(std::move(sex)),age(age){
}
};
/**
* @brief 自定义比较函数类
*
* 该类用于定义对学生对象的比较方式,这里比较的是学生的年龄。
*/
class Compare{
public:
/**
* @brief 比较两个学生对象的年龄
*
* @param s1 第一个学生对象
* @param s2 第二个学生对象
* @return bool 如果s1的年龄大于s2的年龄返回true,否则返回false
*/
bool operator()(const Student& s1,const Student& s2) const{
return s1.age > s2.age;
}
};
int main(){
// 使用自定义比较函数类实例化set容器,用于存储学生对象
set<Student,Compare>S;
// 创建并插入学生对象到set中
Student stu1("Jack","男",16);
S.insert(stu1);
Student stu2("Mala","男",18);
S.insert(stu2);
Student stu3("Joko","女",19);
S.insert(stu3);
Student stu4("Mali","男",17);
S.insert(stu4);
Student stu5("Doro","女",15);
S.insert(stu5);
// 遍历set容器,输出每个学生的信息
for(auto &it : S){
cout << it.name << " " << it.sex << " " << it.age << endl;
}
return 0;
}