学生管理系统(C++实现)

发布于:2025-07-15 ⋅ 阅读:(12) ⋅ 点赞:(0)

这个学生管理系统涵盖了 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++ 标准库中的一个重要头文件,
// 全称是 ​​i​​nput/​​o​​utput ​​manip​​ulators(输入/输出操纵器)。
// 它提供了一系列用于格式化输入输出的工具函数,
// 主要配合 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++ 标准库中的一个函数,用于将字符串转换为整数。
                //它的名称是 "​​st​​ring ​​to​​ ​​i​​nteger" 的缩写,是 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;
                }

                // 验证ID
                if (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;
}


网站公告

今日签到

点亮在社区的每一天
去签到