学生管理系统(C++实现)
这个学生管理系统涵盖了 C++ 的多个核心知识点:
1. 面向对象编程 (OOP)
• 类与对象:Student 和 StudentManager 类
• 封装:私有成员变量和公共接口
• 构造函数:默认构造和参数化构造
• 成员函数:getter/setter 方法
• 操作符重载:operator< 用于排序
2. 标准模板库 (STL)
• 容器:vector 存储学生对象
• 算法:sort 排序算法
• 迭代器:遍历容器元素
• 字符串:string 操作
3. 输入输出系统
• 标准 I/O:cin, cout, cerr
• 文件 I/O:ifstream, ofstream
• 格式化输出:<iomanip> 中的 setw, left, fixed, setprecision
4. 内存管理
• 智能指针:未显式使用,但通过 vector 自动管理
• 引用:const auto& 避免复制
• 指针:查找学生返回指针
5. 异常处理
• 输入验证:cin.fail() 检查
• 流状态恢复:cin.clear(), cin.ignore()
• 基本异常处理:try-catch 在加载函数中
6. 函数与 Lambda
• 成员函数:各种管理功能
• Lambda 表达式:自定义排序规则
• 函数封装:独立功能模块化
7. 类型转换
• stoi:字符串转整数
• stod:字符串转双精度浮点数
8. 字符串处理
• 查找:find(), find(',', pos)
• 子串:substr()
• 空值检查:npos
9. 控制结构
• 循环:while, for (范围 for)
• 条件:if-else, switch-case
• 跳转:break, continue, return
10. 其他特性
• 命名空间:using namespace std
• 常量:const 成员函数和参数
• 头文件:合理包含标准库头文件
源代码:
//提供基本的输入输出功能
#include <iostream>
//提供动态数组容器
#include <vector>
//提供文件操作功能
#include <fstream>
//包含各种算法函数
#include <algorithm>
//<iomanip> 是 C++ 标准库中的一个重要头文件,
// 全称是 input/output manipulators(输入/输出操纵器)。
// 它提供了一系列用于格式化输入输出的工具函数,
// 主要配合 iostream 库中的 cin 和 cout 等流对象使用。
// 用于格式化输出
#include <iomanip> // 用于输入验证
#include <limits>
//提供字符串操作功能
#include <string> //可以直接使用cout而不必写std::cout
using namespace std;// ==================== 学生类 ====================
class Student {
private:string name;int id;double score;public:// 构造函数(默认构造函数、带参数构造函数)// 默认构造函数 :开始成员初始化列表Student() : name(""), id(0), score(0.0) {} // 参数化构造函数Student(string n, int i, double s): name(n), id(i), score(s) {} // Getter方法(封装思想)// 常量成员函数string getName() const { return name; }int getID() const { return id; }double getScore() const { return score; }// Setter方法(封装思想)void setName(string n) { name = n; }void setID(int i) { id = i; }void setScore(double s) { score = s; }// 显示学生信息void display() const {// 格式化输出//std::left: 设置输出左对齐//std::setw() : 设置下一个字段的输出宽度cout << left << setw(10) << id << left << setw(20) << name//endl 插入换行符并刷新输出缓冲区<< left << setw(10) << score << endl;}// 操作符重载 - 用于排序// 按成绩升序排序bool operator<(const Student& other) const {return score < other.score; }
};// ==================== 学生管理系统类 ====================
class StudentManager {//访问修饰符(private, public, protected)后面必须跟冒号
private:// 使用vector容器存储学生对象vector<Student> students;
public:// ======== 核心功能 ========// 添加学生(参数验证)void addStudent() {string name;int id;double score;cout << "\n=== 添加学生 ===" << endl;// 输入姓名cout << "请输入学生姓名: ";// 清除输入缓冲区cin.ignore(); /*getline(cin, name); 是:读取整行文本的标准方法特别适合需要包含空格的输入正确处理换行符和输入缓冲区在学生管理系统中用于读取完整学生姓名需要与 cin.ignore() 配合使用以避免输入问题*/getline(cin, name);// 输入ID(验证唯一性)bool idExists;do {idExists = false;cout << "请输入学生ID: ";//cin >> id;// 检查输入是否为有效整数if (!(cin >> id)) {// 清除错误状态cin.clear();// 忽略无效输入cin.ignore(numeric_limits<streamsize>::max(), '\n');cout << "错误:请输入有效的数字ID!" << endl;return; // 重新开始循环}// 检查ID是否已存在/*auto:自动类型推导(编译器推断为 Student)&:引用(避免复制对象)const:保证不修改对象*/for (const auto& student : students) {if (student.getID() == id) {cout << "错误:该ID已存在!请重新输入。" << endl;idExists = true;break;}}} while (idExists);// 输入成绩(验证范围)do {cout << "请输入学生成绩 (0-100): ";//cin >> score;if (!(cin >> score)) {// 清除错误状态cin.clear();// 忽略无效输入cin.ignore(numeric_limits<streamsize>::max(), '\n');cout << "错误:请输入有效的数字ID!" << endl;return; // 重新开始循环}if (score < 0 || score > 100) {cout << "错误:成绩必须在0-100之间!" << endl;}} while (score < 0 || score > 100);// 创建学生对象并添加到vector//将新创建的学生对象添加到 vector 的末尾students.push_back(Student(name, id, score));cout << "学生添加成功!" << endl;}// 显示所有学生void displayAll() const {if (students.empty()) {cout << "\n没有学生记录!" << endl;return;}cout << "\n=== 所有学生 ===" << endl;cout << left << setw(10) << "ID"<< left << setw(20) << "姓名"<< left << setw(10) << "成绩" << endl;//输出一行由40个连字符(-)组成的字符串,然后换行cout << string(40, '-') << endl;for (const auto& student : students) {student.display(); // 调用对象方法}}// 根据ID查找学生(使用const引用提高效率)Student* findStudent(int id) {for (auto& student : students) {if (student.getID() == id) {return &student; // 返回指针}}return nullptr; // 未找到返回空指针}// 更新学生信息void updateStudent() {int id;cout << "\n请输入要更新的学生ID: ";/* cin >> id;*/if (!(cin >> id)) {// 清除错误状态cin.clear();// 忽略无效输入cin.ignore(numeric_limits<streamsize>::max(), '\n');cout << "错误:请输入有效的数字ID!" << endl;return;}Student* student = findStudent(id);//!student 在 student 为空时成立if (!student) {cout << "找不到该学生!" << endl;return;}string name;double score;cout << "请输入新的姓名: ";cin.ignore();getline(cin, name);do {cout << "请输入新的成绩 (0-100): ";//cin >> score;if (!(cin >> score)) {// 清除错误状态cin.clear();// 忽略无效输入cin.ignore(numeric_limits<streamsize>::max(), '\n');cout << "错误:请输入有效的数字ID!" << endl;return;}if (score < 0 || score > 100) {cout << "错误:成绩必须在0-100之间!" << endl;}} while (score < 0 || score > 100);//通过指针调用成员函数//等价于 (*student).setName(name)student->setName(name);//通过指针调用成员函数//等价于 (*student).setScore(score)student->setScore(score);cout << "学生信息更新成功!" << endl;}// 删除学生(使用迭代器)void deleteStudent() {int id;cout << "\n请输入要删除的学生ID: ";cin >> id;//返回指向容器第一个元素的迭代器//等价于 指向第一个学生对象的指针auto it = students.begin();while (it != students.end()) {if (it->getID() == id) {// 删除元素并返回下一个位置it = students.erase(it);cout << "学生删除成功!" << endl;return;}else {++it;}}cout << "找不到该学生!" << endl;}// ======== 扩展功能 ========// 按成绩排序(使用STL算法和lambda表达式)void sortStudents() {if (students.empty()) {cout << "\n没有学生记录!" << endl;return;}// 使用sort算法和自定义排序规则/* sort(students.begin(), students.end());*/// 使用lambda表达式反转排序(降序)//sort(students.begin(), students.end(), ...);/* []:捕获列表(空,表示不捕获外部变量)(const Student & a, const Student & b):参数列表{...}:函数体无显式返回类型声明(编译器自动推导为 bool)*/sort(students.begin(), students.end(),[](const Student& a, const Student& b) {return a.getScore() > b.getScore(); // 降序排序});cout << "按成绩降序排序完成!" << endl;}// 计算平均分void calculateAverage() const {if (students.empty()) {cout << "\n没有学生记录!" << endl;return;}double total = 0.0;for (const auto& student : students) {total += student.getScore();}// 设置输出精度/* 设置后所有后续浮点数输出:固定小数格式:不使用科学计数法两位小数:即使整数也显示两位小数(如 85.00)*/cout << fixed << setprecision(2); cout << "\n班级平均分: " << total / students.size() << endl;}// ======== 文件操作 ========// 保存数据到CSV文件(文件I/O)void saveToFile(const string& filename) {//创建输出文件流ofstream outFile(filename);//检查文件是否成功打开if (!outFile) {cerr << "无法打开文件进行写入!" << endl;return;}// 写入CSV表头outFile << "ID,姓名,成绩\n";// 写入每个学生数据for (const auto& student : students) {outFile << student.getID() << ","<< student.getName() << ","<< student.getScore() << "\n";}//关闭文件outFile.close();cout << "\n数据已保存到 " << filename << endl;}// 从CSV文件加载数据(文件I/O)void loadFromFile(const string& filename) {//创建输入文件流ifstream inFile(filename);// 检查文件是否成功打开if (!inFile) {cerr << "错误:无法打开文件 '" << filename << "' 进行读取!" << endl;return;}// 清空当前学生记录students.clear();string line;int lineNumber = 0;int successCount = 0;int errorCount = 0;// 跳过CSV文件头行if (getline(inFile, line)) {lineNumber++;// 验证标题行格式if (line != "ID,姓名,成绩") {cerr << "警告:文件标题行不匹配,可能不是标准格式" << endl;}}// 逐行读取文件内容while (getline(inFile, line)) {lineNumber++;// 跳过空行if (line.empty()) {continue;}//解析CSV行//查找第一个逗号//在字符串 line 中查找第一个逗号 ','//从位置 0(开头)开始搜索size_t pos1 = line.find(','); //从第一个逗号后查找第二个逗号//从位置 pos1 + 1 开始搜索(跳过第一个逗号)size_t pos2 = line.find(',', pos1 + 1);// 验证找到两个逗号if (pos1 == string::npos) {//throw runtime_error("未找到第一个逗号分隔符");//cerr << "未找到第一个逗号分隔符" << endl;cerr << "警告:行 " << lineNumber << " 未找到第一个逗号分隔符" << endl;return;}if (pos2 == string::npos) {//throw runtime_error("未找到第二个逗号分隔符");//cerr << "未找到第二个逗号分隔符" << endl;cerr << "警告:行 " << lineNumber << " 未找到第二个逗号分隔符" << endl;return;}// 提取ID字段string idStr = line.substr(0, pos1);if (idStr.empty()) {//throw runtime_error("ID字段为空");cerr << "警告:行 " << lineNumber << " ID字段为空" << endl;return;}// 验证找到两个逗号/* pos1 != string::npos字面意思:位置 pos1 不等于"未找到"值实际含义:成功找到了逗号等价于:find() 操作成功*/if (pos1 != string::npos && pos2 != string::npos) {// 提取ID/* line.substr(0, pos1)作用:从字符串 line 中提取子字符串参数:0:起始位置(从索引0开始)pos1:子字符串的长度(字符数)返回值:一个新字符串,包含 line[0] 到 line[pos1 - 1] 的字符示例:如果 line = "101,张三,85.5" 且 pos1 = 3,则返回 "101"*///stoi 函数详解:字符串到整数的转换//stoi 是 C++ 标准库中的一个函数,用于将字符串转换为整数。//它的名称是 "string to integer" 的缩写,是 C++11 引入的一组字符串转换函数之一int id = 0;try {id = stoi(idStr);}catch (const invalid_argument& e) {cerr << "警告:行 " << lineNumber << " ID格式无效: " + idStr << endl;return;/*throw runtime_error("ID格式无效: " + idStr);*/}catch (const out_of_range& e) {/*throw runtime_error("ID超出范围: " + idStr);*/cerr << "警告:行 " << lineNumber << " ID超出范围: " + idStr << endl;return;}// 验证IDif (id <= 0) {//throw runtime_error("ID必须是正整数: " + to_string(id));cerr << "警告:行 " << lineNumber << " ID必须是正整数: " + to_string(id) << endl;return;}// 提取姓名// pos2 - pos1 - 1//确保只获取姓名内容//不包括分隔符逗号string name = line.substr(pos1 + 1, pos2 - pos1 - 1);if (name.empty()) {cerr << "警告:行 " << lineNumber << " 姓名字段为空" << endl;return;}// 提取成绩//line.substr(pos2 + 1)/* 作用:从字符串 line 中提取子字符串参数:pos2 + 1:起始位置(第二个逗号之后)无长度参数:提取到字符串末尾返回值:包含从 pos2 + 1 到行尾的所有字符示例:如果 line = "101,张三,85.5" 且 pos2 = 6返回 "85.5"*/double score =0;try{score = stod(line.substr(pos2 + 1));if (score < 0 || score > 100) {cerr << "警告:行 " << lineNumber << " 成绩:"<<score <<" \n错误:成绩必须在0 - 100之间!" << endl;return;}}catch (const std::exception&){cerr << "警告:行 " << lineNumber << " 成绩字段为空" << endl;return;}// 创建学生对象并添加到容器students.push_back(Student(name, id, score));}}// 关闭文件inFile.close();cout << "\n从 " << filename << " 加载数据成功!" << endl;}
};// ==================== 用户界面 ====================
void displayMenu() {cout << "\n===== 学生管理系统 =====" << endl;cout << "1. 添加学生" << endl;cout << "2. 显示所有学生" << endl;cout << "3. 查找学生" << endl;cout << "4. 更新学生信息" << endl;cout << "5. 删除学生" << endl;cout << "6. 按成绩排序(降序)" << endl;cout << "7. 计算平均分" << endl;cout << "8. 保存数据到文件" << endl;cout << "9. 从文件加载数据" << endl;cout << "0. 退出" << endl;cout << "=======================" << endl;cout << "请选择操作: ";
}// ==================== 主函数 ====================
int main() {StudentManager manager;int choice;const string FILENAME = "students.csv";cout << "欢迎使用学生管理系统!" << endl;// 尝试加载数据文件(如果存在)manager.loadFromFile(FILENAME);do {while (true) {displayMenu();cin >> choice;// 检查输入流是否处于失败状态//输入数据类型不匹配(如期望整数但输入字母)if (cin.fail()) {// 清除错误标志cin.clear();//从缓冲区中移除所有字符,直到遇到换行符//等价于 清空当前行的所有输入cin.ignore(numeric_limits<streamsize>::max(), '\n'); // 忽略无效输入cout << "无效输入,请重新选择!" << endl;continue;}else {//退出while循环break;}}switch (choice) {case 1:manager.addStudent();break;case 2:manager.displayAll();break;case 3: {int id;cout << "请输入要查找的学生ID: ";//cin >> id;if (!(cin >> id)) {// 清除错误状态cin.clear();// 忽略无效输入cin.ignore(numeric_limits<streamsize>::max(), '\n');cout << "错误:请输入有效的数字ID!" << endl;break; // 重新开始循环}Student* student = manager.findStudent(id);if (student) {cout << "\n找到学生:" << endl;cout << left << setw(10) << "ID"<< left << setw(20) << "姓名"<< left << setw(10) << "成绩" << endl;cout << string(40, '-') << endl;student->display();}else {cout << "找不到该学生!" << endl;}break;}case 4:manager.updateStudent();break;case 5:manager.deleteStudent();break;case 6:manager.sortStudents();manager.displayAll();break;case 7:manager.calculateAverage();break;case 8:manager.saveToFile(FILENAME);break;case 9:manager.loadFromFile(FILENAME);break;case 0:cout << "感谢使用学生管理系统!" << endl;break;default:cout << "无效选择,请重新输入!" << endl;}} while (choice != 0);return 0;
}