【C++第三阶段】Set & 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;
}

运行结果:

image-20240429161546086

大小和交换

目的:统计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;
}

运行结果:

image-20240429165005296

插入和删除

目的: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++标准库中,容器的迭代器可以大致分为两类:那些在容器被修改后仍然保持有效的迭代器,和那些在特定修改操作后可能失效的迭代器。这两类迭代器的行为依赖于它们所属的容器类型。

一次性迭代器(可能迅速失效的迭代器)

  1. 向量(vector

    • 当向vector中添加或删除元素时,特别是当涉及到动态内存重新分配以增加容量时,迭代器可能会失效。例如,使用push_back()可能导致容器大小超过当前容量,从而触发重新分配内存,这将使所有现有迭代器失效。
  2. 字符串(string

    • vector相似,string的迭代器在修改字符串内容或长度时(如添加更多字符导致内存重新分配)也可能失效。
  3. 双端队列(deque

    • 对于deque,在队列的首部或尾部添加或删除元素不会影响其他元素的迭代器,但如果在deque的中间插入或删除元素,则可能导致所有迭代器失效。

长久保留的迭代器

  1. 列表(list

    • list的迭代器在添加或删除元素时都保持有效,因为list通常实现为双向链表,各元素之间通过指针连接,不依赖于连续的内存空间。
  2. 集合(set)、多重集合(multiset

    • 这些基于红黑树的容器允许迭代器在元素插入或删除时保持有效,除非是指向被删除元素的迭代器。
  3. 映射(map)、多重映射(multimap

    • setmultiset相似,这些容器也是基于树的结构,其迭代器在修改操作后依然有效,除非操作直接涉及到迭代器当前指向的那个元素。

如何判断迭代器是否一次性

要确定一个容器的迭代器是否是一次性的,你可以参考下面的准则:

  1. 内部结构:了解容器的内部实现。例如,依赖于连续内存空间的容器(如vectorstring)更可能在扩容时使迭代器失效。

  2. 操作类型:某些操作(如resize(), push_back()等)可能导致内存重新分配,特别是在vectorstring中。了解特定的操作是否影响容器的内部存储结构。

  3. 官方文档:查阅C++标准库的文档,了解不同类型的容器和它们的迭代器行为。这是最可靠的方式。

理解和遵守这些规则可以帮助你编写出更安全、更健壮的C++代码,尤其是在涉及复杂容器操作时。

但实际使用时,仍然是一次性的迭代器,所以,我新建一个。

结果:

image-20240429173824696

查找和统计

目的:

查找某个元素在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;
}

运行结果:

image-20240429181743441

set和multiset的区别

区别:

区别setmultiset
是否可以插入重复数据?不可以可以
插入数据是否返回结果?插入返回结果,表示插入是否成功不会检测数据,因此可以插入重复数据

set插入数据会返回插入结果,第一个是迭代器,第二个是bool值,返回是否插入成功的bool,而multiset则不会。

查看源码:

image-20240429231238681

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);
}

执行结果:

image-20240429231453086

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;

}

运行结果:

image-20240430095821588

内置类型指定排序规则

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);
	
}

运行结果:

image-20240430102544543

原先代码中,出现问题,在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);
}

运行结果:

image-20240430105902715

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);

}

运行结果:

image-20240430144148972

大小和交换

目的:统计map容器大小以及交换map容器

目的函数
返回容器中元素个数
判断容器是否为空
交换两个容器

如果两者容器内元素数据类型不同,是无法交换的。pair必须相同才可以。

示例代码:

运行结果:

image-20240430150907448

问题:此时我想用模板打印,但是没有实现成功。

插入和删除

目的:对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;
}

运行结果:

image-20240430152933080

查找和统计

目的:对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;
}

运行结果:

image-20240430154036005

排序

按照人员年龄来排序,要记住,所有的排序都是按照键来排序,所以要想对年龄排序,键需要是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);
}

运行结果:

内置数据类型排序:

image-20240430160435332

自定义数据类型排序:

image-20240430160408727

员工分组

案例代码:

#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;
}

运行结果:

image-20240506152856581


以上是我的学习笔记,希望对你有所帮助!
如有不当之处欢迎指出!谢谢!

学吧,学无止境,太深了

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

你的铭称

随缘惜缘不攀缘。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值