(C++)list,vector,set,map四种容器的应用——教务管理系统(测试版)(list基础教程)(vector基础教程)(set基础教程)(map基础教程)(STL库教程)

目录

源代码:

代码详解:

第1步:搭建基础框架和数据结构

目标:定义数据结构和全局容器

练习任务:

第2步:实现学生管理功能(使用map)

目标:添加学生和显示学生列表

练习任务:

第3步:实现课程管理功能(使用vector)

目标:添加课程和显示课程列表

练习任务:

第4步:实现选课功能(使用list)

目标:学生选课和退课功能

练习任务:

主函数:

多说一点(重点代码解释):

一.list> enrollments;代码详解

1. 分解理解

list

pair

list>

2. 在项目中的具体用途

3. 为什么选择这种设计?

对比 pair vs tuple

适用场景

 二.为什么明明有全局容器了,每个模块开始都定义变量?

1. 为什么定义这些变量?

2. students.emplace(id, Student{id, name, year}) 是什么意思?

(1) students 是什么?

(2) emplace 方法

(3) Student{id, name, year}

合起来的作用:

三,在课程管理里面,为什么vector不能用迭代器查找? 

为什么 vector 的检查方式和其他容器不同?

1. vector 需要手动遍历检查的原因

2. 其他容器(map/set)如何检查重复?

(1)map(检查学生ID是否重复)

(2)set(检查课程类别是否重复)

四.clearInputBuffer的辅助函数的作用

函数定义与作用

函数内部实现

1. cin.clear()

2. cin.ignore(numeric_limits::max(), '\n')

为什么需要清空输入缓冲区?

示例场景

五.什么时候用emplace什么时候用emplace_back?

核心区别

详细解释

1. emplace_back

2. emplace

何时选择哪个?

注意事项

总结

六.list,map,vector,set他们都是用什么函数删除?

各容器的删除方法

详细示例

1. std::list 的删除

2. std::map 的删除

3. std::vector 的删除

4. std::set 的删除

常见误区:remove 与 erase 的区别

总结

七.为什么不能使用范围 for 循环删除元素 (重点)

错误原因:迭代器失效

正确代码(使用显式迭代器)

为什么范围 for 循环会出错?

源代码:

//头文件
#include 
#include 
#include 
#include 
#include 
#include 
#include 

//命名空间
using namespace std;

//1.结构体 学生信息
struct Student{
	int id;
	string name;
	int year;
};

//2.结构体 课程信息
struct Course{
	int code;
	string title;
	int credit;
	string category;
};

//3.辅助函数:清空输入缓冲区
void clearInputBuffer(){
    cin.clear();
    cin.ignore(numeric_limits::max(), '\n');
}

//4.主函数菜单
void displayMainMenu() {
    cout << "\n===== 学生选课系统 =====\n";
    cout << "1. 学生管理\n";
    cout << "2. 课程管理\n";
    cout << "3. 选课管理\n";
    cout << "4. 查询系统\n";
    cout << "5. 统计系统\n";
    cout << "0. 退出\n";
    cout << "请选择操作: ";
}

//5.全局容器
map students;// 学生信息(ID到学生对象的映射)
vector courses;// 课程列表
list> enrollments;// 选课记录(学生ID和课程代码对)
set categories;// 课程类别集合

