(C++)学生管理系统(正式版)(map数组的应用)(string应用)(引用)(文件储存的应用)(C++教学)(C++项目)
目录
源代码:
代码详解:
学生成绩管理系统实现详解
一、系统整体设计思路
1. 数据结构选择
2. 功能模块划分
二、关键函数实现原理
1. 文件存储与加载
save_file函数
load_file函数
2. 核心数据操作
add函数
mod函数
find和del函数
3. 数据展示
display函数
statistics函数
三、核心技术详解
1. 字符串分割技术
2. map的使用技巧
3. 文件格式设计
4. 错误处理机制
源代码:
/** 头文件部分:包含程序所需的所有标准库头文件* <iostream> - 输入输出流* <map> - 映射容器* <string> - 字符串处理* <iomanip> - 输入输出格式化* <climits> - 整数类型限制* <fstream> - 文件流操作* <sstream> - 字符串流操作* <vector> - 动态数组容器*/
#include <iostream>
#include <map>
#include <string>
#include <iomanip>
#include <climits>
#include <fstream>
#include <sstream>
#include <vector>
using namespace std; // 使用标准命名空间,避免重复写std::/** 学生结构体定义* 包含学生的基本信息:姓名、性别、年龄、成绩和电话* 结构体(struct)是一种自定义数据类型,可以包含多个不同类型的数据成员*/
struct student{string name; // 学生姓名,使用string类型存储string sex; // 学生性别int age; // 学生年龄,整型int score; // 学生成绩,整型string tele; // 电话号码,使用string类型存储
};/** 菜单显示函数* 功能:在控制台显示系统的主菜单界面* cout是标准输出流对象,endl表示换行并刷新缓冲区* \n是换行符,=====用于美化菜单显示*/
void menu(){cout << "\n===== 学生成绩管理系统 =====" << endl;cout << "1. 添加学生成绩" << endl;cout << "2. 查询学生成绩" << endl;cout << "3. 修改学生成绩" << endl;cout << "4. 删除学生记录" << endl;cout << "5. 显示所有学生成绩" << endl;cout << "6. 统计分析" << endl;cout << "0. 退出系统" << endl;cout << "请输入你的选择: ";
}/** 添加学生信息函数* 参数:student_map - 学生信息的map容器引用* map是C++中的关联容器,存储键值对(key-value),这里key是学生ID,value是student结构体* emplace()方法直接在容器内构造元素,比insert更高效*/
void add(map<int,student>& student_map){cout<<"请输入学生相关信息:"<<endl;int id; // 学生ID,作为map的键student s; // 临时student对象,用于存储输入的数据cout<<"ID:";cin>>id; // 从标准输入读取学生IDcout<<"姓名:";cin>>s.name; // 读取学生姓名cout<<"性别:";cin>>s.sex; // 读取学生性别cout<<"年龄:";cin>>s.age; // 读取学生年龄cout<<"成绩:";cin>>s.score; // 读取学生成绩cout<<"电话:";cin>>s.tele; // 读取学生电话student_map.emplace(id,s); // 将学生信息插入map容器cout<<"添加成功!"<<endl; // 操作反馈
}/** 修改学生信息函数* 参数:student_map - 学生信息的map容器引用* count()方法检查map中是否存在指定键* map[key]返回对应值的引用,可以直接修改*/
void mod(map<int,student>& student_map){int a; // 用于存储用户输入的要修改的学生IDif (student_map.empty()) { // empty()检查map是否为空cout << "暂无学生记录!" << endl;return;}cout<<"请输入ID:";cin>>a;if(student_map.count(a)==0){ // count()返回键出现的次数(0或1)cout << "暂无该ID记录!" << endl;return;}else{student& s=student_map[a]; // 获取该学生的引用// 显示当前信息cout << "\n===== 当前学生信息 =====" << endl;cout << "ID:" << a << endl;cout << "姓名:" << s.name << endl;cout << "性别:" << s.sex << endl;cout << "年龄:" << s.age << endl;cout << "成绩:" << s.score << endl;cout << "电话:" << s.tele << endl;// 输入新信息cout<<"请修改学生相关信息:"<<endl;cout<<"姓名:";cin>>s.name; // 直接修改原数据cout<<"性别:";cin>>s.sex;cout<<"年龄:";cin>>s.age;cout<<"成绩:";cin>>s.score;cout<<"电话:";cin>>s.tele;cout<<"修改成功!"<<endl;}
}/** 查询学生信息函数* 功能:根据ID查询并显示学生信息* 使用了与修改函数类似的查找逻辑*/
void find(map<int,student>& student_map){int a; // 存储要查询的学生IDif (student_map.empty()) {cout << "暂无学生记录!" << endl;return;}cout<<"请输入ID:";cin>>a;if(student_map.count(a)==0){cout << "暂无该ID记录!" << endl;return;}else{student& s=student_map[a]; // 获取学生引用// 显示查询结果cout << "\n===== 学生信息 =====" << endl;cout << "ID:" << a << endl;cout << "姓名:" << s.name << endl;cout << "性别:" << s.sex << endl;cout << "年龄:" << s.age << endl;cout << "成绩:" << s.score << endl;cout << "电话:" << s.tele << endl;}
}/** 删除学生记录函数* 功能:根据ID删除学生记录* erase()方法从map中删除指定键的元素*/
void del(map<int,student>& student_map){int a; // 存储要删除的学生IDif (student_map.empty()) {cout << "暂无学生记录!" << endl;return;}cout<<"请输入ID:";cin>>a;if(student_map.count(a)==0){cout << "暂无该ID记录!" << endl;return;}else{student& s=student_map[a]; // 获取学生引用// 显示将被删除的信息cout << "\n===== 当前学生信息 =====" << endl;cout << "ID:" << a << endl;cout << "姓名:" << s.name << endl;cout << "性别:" << s.sex << endl;cout << "年龄:" << s.age << endl;cout << "成绩:" << s.score << endl;cout << "电话:" << s.tele << endl;student_map.erase(a); // 从map中删除该学生cout << "删除成功!" << endl;}
}/** 显示所有学生信息函数* 功能:格式化输出所有学生信息* setw()设置输出字段宽度,实现对齐效果* 使用结构化绑定(C++17特性)遍历map*/
void display(map<int,student>& student_map){if (student_map.empty()) {cout << "暂无学生记录!" << endl;return;}cout << "\n===== 所有学生列表 =====" << endl;// 输出表头,setw设置列宽cout<<setw(5)<<"ID"<<setw(10)<<"姓名"<<setw(5)<<"性别"<<setw(5)<<"年龄"<<setw(5)<<"成绩"<<setw(15)<<"电话"<<endl;// 遍历map中的所有元素// const auto& [id,s] - C++17结构化绑定,将pair解包为id和sfor(const auto& [id,s]:student_map){// 格式化输出每个字段cout<<setw(5)<<id // ID,5字符宽<<setw(10)<<s.name // 姓名,10字符宽<<setw(5)<<s.sex // 性别,5字符宽<<setw(5)<<s.age // 年龄<<setw(5)<<s.score // 成绩<<setw(15)<<s.tele // 电话,15字符宽<<endl; // 换行}
}/** 统计分析函数* 功能:计算并显示成绩统计信息* INT_MIN和INT_MAX是<climits>中定义的整型极限值*/
void statistics(map<int,student>& student_map){if (student_map.empty()) {cout << "暂无学生记录!" << endl;return;}float sum=0; // 成绩总和int count=0; // 学生人数int max_score = INT_MIN; // 初始化为最小整数int min_score = INT_MAX; // 初始化为最大整数int max_id = -1, min_id = -1; // 最高/最低分学生ID// 遍历所有学生for(const auto& [id, s] : student_map){sum += s.score; // 累加成绩count++; // 计数// 更新最高分记录if(s.score > max_score) {max_score = s.score;max_id = id;}// 更新最低分记录if(s.score < min_score) {min_score = s.score;min_id = id;}}// 输出统计结果cout << "\n===== 成绩分析 =====" << endl;cout << "学生总数: " << count << endl;cout << "平均成绩: " << sum/count << endl; // 注意整数除法问题cout << "最高分: " << max_score << " (ID: " << max_id << ")" << endl;cout << "最低分: " << min_score << " (ID: " << min_id << ")" << endl;
}/** 保存数据到文件函数* 功能:将学生数据保存到文本文件* ofstream是输出文件流类,用于写入文件* \t是制表符,用于分隔字段*/
void save_file(map<int,student>& student_map){// 1. 创建并打开文件ofstream outfile("student.txt"); // 默认模式是ios::out// 2. 检查文件是否成功打开if(!outfile){cerr<<"无法打开文件"<<endl; // cerr是标准错误流return;}// 3. 写入表头行outfile <<"ID\t姓名\t性别\t年龄\t成绩\t电话"<<endl;// 4. 遍历写入每条记录for(auto& [id,s]:student_map){outfile<<id<<"\t"<<s.name<<"\t"<<s.sex<<"\t"<<s.age<<"\t"<<s.score<<"\t"<<s.tele<<endl;}// 5. 关闭文件(析构函数会自动关闭,但显式关闭是好习惯)outfile.close();cout<<"数据已经写入文件student.txt"<<endl;
}/** 从文件加载数据函数* 功能:从文本文件读取学生数据* ifstream是输入文件流类,用于读取文件* getline()按行读取,istringstream分割字段*/
void load_file(map<int,student>& student_map){// 1. 打开文件ifstream infile("student.txt");// 2. 检查文件是否成功打开if(!infile){cerr<<"暂时无法打开文件"<<endl;return;}// 3. 清空当前数据student_map.clear();// 4. 读取并跳过表头行string header;getline(infile,header);// 5. 逐行读取数据string line; // 存储每行内容int line_count=0; // 行计数器int success_count=0;// 成功加载记录数while (getline(infile,line)) // 读取一行{line_count++;if(line.empty()){ // 跳过空行continue;}// 5.1 使用字符串流分割字段istringstream iss(line); // 创建字符串流vector<string> fields; // 存储分割后的字段string field; // 临时存储每个字段// 按制表符分割字段while(getline(iss,field,'\t')){fields.push_back(field);}// 6. 检查字段数量是否正确(应有6个)if (fields.size() != 6) {cerr << "警告:第 " << line_count << " 行字段数量错误 ("<< fields.size() << " 个字段,应为6个)" << endl;cerr << "内容: " << line << endl;continue;}try // 尝试解析字段{// 解析字段int id=stoi(fields[0]); // 字符串转整数student s;s.name=fields[1];s.sex=fields[2];s.age=stoi(fields[3]);s.score=stoi(fields[4]);s.tele=fields[5];// 添加到mapstudent_map.emplace(id,s);success_count++;}catch(const exception& e) // 捕获异常{cerr << "解析第 " << line_count << " 行时出错: " << e.what() << endl;cerr << "内容: " << line << endl;}}// 7. 关闭文件infile.close();// 8. 输出加载结果if (line_count - success_count > 0) {cout << "警告:有 " << (line_count - success_count) << " 条记录未成功加载" << endl;}
}/** 主函数* 功能:程序入口,实现主循环和菜单选择*/
int main(){map<int,student> student_map; // 创建学生map容器// 启动时加载数据load_file(student_map);int choice; // 存储用户选择while(true){ // 主循环menu(); // 显示菜单cin >> choice; // 读取用户输入switch (choice) { // 根据选择调用不同功能case 0: // 退出save_file(student_map); // 退出前保存数据cout <<"感谢使用,再见"<< endl;return 0; // 结束程序case 1:add(student_map);break;case 2:find(student_map);break;case 3:mod(student_map);break;case 4:del(student_map);break;case 5:display(student_map);break;case 6:statistics(student_map);break;default:cout << "无效选项,请重新输入!" << endl;break;}}return 0;
}
代码详解:
学生成绩管理系统实现详解
下面我将从整体设计思路、关键函数实现原理和核心技术点三个方面详细介绍这个学生成绩管理系统的实现。
一、系统整体设计思路
1. 数据结构选择
-
核心数据结构:使用
map<int, student>
存储学生数据-
键(key):学生ID(整数),保证唯一性
-
值(value):student结构体,包含完整学生信息
-
-
选择原因:
-
按ID查询效率高(O(log n))
-
自动按ID排序
-
易于实现增删改查
-
2. 功能模块划分
1. 数据管理核心- 添加(add)- 查询(find)- 修改(mod)- 删除(del)2. 数据展示- 列表显示(display)- 统计分析(statistics)3. 持久化存储- 保存到文件(save_file)- 从文件加载(load_file)4. 用户界面- 菜单系统(menu)- 主控制循环(main)
二、关键函数实现原理
1. 文件存储与加载
save_file
函数
void save_file(map<int,student>& student_map){ofstream outfile("student.txt");// [错误处理...]outfile <<"ID\t姓名\t性别\t年龄\t成绩\t电话"<<endl;for(auto& [id,s]:student_map){outfile<<id<<"\t"<<s.name<<"\t"<<s.sex<<"\t"<<s.age<<"\t"<<s.score<<"\t"<<s.tele<<endl;}// [关闭文件...]
}
-
实现原理:
-
创建文本文件输出流
-
写入表头行(字段名称)
-
遍历map,将每个学生信息按字段写入,用制表符(
\t
)分隔 -
每行对应一个学生记录
-
-
设计考虑:
-
使用制表符分隔:比逗号更可靠(姓名中可能包含逗号)
-
文本格式:便于人工查看和调试
-
简单直接:不处理复杂转义,假设数据中不包含制表符
-
load_file
函数
void load_file(map<int,student>& student_map){ifstream infile("student.txt");// [错误处理...]getline(infile,header); // 跳过标题行while(getline(infile,line)) {istringstream iss(line);vector<string> fields;string field;while(getline(iss,field,'\t')) {fields.push_back(field);}// [字段解析...]}// [关闭文件...]
}
-
实现原理:
-
创建输入文件流
-
跳过标题行
-
逐行读取,使用
istringstream
分割制表符 -
将分割后的字段存入vector
-
解析字段并存入map
-
-
核心技术:
-
字符串分割:使用
istringstream
+getline
组合-
istringstream
将字符串转为可操作的流 -
getline
的第三个参数指定分隔符(这里是\t
)
-
-
健壮性处理:
-
检查字段数量
-
try-catch捕获转换异常
-
统计成功/失败记录数
-
-
2. 核心数据操作
add
函数
void add(map<int,student>& student_map){// [输入ID...]if(student_map.count(id)>0){// ID已存在处理}student s;// [输入其他字段...]student_map.emplace(id,s);
}
-
关键点:
-
先检查ID是否已存在(避免覆盖)
-
使用
emplace
直接构造元素,比insert
更高效 -
未做数据有效性校验(实际应用应添加)
-
mod
函数
void mod(map<int,student>& student_map){// [输入ID...]student& s=student_map[id]; // 获取引用// [显示当前值...]// [输入新值...]// 直接通过引用修改原数据
}
-
关键点:
-
使用引用(
&
)直接修改map中的元素 -
显示当前值让用户知道在修改什么
-
同样缺乏输入验证
-
find
和del
函数
-
共用相同查找逻辑:
if(student_map.count(a)==0){// 不存在处理 } student& s=student_map[a]; // 获取引用
-
设计一致性:所有操作都先检查存在性,保证健壮性
3. 数据展示
display
函数
if(student_map.count(a)==0){// 不存在处理
}
student& s=student_map[a]; // 获取引用
-
格式化输出:
-
setw(n)
:设置字段宽度,实现对齐 -
宽度值需要根据实际数据调整
-
适合控制台表格显示
-
statistics
函数
int max_score = INT_MIN;
int min_score = INT_MAX;if(s.score > max_score) {max_score = s.score;max_id = id;
}
// [类似处理min...]
-
极值查找技巧:
-
初始化max为最小可能值,min为最大可能值
-
确保第一个元素一定会被记录
-
同时记录对应的ID
-
三、核心技术详解
1. 字符串分割技术
为什么这样实现:
istringstream iss(line);
vector<string> fields;
string field;while(getline(iss,field,'\t')) {fields.push_back(field);
}
-
istringstream:
-
将字符串包装成流,可以使用流操作
-
比C风格的
strtok
更安全(不修改原字符串)
-
-
getline分隔:
-
第三个参数指定分隔符(这里是
\t
) -
自动处理连续分隔符
-
不会跳过空字段(如
a\t\tc
会得到["a","","c"])
-
替代方案比较:
方法 | 优点 | 缺点 |
---|---|---|
istringstream+getline | 安全、标准 | 稍慢 |
strtok | 快速 | 不安全、修改原串 |
手动find+substr | 完全控制 | 代码复杂 |
2. map的使用技巧
元素访问方式:
// 安全访问(不自动插入)
if(student_map.count(id)>0){student& s = student_map[id];
}// 直接访问(不存在时会插入)
student& s = student_map[id];
遍历方法:
// C++17结构化绑定
for(const auto& [id,s] : student_map){}// 传统方式
for(const auto& pair : student_map){int id = pair.first;student s = pair.second;
}
3. 文件格式设计
文本格式选择原因:
-
可读性强:可以直接用文本编辑器查看
-
调试方便:容易发现数据问题
-
跨平台:不同系统都能处理
-
兼容性:其他程序(如Excel)可以直接打开
潜在改进:
-
添加引号处理:
"张三,李四"\t男\t20
-
转义字符:处理字段中的制表符
-
CSV格式:更通用但需要处理逗号
4. 错误处理机制
当前实现:
-
文件打开失败检查
-
字段数量验证
-
stoi转换的try-catch
-
记录加载成功/失败统计
可扩展方向:
-
更详细的错误日志
-
数据校验(年龄范围、电话格式等)
-
恢复机制(跳过错误行继续)
注:该代码是本人自己所写,可能不够好,不够简便,欢迎大家指出我的不足之处。如果遇见看不懂的地方,可以在评论区打出来,进行讨论,或者联系我。上述内容全是我自己理解的,如果你有别的想法,或者认为我的理解不对,欢迎指出!!!如果可以,可以点一个免费的赞支持一下吗?谢谢各位彦祖亦菲!!!!!