以下内容仅为当前认识,可能有不足之处,欢迎讨论!
文章目录
- Set容器
- 构造和赋值
- 大小和交换
- 插入和删除
- 一次性迭代器(可能迅速失效的迭代器)
- 长久保留的迭代器
- 如何判断迭代器是否一次性
- 查找和统计
- set和multiset的区别
- pari对组的创建
- 内置类型指定排序规则
- 自定义数据类型指定排序规则
- Map容器
- 概念构造和赋值
- 大小和交换
- 插入和删除
- 查找和统计
- 排序
- 员工分组
Set容器
集合容器,元素放入其中会被排序。
set/multiset ∈ \in ∈ 关联式容器,底层结构用二叉树实现。
set V S VS VS multiset区别:
- set不允许容器中有重复的元素
- multiset允许容器中有重复的元素
构造和赋值
2种构造方式
目的 | 方式 |
---|---|
默认构造函数 | set<T> st; |
拷贝构造函数 | set(const set &st); |
一种赋值方式——重载等号操作符
set & operator=(const set &st);
返回值set类型,等号重写。
示例代码:
void test0429a() {
set<int> st;
//由于set容器没有先后顺序,所以直接insert插入即可
st.insert(10);
st.insert(40);
st.insert(20);
st.insert(10);
st.insert(30);
cout << "st的元素有↓" << endl;
//看起来插入了5个元素,实际上因为set集合容器的特性,所以只有4个。
print(st);
cout << endl;
cout << "拷贝构造方式后,st2的元素有↓" << endl;
//拷贝构造方式
set<int> st2(st);
print(st2);
cout << endl;
cout << "等号赋值操作后,st3的元素有↓" << endl;
//=号赋值构造方式
set<int> st3 = st2;
print(st2);
cout << endl;
}
打印函数说明:
template <typename T>
void print(const set<T>& st) {
for (typename set<T>::iterator st_begin = st.begin(); st_begin != st.end(); st_begin++) {
cout << *st_begin;
cout << " ";
}
cout << endl;
}
运行结果:
大小和交换
目的:统计set容器元素个数(原文说统计set容器大小),以及交换set容器。
目的 | 函数 |
---|---|
容器中元素个数 | size(); |
判断容器是否为空 | empty(); |
交换两个set容器 | swap(); |
示例代码:
void test0429b() {
set<int> st;
set<int> ts;
st.insert(10 );
st.insert(40 );
st.insert(20 );
st.insert(30 );
ts.insert(1);
ts.insert(5);
ts.insert(2);
ts.insert(3);
ts.insert(4);
cout << "st的元素如下↓" << endl;
print(st);
cout << "ts的元素如下↓" << endl;
print(ts);
cout << endl;
cout << "st的元素个数:" << st.size() << "." << endl;
cout << "ts的元素个数:" << ts.size() << "." << endl;
cout << "st是空容器吗?1为是,0为否。\t" <<st.empty()<< endl;
cout << "ts是空容器吗?1为是,0为否。\t" <<ts.empty()<< endl;
cout << endl;
cout << "交换st和ts两个容器" << endl;
cout << "交换前,st=";
print(st);
cout << "交换前,ts=";
print(ts);
cout << endl;
st.swap(ts);
cout << "交换后,st=";
print(st);
cout << "交换后,ts=";
print(ts);
cout << endl;
}
运行结果:
插入和删除
目的:set容器进行插入数据和删除数据
目的 | 函数 |
---|---|
容器中插入元素 | insert(elem); |
删除position迭代器所指的元素,返回下一个元素的迭代器 | erase(pos); |
删除区间[begin , end)的所有元素,返回下一个元素的迭代器 | erase(begin , end); |
删除容器中值为elem的元素(只能删除一个,因为不重复) | erase(elem); |
清除所有元素 | clear(); |
示例代码:
void test0429c() {
//容器中插入一定数量的元素后,删除第二个元素。
//删除第三个元素到倒数第二个元素。
//删除第一个元素,此时指定第一个元素值为10。
//清空所有元素,并判断容器是否为空。
set<int> st;
st.insert(-100);
for (int i = 0; i < 9; i++) {
st.insert(i * i - 2 + i/2);
}
cout << "st = ";
print(st);
cout << endl;
set<int>::iterator st_ptr = st.begin();
//删除第二个元素
advance(st_ptr, 1);
st.erase(st_ptr);
cout << "删除第二个元素后,st = ";
print(st);
cout << endl;
//删除第三个元素到倒数第二个元素区间
set<int>::iterator st_ptr2 = st.begin();
advance(st_ptr2, 2);
st.erase(st_ptr2, --(st.end()));
cout << "删除第三个元素—倒数第二个元素区间后,st = ";
print(st);
cout << endl;
//由于事先指定第一个元素为-100,所以删除-100。
st.erase(-100);
cout << "删除-100后,st = ";
print(st);
cout << endl;
//清空st容器
st.clear();
cout << "清空st容器后,此时st为空吗?1为是,0为否==》" << st.empty() << "." << endl;
cout << endl;
}
运行结果:
根据GPT的回答以及黑马老师的讲解。回答该问题:C++ 中提供了容器这一特性,容器中有迭代器。什么容器的迭代器是一次性的?什么容器的迭代器是长久保留的?如何判断容器的迭代器是一次性的还是长久保留的?
黑马老师回答:
不会造成原有迭代器的失效,在vector中是不允许的。
为什么?因为vector,deque,stack,string是连续的内存空间。而list,set,multiset,map容器不是连续的内存空间。迭代器指向的是地址,由于是链表结构,其数据包含指针域—地址。所以不会失效。
对于同样的问题,询问GPT后:
在C++标准库中,容器的迭代器可以大致分为两类:那些在容器被修改后仍然保持有效的迭代器,和那些在特定修改操作后可能失效的迭代器。这两类迭代器的行为依赖于它们所属的容器类型。
一次性迭代器(可能迅速失效的迭代器)
向量(
vector
):
- 当向
vector
中添加或删除元素时,特别是当涉及到动态内存重新分配以增加容量时,迭代器可能会失效。例如,使用push_back()
可能导致容器大小超过当前容量,从而触发重新分配内存,这将使所有现有迭代器失效。字符串(
string
):
- 与
vector
相似,string
的迭代器在修改字符串内容或长度时(如添加更多字符导致内存重新分配)也可能失效。双端队列(
deque
):
- 对于
deque
,在队列的首部或尾部添加或删除元素不会影响其他元素的迭代器,但如果在deque
的中间插入或删除元素,则可能导致所有迭代器失效。长久保留的迭代器
列表(
list
):
list
的迭代器在添加或删除元素时都保持有效,因为list
通常实现为双向链表,各元素之间通过指针连接,不依赖于连续的内存空间。集合(
set
)、多重集合(multiset
):
- 这些基于红黑树的容器允许迭代器在元素插入或删除时保持有效,除非是指向被删除元素的迭代器。
映射(
map
)、多重映射(multimap
):
- 与
set
和multiset
相似,这些容器也是基于树的结构,其迭代器在修改操作后依然有效,除非操作直接涉及到迭代器当前指向的那个元素。如何判断迭代器是否一次性
要确定一个容器的迭代器是否是一次性的,你可以参考下面的准则:
内部结构:了解容器的内部实现。例如,依赖于连续内存空间的容器(如
vector
和string
)更可能在扩容时使迭代器失效。操作类型:某些操作(如
resize()
,push_back()
等)可能导致内存重新分配,特别是在vector
和string
中。了解特定的操作是否影响容器的内部存储结构。官方文档:查阅C++标准库的文档,了解不同类型的容器和它们的迭代器行为。这是最可靠的方式。
理解和遵守这些规则可以帮助你编写出更安全、更健壮的C++代码,尤其是在涉及复杂容器操作时。
但实际使用时,仍然是一次性的迭代器,所以,我新建一个。
结果:
查找和统计
目的:
查找某个元素在set容器中的位置。返回其迭代器,如果找不到,则返回最后一个元素。
示例代码:
void findElement(const set<int>& st, int elem) {
set<int>::iterator result = st.find(elem);
if (result != st.end()) {
cout << "哦耶,"<<elem<<"在st中找到啦!" << endl;
return;
//return result;
}
cout << "芜湖," << elem << "没在st中找到……" << endl;
return;
//return result;
}
void test0429d() {
set<int> st;
for (int i = 0; i < 5; i++) {
st.insert(i + 1);
}
//查找某个元素
//查找1
findElement(st, 1);
//查找100
findElement(st, 100);
//统计元素个数
cout << "2在st中总共出现了" << st.count(2) << "次。" << endl;
}
运行结果:
set和multiset的区别
区别:
区别 | set | multiset |
---|---|---|
是否可以插入重复数据? | 不可以 | 可以 |
插入数据是否返回结果? | 插入返回结果,表示插入是否成功 | 不会检测数据,因此可以插入重复数据 |
set插入数据会返回插入结果,第一个是迭代器,第二个是bool值,返回是否插入成功的bool,而multiset则不会。
查看源码:
void test0429e() {
set<int> st;
for (int i = 0; i < 5; i++) {
st.insert(i * 10);
}
cout << "st当前的元素为:";
print(st);
//set的插入返回一个pair,包括一个迭代器,以及bool结果。
pair<set<int>::iterator, bool> st_result = st.insert(20);
if (st_result.second) {
cout << "20插入成功." << endl;
}
else {
cout << "20插入失败." << endl;
}
multiset<int> mst;
for (int i = 0; i < 5; i++) {
mst.insert(i * 10);
mst.insert(i * 10);
}
cout << "mst当前的元素为:";
print(mst);
}
执行结果:
pari对组的创建
目的,将两个数据进行打包提取,作为一对。是否可以作为映射?
目的 | 函数 |
---|---|
构建方式1,构造函数创建 | pair<type_name1 , type_name2> pair_name (type_value1 , type_value2); |
构建方式2,方法创建 | pair<type_name1 , type_name2> pair_name = make_pair(type_value1,type_value2); |
示例代码:
void test0430a() {
pair<string, int> Jack("Jack", 30);
cout << Jack.first << "年龄为" << Jack.second << "." << endl;
cout << endl;
pair<string, int > Ross;
Ross = make_pair("Ross", 28);
cout << Ross.first << "年龄为" << Ross.second << "." << endl;
cout << endl;
}
运行结果:
内置类型指定排序规则
set容器排序怎么自定义?==》掌握如何改变排序规则。
技术点:利用仿函数。
与之前list的排序不同,仿函数在此时是类里面对()
重载。
代码示例:
class MyCompare {
public:
bool operator()(int it1, int it2){
return it1 > it2;
}
};
template <typename T>
void print(set<T,MyCompare>& st) {
for (typename set<T, MyCompare>::iterator st_begin = st.begin(); st_begin != st.end(); st_begin++) {
cout << *st_begin;
cout << " ";
}
cout << endl;
}
void test0430b(){
//内置数据类型的自定义排序
set<int> st;
for (int i = 0; i < 5; i++) {
st.insert(i * i);
}
cout << "默认排序后,st为↓" << endl;
print(st);
cout << "创建一个元素和st一致的ts" << endl;
set<int, MyCompare> ts;
for (int i = 0; i < 5; i++) {
ts.insert(i * i);
}
cout << "自定义排序后,ts为↓" << endl;
print(ts);
}
运行结果:
原先代码中,出现问题,在compare中没有对operator添加const属性,报错:C3848 具有类型“const MyCompare”的表达式会丢失一些 const-volatile 限定符以调用“bool MyCompare::operator ()(int,int)”
,直接搜索CSDN后得到:博客园参考链接。原因是,因为std::not1用的是const Predicate&,所以我的predicate到not1之中以后就是const Predicate&了,所以在调用operator()的时候要求operator()也具有const属性,否则就会导致丢失const限定符的错误
,因为我是重写了操作运算符(),需要加上const属性。
自定义数据类型指定排序规则
首先得重写对应数据类型的打印方法。其次得在重写的打印方法中对person也加上const修饰。
class Person {
public:
Person(string name, int age):person_name(name),person_age(age){}
public:
string person_name;
int person_age;
};
class PersonCompare {
public:
bool operator()(const Person& per, const Person& son) const {
return per.person_age > son.person_age;
}
};
ostream& operator<<(ostream& out, const Person& person) {
out << person.person_name << "年龄为:" << person.person_age << "." << endl;
return out;
}
template <typename T,typename C>
void print(const set<T,C>& st) {
for (typename set<T, C>::iterator st_begin = st.begin(); st_begin != st.end(); st_begin++) {
//Person* temp = st_begin;
cout << *st_begin << endl;
//cout << " ";
}
cout << endl;
}
void test0430c() {
//自定义数据类型的自定义排序
set<Person,PersonCompare> person_st;
Person p("张三", 23);
Person e("李四", 24);
Person r("王五", 25);
Person s("赵六", 16);
Person o("侯七", 17);
Person n("周八", 18);
person_st.insert(p);
person_st.insert(e);
person_st.insert(r);
person_st.insert(s);
person_st.insert(o);
person_st.insert(n);
cout << "person_st为" << endl;
print(person_st);
}
运行结果:
Map容器
概念构造和赋值
map相当于映射,容器内的每一个元素有其键值对。每一个元素都是pair元素,因为它是有键值对的。
和set一致,map本身是按照键来看的,所以map不能有重复的key值,value值无所谓。
但multimap就不同,它允许出现重复key值。
简介:map中所有元素都是pair,第一个元素为key,起到索引作用,第二个元素为value。所有元素会根据元素的键值自动排序。
本质:map/multimap属于关联式容器,底层结构用二叉树实现。
优点:可以根据key值快速找到value值。
目的 | 函数 |
---|---|
默认构造函数 | map<T1,T2> mp; |
拷贝构造函数 | map(const map &mp); |
赋值重载等号运算符 | map& operator=(const map &mp); |
示例代码:
void print(map<int, string> &mp) {
for (map<int, string>::iterator mp_elem = mp.begin(); mp_elem != mp.end(); mp_elem++) {
cout << mp_elem->first<<"值为"<<mp_elem->second<<".";
cout << endl;
}
cout << endl;
}
void test0430a() {
map<int,string> mp;
for (int i = 0; i < 5; i++) {
mp.insert(pair<int, string>(i, to_string(i * i)));
}
cout << "mp为↓" << endl;
print(mp);
map<int, string> mp1(mp);
cout << "拷贝构造时,mp1为↓" << endl;
print(mp);
map<int, string> mp2;
mp2 = mp;
cout << "mp等号赋值时,mp2↓" << endl;
print(mp2);
}
运行结果:
大小和交换
目的:统计map容器大小以及交换map容器
目的 | 函数 |
---|---|
返回容器中元素个数 | |
判断容器是否为空 | |
交换两个容器 |
如果两者容器内元素数据类型不同,是无法交换的。pair必须相同才可以。
示例代码:
运行结果:
问题:此时我想用模板打印,但是没有实现成功。
插入和删除
目的:对map容器插入数据或删除数据。
目的 | 函数 |
---|---|
容器中插入元素 | |
清空所有元素 | |
删除迭代器所指元素,返回下一个元素的迭代器。 | |
删除区间的所有元素,返回下一个元素的迭代器。 | |
删除容器中键为key的元素。此时,只能按照键来删除,不能按照值来删除。 |
注意,此时insert有三种方式,除了pair<type a , type b>(value1 , value2);
还有make_pair(value1,value2);
以及map<typea , typeb >::value_type(value1 , value2);
。
最后一种方式不建议使用,因为如果键不对,或自动有一个默认数据加入其中。
示例代码:
void test0430c() {
map<int, string> mp;
//第一种插入方式
mp.insert(pair<int, string>(1, to_string(1)));
mp.insert(pair<int, string>(5, to_string(5)));
//第二种插入方式
mp.insert(make_pair(2, to_string(2)));
mp.insert(make_pair(6, to_string(6)));
//第三种插入方式
mp.insert(map<int, string>::value_type(3, to_string(3)));
mp.insert(map<int, string>::value_type(7, to_string(7)));
//第四种插入方式
mp[4] = to_string(4);
print(mp);
cout << "删除第二个元素" << endl;
mp.erase(++mp.begin());
print(mp);
cout << "删除第三个元素到倒数第二个元素" << endl;
mp.erase(++(++mp.begin()), --mp.end());
print(mp);
cout << "删除键为1的元素" << endl;
mp.erase(1);
print(mp);
cout << "清空mp" << endl;
mp.clear();
cout << "此时mp是否为空?1为是,0为否==》" << mp.empty() << "." << endl;
}
运行结果:
查找和统计
目的:对map容器中的内容,进行查找数据以及统计数据。
目的 | 函数 |
---|---|
查找key是否存在,如果存在,返回该键元素的迭代器,如果不存在,返回set.end() | find(key); |
统计key元素个数 | count(key); |
示例代码:
void test0430d() {
map<int, string> mp;
for (int i = 0; i < 5; i++) {
mp.insert(pair<int, string>(i, to_string(i)));
}
cout << "此时mp=" << endl;
print(mp);
map<int, string>::iterator result = mp.find(10);
if (result != mp.end()) {
cout << "找到了!" << endl;
}
else {
cout << "没找到……" << endl;
}
cout << "mp的2个数有:" << mp.count(2) << "." << endl;
}
运行结果:
排序
按照人员年龄来排序,要记住,所有的排序都是按照键来排序,所以要想对年龄排序,键需要是Person类。
代码示例:
class MyCompare {
public:
bool operator()(int pa, int ir) const{
return pa > ir;
}
};
class Person {
public:
Person() {
this->person_age = 0;
this->person_name = "";
}
Person(string name, int age) :person_name(name), person_age(age) {}
public:
string person_name;
int person_age;
};
class PersonCompare {
public:
bool operator()(Person per, Person son) const{
return per.person_age > son.person_age;
}
};
void print(map<int, string,MyCompare>& mp) {
for (map<int, string, MyCompare>::iterator mp_elem = mp.begin(); mp_elem != mp.end(); mp_elem++) {
cout << mp_elem->first << "值为" << mp_elem->second << ".";
cout << endl;
}
cout << endl;
}
ostream& operator<<(ostream& out,const Person& person) {
cout << person.person_name << "\t年龄:" << person.person_age << "." << endl;
return out;
}
void print(map< Person,int, PersonCompare>& mp) {
for (map< Person,int, PersonCompare>::iterator mp_elem = mp.begin(); mp_elem != mp.end(); mp_elem++) {
cout << "编号:" << mp_elem->second << endl;
cout << mp_elem->first << endl;
//cout << endl;
}
cout << endl;
}
void test0430e() {
map<int, string,MyCompare> mp;
for (int i = -3; i < 4; i++) {
mp.insert(pair<int, string>(i * i * i, to_string(i)));
}
print(mp);
}
void test0430f() {
map< Person,int, PersonCompare> person_mp;
person_mp.insert(pair< Person,int>( Person("张三", 18),1));
person_mp.insert(pair< Person,int>( Person("李四", 24),2));
person_mp.insert(pair< Person,int>( Person("王五", 19),3));
person_mp.insert(pair< Person,int>( Person("赵六", 20),4));
person_mp.insert(pair< Person,int>( Person("郑八", 17),5));
print(person_mp);
}
运行结果:
内置数据类型排序:
自定义数据类型排序:
员工分组
案例代码:
#include<iostream>
#include<string>
using namespace std;
#include<map>
#include<vector>
#define ART 0
#define PLAN 1
#define RD 2
#include<ctime>
/*
* 公司招聘了10个员工,进入公司后,需要指派公司在哪个部门工作。员工信息有:姓名,工资组成;部门分为:策划,美术,研发
* 随机给10名员工分配部门和工资。
* 通过multimap进行信息插入 ,key-部门编号,value-员工
* 分部门显示员工信息。
*
* 创建10名员工,放入vector中。
* 遍历vector,取出每个员工,进行随机分组。
* 分组后,员工部门编号作为key,具体员工作为value,放入到multimap容器中
* 分部门显示员工信息。
*
* ---
*
* √,完成——员工,用类表示。
* 随机分组?怎么随机分组?使用随机种子。
*
*/
//员工类信息
class Staff {
public:
Staff() {};
Staff(string name, int salary) {
//cout << "员工姓名:" << name << "\t, 员工薪资:" << salary << " ." << endl;
set_staff_name(name);
set_staff_salary(salary);
};
//员工姓名和工资不可以公开。
public:
void set_staff_name(string name){
this->staff_name = name;
}
string get_staff_name() {
return this->staff_name;
}
void set_staff_salary(int salary) {
this->staff_salary = salary;
}
int get_staff_salary() {
return this->staff_salary;
}
private:
string staff_name = "????";
int staff_salary=-1;
};
vector<Staff> setStaffVector(vector <Staff>& vt) {
string staffs = "ABCDEFGHIJ";
for (int i = 0; i < staffs.size(); i++) {
int salary = rand() % 10000 + 10000;
string name = "员工";
name += staffs[i];
vt.push_back(Staff(name, salary));
}
return vt;
}
//给员工分配不同的部门,其中每个部门的人数不确定。
//对于每一个员工,分配其0-2的部门。
multimap<int,Staff> setStaffDepartment(vector <Staff>& vt) {
multimap<int, Staff> mtm;
for (vector<Staff>::iterator staff = vt.begin(); staff != vt.end(); staff++) {
int partment_number = rand() % 3;
mtm.insert(make_pair(partment_number, *staff));
}
return mtm;
}
void printStaff(multimap<int, Staff> mtm) {
for (multimap<int, Staff>::iterator staff = mtm.begin(); staff != mtm.end(); staff++) {
int result = (*staff).first;
switch (result) {
case ART:
cout << "部门:美术\t" << "员工姓名:" << (*staff).second.get_staff_name() << "\t员工薪资:" << (*staff).second.get_staff_salary() << "." << endl;
break;
case PLAN:
cout << "部门:策划\t" << "员工姓名:" << (*staff).second.get_staff_name() << "\t员工薪资:" << (*staff).second.get_staff_salary() << "." << endl;
break;
case RD:
cout << "部门:研发\t" << "员工姓名:" << (*staff).second.get_staff_name() << "\t员工薪资:" << (*staff).second.get_staff_salary() << "." << endl;
break;
default:
cout << "部门:????\t" << "员工姓名:????\t员工薪资:????." << endl;
break;
}
}
}
void test0506() {
srand((unsigned int)time(NULL));
vector <Staff> vt;
vt = setStaffVector(vt);
multimap<int, Staff> mtm = setStaffDepartment(vt);
//创建员工时,可以按照字符串形式创建。
printStaff(mtm);
}
int main() {
test0506();
system("pause");
return 0;
}
运行结果:
以上是我的学习笔记,希望对你有所帮助!
如有不当之处欢迎指出!谢谢!