//5.1学生管理
//5.1.1添加学生信息
void addStudent(){
	//临时定义变量
	int id, year;
	string name;
	
	//1.输入学生id
	cout << "输入学生ID: ";
	cin >> id;
	if(students.count(id)>0){
		cout<<"学生ID"<>year;
	//4.添加新学生
	students.emplace(id,Student{id,name,year});
	cout << "添加学生: " << name << " (ID: " << id << ")" << endl;
}

//5.1.2显示学生函数
void displayStudents(){
	//1.检查是否为空
	if(students.empty()){
		cout<<"当前没有学生"<> choice;
	    
	    switch(choice) {
	        case 1:
	            addStudent();
	            break;
	        case 2:
	            displayStudents();
	            break;
	        case 0:
	            return;
	        default:
	            cout << "无效选择!\n";
	    }
	} while (true);
}

//5.2课程管理
//5.2.1添加课程
void addCourse(){
	//临时定义变量
	int code,credit;
	string title,category;
	//1.输入课程代码
	cout<<"输入课程代码:";
	cin>>code;
	//vector数组要手动遍历检查
	for(const auto& it:courses){
		if(it.code==code){
			cout << "课程代码 " << code << " 已存在!" << endl;
			return;
		}
	}
	//2.输入其他的课程信息
	cout << "输入课程名称: ";
	clearInputBuffer();
	getline(cin, title);
	    
	cout << "输入课程学分: ";
	cin >> credit;
	    
	cout << "输入课程类别: ";
	clearInputBuffer();
	getline(cin,category);
	//3.添加课程
	courses.emplace_back(Course{code,title,credit,category});
	//添加课程类别到set
	categories.emplace(category);
	cout << "添加课程: " << title << " (代码: " << code << ")" << endl;
}

//5.2.2显示课程列表函数
void displayCourses(){
	//1.检查是否为空
	if(courses.empty()){
		cout<<"暂时没有课程"<> choice;
	    
	    switch(choice) {
	        case 1:
	            addCourse();
	            break;
	        case 2:
	            displayCourses();
	            break;
	        case 0:
	            return;
	        default:
	            cout << "无效选择!\n";
	    }
	} while (true);
}

//5.3选课管理
//5.3.1学生选课函数
void enrollCourse(){
	//定义临时变量
	int studentId,courseCode;
	//1.输入学生id
	cout<<"输入学生id:";
	cin>>studentId;
	//检查是否存在
	if(students.count(studentId)==0){
		cout<<"当前学生id不存在";
		return;
	}
	//2.输入课程code
	cout<<"输入课程代码:";
	cin>>courseCode;
	//检查是否存在
	bool find=false;
	string courseTitle;
	for(auto& it:courses){
		if(it.code==courseCode){
			find=true;
			//找到当前课程名
			courseTitle = it.title;
			break;
		}
	}
	if(find==false){
		cout<<"当前课程代码不存在";
		return;
	}
	//3.检查是否已选该课程
	for(auto& enrollment:enrollments){
		if(enrollment.first==studentId && enrollment.second==courseCode){
			cout << "该学生已选修此课程!" << endl;
			return;
		}
	}
	//4.添加选课记录
	enrollments.emplace_back(studentId,courseCode);
	cout << students[studentId].name << " 选修了 " <>studentId;
	//2.输入课程code
	cout<<"输入课程代码:";
	cin>>courseCode;
	//3.查找选课记录
	auto it = enrollments.begin();// 1. 初始化迭代器指向容器起始位置
	while (it != enrollments.end()) {// 2. 循环条件:未到达容器末尾
	    if (it->first == studentId && it->second == courseCode) {// 3. 找到匹配元素
	    	//查找当前学生名
	        string studentName = students[studentId].name;
	        //查找课程名
	        string courseTitle;
	        for (const auto& course : courses) {
	            if (course.code == courseCode) {
	                courseTitle = course.title;
	                break;
	            }
	        }
	        //删除选课记录
	        enrollments.erase(it);  // 关键:更新迭代器
	        cout << studentName << " 退选了 " << courseTitle << endl;
	        return;
	    }
	    ++it;
	}
}

//5.3.3课管理菜单
void enrollmentMenu() {
    int choice;
    do {
        cout << "\n===== 选课管理 =====\n";
        cout << "1. 学生选课\n";
        cout << "2. 学生退课\n";
        cout << "0. 返回主菜单\n";
        cout << "请选择: ";
        cin >> choice;
        
        switch(choice) {
            case 1:
                enrollCourse();
                break;
            case 2:
                dropCourse();
                break;
            case 0:
                return;
            default:
                cout << "无效选择!\n";
        }
    } while (true);
}

//6.主函数
int main(){
	int choice;
	do{
		displayMainMenu();
		cin>>choice;
		switch (choice) {
			case 1:
				studentMenu();
				break;
			case 2:
				courseMenu();
				break;
			case 3:
				enrollmentMenu();
				break;
			default:
				//TODO
				break;
		}
	}while(choice!=0);
	cout << "感谢使用,再见!\n";
	return 0;
}

代码详解:

第1步:搭建基础框架和数据结构

目标:定义数据结构和全局容器

//头文件
#include 
#include 
#include 
#include 
#include 
#include 
#include 

//命名空间
using namespace std;

//1.结构体 学生信息
struct Student{
	int id;
	string name;
	int year;
};

//2.结构体 课程信息
struct Course{
	int code;
	string title;
	int credit;
	string category;
};

//3.辅助函数:清空输入缓冲区
void clearInputBuffer(){
    cin.clear();
    cin.ignore(numeric_limits::max(), '\n');
}

//4.主函数菜单
void displayMainMenu() {
    cout << "\n===== 学生选课系统 =====\n";
    cout << "1. 学生管理\n";
    cout << "2. 课程管理\n";
    cout << "3. 选课管理\n";
    cout << "4. 查询系统\n";
    cout << "5. 统计系统\n";
    cout << "0. 退出\n";
    cout << "请选择操作: ";
}

//5.全局容器
map students;// 学生信息(ID到学生对象的映射)
vector courses;// 课程列表
list> enrollments;// 选课记录(学生ID和课程代码对)
set categories;// 课程类别集合

练习任务:

  1. 理解每个数据结构的定义

  2. 观察四种容器的声明方式

  3. 编译运行确保基础框架正常工作

第2步:实现学生管理功能(使用map)

目标:添加学生和显示学生列表

//5.1学生管理
//5.1.1添加学生信息
void addStudent(){
	//临时定义变量
	int id, year;
	string name;
	
	//1.输入学生id
	cout << "输入学生ID: ";
	cin >> id;
	if(students.count(id)>0){
		cout<<"学生ID"<>year;
	//4.添加新学生
	students.emplace(id,Student{id,name,year});
	cout << "添加学生: " << name << " (ID: " << id << ")" << endl;
}

//5.1.2显示学生函数
void displayStudents(){
	//1.检查是否为空
	if(students.empty()){
		cout<<"当前没有学生"<> choice;
	    
	    switch(choice) {
	        case 1:
	            addStudent();
	            break;
	        case 2:
	            displayStudents();
	            break;
	        case 0:
	            return;
	        default:
	            cout << "无效选择!\n";
	    }
	} while (true);
}
// 在main函数中添加调用
int main() {
    // ...之前代码...
    do {
        displayMainMenu();
        cin >> choice;
        
        switch(choice) {
            case 1:
                studentMenu();
                break;
            // 其他case将在后续步骤添加
        }
    } while (choice != 0);
    // ...之后代码...
}

练习任务:

  1. 添加至少3名学生

  2. 尝试添加重复ID的学生

  3. 观察map如何自动按键排序

  4. 理解students.find(id)的用法

第3步:实现课程管理功能(使用vector)

目标:添加课程和显示课程列表

//5.2课程管理
//5.2.1添加课程
void addCourse(){
	//临时定义变量
	int code,credit;
	string title,category;
	//1.输入课程代码
	cout<<"输入课程代码:";
	cin>>code;
	//vector数组要手动遍历检查
	for(const auto& it:courses){
		if(it.code==code){
			cout << "课程代码 " << code << " 已存在!" << endl;
			return;
		}
	}
	//2.输入其他的课程信息
	cout << "输入课程名称: ";
	clearInputBuffer();
	getline(cin, title);
	    
	cout << "输入课程学分: ";
	cin >> credit;
	    
	cout << "输入课程类别: ";
	clearInputBuffer();
	getline(cin,category);
	//3.添加课程
	courses.emplace_back(Course{code,title,credit,category});
	//添加课程类别到set
	categories.emplace(category);
	cout << "添加课程: " << title << " (代码: " << code << ")" << endl;
}

//5.2.2显示课程列表函数
void displayCourses(){
	//1.检查是否为空
	if(courses.empty()){
		cout<<"暂时没有课程"<> choice;
	    
	    switch(choice) {
	        case 1:
	            addCourse();
	            break;
	        case 2:
	            displayCourses();
	            break;
	        case 0:
	            return;
	        default:
	            cout << "无效选择!\n";
	    }
	} while (true);
}
// 在main函数中添加调用
int main() {
    // ...之前代码...
    switch(choice) {
        case 1:
            studentMenu();
            break;
        case 2:
            courseMenu();
            break;
        // 其他case将在后续步骤添加
    }
    // ...之后代码...
}

练习任务:

  1. 添加至少4门不同类别的课程

  2. 观察vector如何保持插入顺序

  3. 理解categories.insert(category)如何自动去重

  4. 尝试添加重复课程代码的课程

第4步:实现选课功能(使用list)

目标:学生选课和退课功能

//5.3选课管理
//5.3.1学生选课函数
void enrollCourse(){
	//定义临时变量
	int studentId,courseCode;
	//1.输入学生id
	cout<<"输入学生id:";
	cin>>studentId;
	//检查是否存在
	if(students.count(studentId)==0){
		cout<<"当前学生id不存在";
		return;
	}
	//2.输入课程code
	cout<<"输入课程代码:";
	cin>>courseCode;
	//检查是否存在
	bool find=false;
	string courseTitle;
	for(auto& it:courses){
		if(it.code==courseCode){
			find=true;
			//找到当前课程名
			courseTitle = it.title;
			break;
		}
	}
	if(find==false){
		cout<<"当前课程代码不存在";
		return;
	}
	//3.检查是否已选该课程
	for(auto& enrollment:enrollments){
		if(enrollment.first==studentId && enrollment.second==courseCode){
			cout << "该学生已选修此课程!" << endl;
			return;
		}
	}
	//4.添加选课记录
	enrollments.emplace_back(studentId,courseCode);
	cout << students[studentId].name << " 选修了 " <>studentId;
	//2.输入课程code
	cout<<"输入课程代码:";
	cin>>courseCode;
	//3.查找选课记录
	auto it = enrollments.begin();// 1. 初始化迭代器指向容器起始位置
	while (it != enrollments.end()) {// 2. 循环条件:未到达容器末尾
	    if (it->first == studentId && it->second == courseCode) {// 3. 找到匹配元素
	    	//查找当前学生名
	        string studentName = students[studentId].name;
	        //查找课程名
	        string courseTitle;
	        for (const auto& course : courses) {
	            if (course.code == courseCode) {
	                courseTitle = course.title;
	                break;
	            }
	        }
	        //删除选课记录
	        enrollments.erase(it);  // 关键:更新迭代器
	        cout << studentName << " 退选了 " << courseTitle << endl;
	        return;
	    }
	    ++it;
	}
}

//5.3.3课管理菜单
void enrollmentMenu() {
    int choice;
    do {
        cout << "\n===== 选课管理 =====\n";
        cout << "1. 学生选课\n";
        cout << "2. 学生退课\n";
        cout << "0. 返回主菜单\n";
        cout << "请选择: ";
        cin >> choice;
        
        switch(choice) {
            case 1:
                enrollCourse();
                break;
            case 2:
                dropCourse();
                break;
            case 0:
                return;
            default:
                cout << "无效选择!\n";
        }
    } while (true);
}
// 在main函数中添加调用
int main() {
    // ...之前代码...
    switch(choice) {
        case 1:
            studentMenu();
            break;
        case 2:
            courseMenu();
            break;
        case 3:
            enrollmentMenu();
            break;
        // 其他case将在后续步骤添加
    }
    // ...之后代码...
}

练习任务:

  1. 让不同学生选择不同课程

  2. 尝试重复选同一门课程

  3. 实现退课功能

  4. 观察list如何保持插入顺序

  5. 理解list的遍历和删除操作

主函数:

//6.主函数
int main(){
	int choice;
	do{
		displayMainMenu();
		cin>>choice;
		switch (choice) {
			case 1:
				studentMenu();
				break;
			case 2:
				courseMenu();
				break;
			case 3:
				enrollmentMenu();
				break;
			default:
				//TODO
				break;
		}
	}while(choice!=0);
	cout << "感谢使用,再见!\n";
	return 0;
}

多说一点(重点代码解释):

一.list> enrollments;代码详解

这行代码定义了一个名为 enrollments 的变量,它的类型是 list>。让我为你详细解释它的各个部分:

1. 分解理解

list

  • 这是C++标准模板库(STL)中的双向链表容器

  • 特点:可以高效地在任意位置插入和删除元素

  • 在这个项目中用于存储选课记录,因为选课记录可能频繁增删

pair

  • 这是STL中的模板类,表示一个包含两个元素的对(pair)

  • 这里的两个元素都是int类型

  • 第一个int将存储学生ID

  • 第二个int将存储课程代码

list>

  • 组合起来表示:一个链表,其中每个节点存储一个pair

  • 相当于一个可以动态增长的"对"的列表

2. 在项目中的具体用途

在选课系统中,enrollments用于记录哪些学生选了哪些课程:

// 表示学生ID 1001 选了课程代码 101
enrollments.push_back(make_pair(1001, 101));

// 也可以使用emplace_back直接构造
enrollments.emplace_back(1002, 102); // 学生1002选课102

3. 为什么选择这种设计?

  1. 高效增删:选课和退课操作频繁,list的插入删除都是O(1)时间复杂度

  2. 简单关联:只需要记录学生ID和课程代码的对应关系

  3. 扩展性:可以轻松改为list>如果未来需要添加更多信息(如选课时间)

对比 pair vs tuple

  • 相似点:都能存储固定数量的不同类型元素

  • 不同点

    • pair 只能存两个元素,tuple 可以存任意数量

    • pair 可以直接用 .first 和 .second 访问,tuple 需要用 get()

    • pair 更简洁,tuple 更灵活

适用场景

  • 如果需要存储超过两个相关联的数据,tuple 更合适

  • 如果只有两个数据,pair 更直观

 二.为什么明明有全局容器了,每个模块开始都定义变量?

1. 为什么定义这些变量?

int id, year;
string name;

这些变量用于临时存储用户输入的学生信息

  • id:学生的唯一标识(整数类型)

  • year:学生的年级(整数类型)

  • name:学生的姓名(字符串类型)

这些变量会在后续步骤中通过 cin 和 getline 接收用户输入,然后用于构造一个新的 Student 对象。


2. students.emplace(id, Student{id, name, year}) 是什么意思?

这行代码的作用是向 students 容器中插入一个新的学生对象。具体解析如下:

(1) students 是什么?
  • students 是一个全局的 map 容器。

  • 它以学生 id 为键(Key),对应的 Student 对象为值(Value)。

(2) emplace 方法
  • emplace 是 map 的成员函数,用于直接构造并插入一个键值对。

  • 它避免了临时对象的拷贝,效率更高(类似就地构造)。

(3) Student{id, name, year}
  • 这是用 idnameyear 构造一个 Student 对象的简洁写法(假设 Student 是一个结构体或类,且有对应的构造函数)。

  • 等价于 Student(id, name, year)(如果构造函数是显式定义的)。

合起来的作用:

将 id 作为键,Student{id, name, year} 作为值,插入到 students 这个 map 中。

三,在课程管理里面,为什么vector不能用迭代器查找? 

为什么 vector 的检查方式和其他容器不同?

容器类型 检查重复的方式 时间复杂度 适用场景
vector 遍历所有元素,逐个比较 O(n) 元素较少,或需要顺序存储
map / set 直接 count() 或 find() O(log n) 需要快速查找/去重
unordered_map / unordered_set 直接 count() 或 find() O(1)(平均) 需要极快查找,不关心顺序

1. vector 需要手动遍历检查的原因

vector 是一个 动态数组,它 不自动维护元素的唯一性,所以必须手动遍历检查:

for (const auto& course : courses) {
    if (course.code == code) {  // 逐个比较
        cout << "课程代码 " << code << " 已存在!" << endl;
        return;
    }
}
  • 缺点:如果 courses 很大,遍历会很慢(O(n))。

  • 优点vector 内存连续,遍历速度快(缓存友好),适合少量数据或需要顺序访问的场景。


2. 其他容器(map/set)如何检查重复?

(1)map(检查学生ID是否重复)

if (students.count(id) > 0) {  // O(log n)
    cout << "学生ID " << id << " 已存在!" << endl;
    return;
}
  • map 是基于 红黑树 的,count() 或 find() 是 O(log n) 的。

  • 不需要遍历,直接通过键(id)查找。

(2)set(检查课程类别是否重复)

if (categories.count(category) > 0) {  // O(log n)
    cout << "课程类别 " << category << " 已存在!" << endl;
    return;
}
  • set 也是基于红黑树,count() 是 O(log n) 的。

同样list也和vector差不多

四.clearInputBuffer的辅助函数的作用

其作用是清空标准输入缓冲区,避免残留字符对后续输入操作产生影响。下面对该函数进行详细解释:

函数定义与作用

clearInputBuffer()是一个无返回值(void)的函数,它不接受任何参数。该函数的主要功能是清除输入缓冲区中可能存在的残留字符,确保后续的输入操作能够正常进行。

函数内部实现

函数体由两条语句构成:

cin.clear();
cin.ignore(numeric_limits::max(), '\n');
1. cin.clear()
  • 作用:清除输入流(cin)的错误标志。
  • 何时需要:当输入操作失败时(例如,用户输入了非数字字符,而程序期望读取一个整数),输入流会被设置为错误状态,后续的输入操作都会失效。此时就需要调用cin.clear()来重置错误标志,让输入流恢复正常工作。
  • 注意cin.clear()仅仅是重置错误标志,它并不会清除输入缓冲区中的数据。
2. cin.ignore(numeric_limits::max(), '\n')
  • 作用:从输入缓冲区中提取并丢弃字符,直到满足特定的终止条件。
  • 参数说明
    • numeric_limits::max():这是输入流能够处理的最大字符数,表示最多可以忽略的字符数量。numeric_limits是 C++ 标准库中的一个模板类,用于获取各种数据类型的极限值。
    • '\n':终止字符,表示遇到换行符时就停止忽略操作。
  • 整体效果:该语句会忽略输入缓冲区中的所有字符,直到遇到换行符为止。这样可以确保缓冲区中残留的所有字符(包括换行符)都被清除。

为什么需要清空输入缓冲区?

在 C++ 中,输入操作(如cin >> variable)通常不会读取换行符(\n),换行符会残留在输入缓冲区中。如果后续的输入操作期望读取字符或字符串,这个残留的换行符就可能被读取,从而导致程序行为不符合预期。

例如,在读取一个整数后紧接着读取字符串时,如果不清除输入缓冲区,残留的换行符会被当作字符串的内容,导致读取的字符串为空。

示例场景

假设有如下代码:

int num;
string name;

cout << "请输入一个数字:";
cin >> num;  // 用户输入 123 并按下回车键

cout << "请输入您的姓名:";
getline(cin, name);  // 这里会直接读取到残留的换行符,导致name为空

如果在getline之前调用clearInputBuffer(),就能避免上述问题:

int num;
string name;

cout << "请输入一个数字:";
cin >> num;

clearInputBuffer();  // 清空输入缓冲区

cout << "请输入您的姓名:";
getline(cin, name);  // 现在可以正常读取用户输入的姓名

五.什么时候用emplace什么时候用emplace_back?

在 C++ 中,emplace 和 emplace_back 都是用于在容器中构造元素的函数,但它们的使用场景和适用容器有所不同。以下是详细对比:

核心区别

函数 适用容器 作用
emplace 关联容器(mapsetunordered_mapunordered_set
序列容器(listforward_listdeque
在容器的指定位置构造元素(需要迭代器指定位置)。
emplace_back 序列容器(vectordequelist 在容器的尾部构造元素(无需指定位置)。

详细解释

1. emplace_back
  • 适用场景:向容器尾部添加元素,且元素需要通过构造函数初始化。
  • 优势:直接在容器内存中构造对象,避免临时对象的拷贝 / 移动操作,效率更高。
  • 示例
    std::vector> vec;
    
    // 使用 push_back(需要先创建临时对象)
    vec.push_back({1, "Alice"});  // 隐式创建临时 pair,再拷贝到容器
    
    // 使用 emplace_back(直接构造)
    vec.emplace_back(1, "Alice");  // 直接在容器尾部构造 pair 对象
    
2. emplace
  • 适用场景
    • 关联容器(如 mapset):插入元素时自动排序或去重,无需指定位置。
    • 序列容器(如 list):在指定位置插入元素。
  • 优势
    • 对于关联容器:直接构造键值对,避免临时对象的拷贝。
    • 对于序列容器:在任意位置高效插入元素。
  • 示例
    // 关联容器示例
    std::map myMap;
    myMap.emplace(1, "Alice");  // 直接构造键值对,等价于 insert({1, "Alice"})
    
    // 序列容器示例(在指定位置插入)
    std::list myList = {1, 3};
    auto it = myList.begin();
    std::advance(it, 1);  // 指向位置 1(第二个元素)
    myList.emplace(it, 2);  // 在位置 1 插入元素 2,结果:[1, 2, 3]
    

何时选择哪个?

场景 选择 示例代码
向 vector/deque 尾部添加元素 emplace_back vec.emplace_back(1, "Alice");
向 map/set 插入元素 emplace myMap.emplace(1, "Alice");
在 list 的指定位置插入元素 emplace myList.emplace(it, 2);it 是迭代器)
在 vector 的中间插入元素 不推荐(效率低) 优先使用 push_back/emplace_back,避免在中间插入(会导致元素移动)

注意事项

  1. 参数传递

    • emplace_back:参数是构造对象所需的参数(如 emplace_back(arg1, arg2))。
    • emplace:对于 map/set,参数通常是键值对的构造参数(如 emplace(key, value))。
  2. 效率对比

    • emplace 系列函数通常更高效,因为它们避免了临时对象的创建和拷贝。
    • 但对于简单类型(如 intstring),push_back 和 emplace_back 的性能差异可忽略不计。
  3. 兼容性

    • emplace_back 是 C++11 引入的,旧代码可能使用 push_back
    • 优先使用 emplace_back 替代 push_back,除非需要兼容旧版本。

总结

  • 尾部插入:用 emplace_back(针对 vectordequelist)。
  • 关联容器插入:用 emplace(针对 mapset 等)。
  • 指定位置插入:用 emplace(针对 listdeque 等支持任意位置插入的容器)。

六.list,map,vector,set他们都是用什么函数删除?

在 C++ 中,listmapvectorset 删除元素的函数各不相同,且 remove 不是它们的成员函数(除了 std::list)。以下是详细对比:

各容器的删除方法

容器类型 删除元素的正确方法
std::list 1. 按值删除list.remove(value)(直接删除所有等于 value 的元素)
2. 按位置删除list.erase(iterator) 或 list.erase(first, last)
std::map 1. 按键删除map.erase(key)
2. 按位置删除map.erase(iterator)
std::vector 1. 按位置删除vector.erase(iterator) 或 vector.erase(first, last)
2. 删除尾部元素vector.pop_back()
std::set 1. 按值删除set.erase(value)
2. 按位置删除set.erase(iterator)

详细示例

1. std::list 的删除
#include 
#include 

int main() {
    std::list myList = {1, 2, 3, 2, 4};
    
    // 按值删除(删除所有等于2的元素)
    myList.remove(2);  // 结果:[1, 3, 4]
    
    // 按位置删除(删除第一个元素)
    auto it = myList.begin();
    myList.erase(it);  // 结果:[3, 4]
    
    return 0;
}
2. std::map 的删除
#include 
#include 

int main() {
    std::map myMap = {{1, "Alice"}, {2, "Bob"}, {3, "Charlie"}};
    
    // 按键删除
    myMap.erase(2);  // 删除键为2的元素,结果:{1: "Alice", 3: "Charlie"}
    
    // 按位置删除(删除第一个元素)
    auto it = myMap.begin();
    myMap.erase(it);  // 结果:{3: "Charlie"}
    
    return 0;
}
3. std::vector 的删除
#include 
#include 

int main() {
    std::vector myVector = {1, 2, 3, 4};
    
    // 按位置删除(删除第二个元素)
    auto it = myVector.begin() + 1;
    myVector.erase(it);  // 结果:[1, 3, 4]
    
    // 删除尾部元素
    myVector.pop_back();  // 结果:[1, 3]
    
    return 0;
}
4. std::set 的删除
#include 
#include 

int main() {
    std::set mySet = {1, 2, 3, 4};
    
    // 按值删除
    mySet.erase(3);  // 结果:{1, 2, 4}
    
    // 按位置删除(删除第一个元素)
    auto it = mySet.begin();
    mySet.erase(it);  // 结果:{2, 4}
    
    return 0;
}

常见误区:remove 与 erase 的区别

  1. remove 是算法,不是成员函数

    • std::remove 是标准库算法(定义在  中),适用于 所有容器(但需配合 erase 使用)。
    • 例如,在 vector 中删除值为 value 的元素:
      #include 
      myVector.erase(std::remove(myVector.begin(), myVector.end(), value), myVector.end());
      
    • 原理remove 将不需要的元素移到末尾,返回新的逻辑末尾迭代器,再用 erase 删除这些元素。
  2. std::list 的 remove 是成员函数

    • list.remove(value) 直接删除所有等于 value 的元素,无需配合 erase
    • 效率更高,因为 list 无需移动元素。

总结

容器 删除特定值的元素 删除特定位置的元素
list list.remove(value) list.erase(iterator)
map map.erase(key) map.erase(iterator)
vector vector.erase(remove(...)) vector.erase(iterator)
set set.erase(value) set.erase(iterator)

关键记忆点

  • 关联容器(map/set:通过键或迭代器删除。
  • 序列容器(vector/list:通过迭代器删除,list 可直接按值删除。

七.为什么不能使用范围 for 循环删除元素 (重点)

错误原因:迭代器失效

在 C++ 中,删除容器元素会使指向该元素的迭代器失效。你的代码使用了范围 for 循环(for(auto& it:enrollments)),这种循环的底层实现依赖于一个隐式的迭代器,当你调用 enrollments.erase(it) 后:

  1. 当前迭代器失效it 不再指向有效元素。
  2. 范围 for 循环无法正确推进:循环内部的隐式迭代器无法正确移动到下一个元素,导致程序崩溃或行为异常。

正确代码(使用显式迭代器)

你提供的第一段代码是正确的,因为它使用 显式迭代器 并在删除后正确处理迭代器:

auto it = enrollments.begin();
while (it != enrollments.end()) {
    if (it->first == studentId && it->second == courseCode) {
        // ... 查找学生名和课程名 ...
        it = enrollments.erase(it);  // 删除当前元素,并获取指向下一个元素的迭代器
        cout << studentName << " 退选了 " << courseTitle << endl;
        return;
    }
    ++it;  // 未删除时,正常推进迭代器
}

关键点erase(it) 返回指向下一个元素的迭代器,必须将其赋值给 it

范围 for 循环绝对不能用于删除元素,因为它的迭代器机制不支持在循环中修改容器结构。必须使用显式迭代器,并在删除元素后更新迭代器位置。

为什么范围 for 循环会出错?

对比错误代码:

for(auto& it:enrollments){  // 范围for循环的隐式迭代器
    if(...) {
        enrollments.erase(it);  // 删除元素后,隐式迭代器失效
        // 范围for循环无法正确推进迭代器,导致崩溃
    }
}

  • 范围 for 循环的工作原理
    • 底层使用隐式迭代器遍历容器。
    • 每次循环结束时,隐式迭代器会自动递增(如 ++it)。
  • 删除元素后的问题
    • 删除元素后,隐式迭代器指向的元素已失效,但循环仍会尝试递增它。
    • 这会导致未定义行为(如崩溃或跳过元素)。

注:该代码是本人自己所写,可能不够好,不够简便,欢迎大家指出我的不足之处。如果遇见看不懂的地方,可以在评论区打出来,进行讨论,或者联系我。上述内容全是我自己理解的,如果你有别的想法,或者认为我的理解不对,欢迎指出!!!如果可以,可以点一个免费的赞支持一下吗?谢谢各位彦祖亦菲!!!!!

你可能感兴趣的:(STL,C++,C++基础教学,C++项目,c++,list,开发语言,数据结构,c语言)