2023-01-12 用C++完成一个多平台可用的学生管理系统

用C++完成一个跨平台可用的学生管理系统

  • 前言
  • 一、Windows和Linux终端编码
  • 二、完整代码
  • 总结


前言

学生管理系统可能是计算机相关专业学生在学完C语言和C++之后能做的比较完整的一个程序, 所以问答中经常出现. 例如设计学生成绩管理系统.

用C语言或C++的基本特性就完全能够解决, 基本用不到多线程, 多态, 模板等.

但有个小问题, 在Windows平台和Linux平台, 对中文的编码和输出不太一样, Windows终端是GBK编码, 虽然可以用简单命令转为UTF-8编码, 但实践操作中发现, 这只适用于汉字输出, 对于输入是一塌糊度. 如果是纯Windows平台程序, 我倒是建议将代码编码格式直接设置为GBK, 这样对于输入输出不会有什么问题, 只不过会有一些编码警告, 忽略即可.

如果要跨平台, 甚至日后进行拓展, 则需要UTF-8编码, 而对编码的转换, 需要一些函数实现, 目前问题主要集中在Windows中.


一、Windows和Linux终端编码

可能是某些历史原因, Windows终端选择用GBK编码, 而Linux终端则选择用utf8编码, 这导致了跨平台IO的不同, 一套代码不能在两个平台使用.

以前我们讨论过, 关于中文编码, 存储, 编码转换, 多字节, 宽字节等等, 总之到处是坑, 防不胜防, 不亲自踩几个坑, 很难知道这水有多混.

由于Linux终端编码如此贴心, 无需太多顾忌, 我们只需处理号Windows终端编码就好了.

我们需要判断目前程序是在哪个平台, 于是要用到宏:

#ifdef __WIN64
    SetConsoleOutputCP(CP_UTF8); // 终端输出的是UTF8编码
    SetConsoleCP(CP_ACP);        // 终端输入的是GBK编码
#endif

以上代码的意思是如果定义了win64, 也就是在Windows64平台, 那么设置输出编码是UTF8, 输入编码是系统的local编码, 也就是中文GBK. 当然以上编码需要windows.h头文件, 这个会在下面程序代码中.

为什么不把输入编码设为UTF8, 和Linux一样, 这样就省事多了. 开始我也是这么想的, 但是马上就踩坑了, 当输入设置为utf8, 汉字无法输入, 我不知到它究竟将汉字转换成什么东西, 总之是不可以, 这个终端黑盒没有透露什么细节, 所以没办法.

退而求其次, 用终端local编码进行输入, 因为我们的程序编码是utf8, 输入进来的是GBK, 中间没有转化, 所以如果直接看, 就是乱码, 直接输出也是乱码.

为了补足编码转换, 我用了下面这个Windows专有的转换函数. 通过终端传过来的四不像编码文字转换为宽字符( MultiByteToWideChar() ), 再将宽字符转回真正的utf8编码文字( WideCharToMultiByte() ).

细节上, 由于名字很可能有空格, 所以需要读取整行, 但是通常如果直接读会读到上一个输入剩下的换行符, 完美错过真正的内容, 所以需要进行输入流清空( ignore() ). 同时为防止姓名字过多, 一般是故意的, 但某些国家名字百八十个字母也是有的, 需要在超出缓冲区后清空输入流, 以防止后续直接跳出.

为了节省点创建缓冲区时间, 我把缓冲区设置为静态区域, 但要注意, 静态区域在你用完后不会自己清空, 所以需要手动清空,以备后用.

#ifdef __WIN64
#include 
void gbkToUtf8(std::istream &istm, char *chrs)
{
    static char temp[BUFFERSIZE] = {};
    static wchar_t wTemp[WCHARBUFFERSIZE] = {};
    istm.clear();
    istm.ignore(CLEARCHARSIZE, '\n');
    if (!istm.getline(temp, BUFFERSIZE))
    {
        istm.clear();
        istm.ignore(CLEARCHARSIZE, '\n');
    }
    MultiByteToWideChar(CP_ACP, 0, temp, -1, wTemp, WCHARBUFFERSIZE);
    WideCharToMultiByte(CP_UTF8, 0, wTemp, -1, chrs, BUFFERSIZE, nullptr,
                        nullptr);
    memset(reinterpret_cast<void *>(temp), 0, BUFFERSIZE);
    memset(reinterpret_cast<void *>(wTemp), 0, BUFFERSIZE);
}
#endif

二、完整代码

这是基本要求, 不难实现:

设计和实现一个“学生成绩管理系统”,满足以下要求:

  1. 系统以菜单方式工作;
  2. 使用链表或结构数组对学生成绩进行管理和维护;
  3. 使用二进制文件在磁盘上保存学生记录信息;
  4. 链表中各结点或结构数组中各元素包括“学号、姓名、成绩数组(含计算机、数学、
    物理、外语四门课程)、总分”基本字段;
  5. 实现如下基本功能:
    (1) 查看所有学生信息
    (2) 计算总分
    (3) 排序
    按学号排序
    按总分排序
    (4) 添加学生(此功能下可实现学生信息的录入)
    (5) 插入学生(在已按学号有序的提下,将学生插入到合适的位置)
    (6) 查找学生(查找并显示学生的学号、姓名、四门课程成绩、总分)
    按学号查找
    按姓名查找
    查询每门课成绩都在80分以上的学生信息
    (7) 删除学生 ( 删除指定学号的学生 )
    (8) 将学生记录保存到文件存盘 ( 将数据以文件的形式存盘 )
    (9) 从文件中读入学生记录 ( 将已经存盘的文件数据读入内存 )

为了满足二进制存储和读取, 学生类不能用string或vector这种非平凡类对象成员, 这样只能用char数组表示名字的字符串, 但为了进行方便比较, 还是实现一个函数将字符串转换为string.

为了便于添加和排序以及查找, 我用list链表存储学生数据. 由于各成员函数使用了大量算法库中的函数, 所以也大量引入了简单的lambda闭包, 如果你对lambda不熟悉, 请务必尽快熟悉, 否则C++高级性质就基本与你无缘. 另外, 某些算法函数会再套用算法函数, 不必害怕, 都非常简单, 相比于自己实现, 这种函数套用更为方便, 且语义更为准确.

菜单实现选择太多, 就不要用if语句了, swich语句是最为合适的.

#include 
#include 
#include 
#include 
#include 
#include 

#define BUFFERSIZE 32
#define WCHARBUFFERSIZE 16
#define CLEARCHARSIZE 1024

#ifdef __WIN64
#include 
void gbkToUtf8(std::istream &istm, char *chrs)
{
    static char temp[BUFFERSIZE] = {};
    static wchar_t wTemp[WCHARBUFFERSIZE] = {};
    istm.clear();
    istm.ignore(CLEARCHARSIZE, '\n');
    if (!istm.getline(temp, BUFFERSIZE))
    {
        istm.clear();
        istm.ignore(CLEARCHARSIZE, '\n');
    }
    MultiByteToWideChar(CP_ACP, 0, temp, -1, wTemp, WCHARBUFFERSIZE);
    WideCharToMultiByte(CP_UTF8, 0, wTemp, -1, chrs, BUFFERSIZE, nullptr,
                        nullptr);
    memset(reinterpret_cast<void *>(temp), 0, BUFFERSIZE);
    memset(reinterpret_cast<void *>(wTemp), 0, BUFFERSIZE);
}
#endif

struct student
{
    student() = default;

    explicit student(std::istream &istm)
    {
        std::cout << "输入学号: " << std::endl;
        istm >> xueHao;
        std::cout << "输入姓名" << std::endl;

#ifdef __WIN64
        gbkToUtf8(istm, xingMing);
#endif

#ifndef __WIN64
        istm >> xingMing;
#endif
        std::cout << "输入计算机成绩" << std::endl;
        istm >> jiSuanJiChengJi;
        std::cout << "输入数学成绩" << std::endl;
        istm >> shuXueChengJi;
        std::cout << "输入物理成绩" << std::endl;
        istm >> wuLiChengJi;
        std::cout << "输入外语成绩" << std::endl;
        istm >> waiYuChengJi;
        zongFen = jiSuanJiChengJi + shuXueChengJi + wuLiChengJi + waiYuChengJi;
    }

    [[nodiscard]] auto showZongFen() const -> int
    {
        return zongFen;
    }

    [[nodiscard]] auto showXueHao() const -> int
    {
        return xueHao;
    }

    [[nodiscard]] auto showXingMing() const -> std::string
    {
        return xingMing;
    }

    void print() const
    {
        std::cout << xueHao << '\t' << xingMing << '\t' << jiSuanJiChengJi
                  << '\t' << shuXueChengJi << '\t' << wuLiChengJi << '\t'
                  << waiYuChengJi << '\t' << zongFen << '\n';
    }

    void printZongFen() const
    {
        std::cout << xueHao << '\t' << xingMing << '\t' << zongFen << '\n';
    }

    void printTo(std::ostream &ostm) const
    {
        ostm.write(reinterpret_cast<const char *>(this), sizeof(student));
    }

    [[nodiscard]] auto everyScoreUp(int num) const -> bool
    {
        return jiSuanJiChengJi > num && shuXueChengJi > num &&
               wuLiChengJi > num && waiYuChengJi > num;
    }

  private:
    int xueHao = 0;
    char xingMing[BUFFERSIZE] = {};
    int jiSuanJiChengJi = 0;
    int shuXueChengJi = 0;
    int wuLiChengJi = 0;
    int waiYuChengJi = 0;
    int zongFen = 0;
};

struct studentVector
{
    studentVector() = default;

    void viewStudent()
    {
        std::cout << "学号\t姓名\t计算机\t数学\t物理\t外语\t总分\n";
        std::for_each(studentList.begin(), studentList.end(),
                      [](const student &stu) { stu.print(); });
    }

    void viewZongFen()
    {
        std::cout << "学号\t姓名\t总分\n";
        std::for_each(studentList.begin(), studentList.end(),
                      [](const student &stu) { stu.printZongFen(); });
    }

    void sortWithXueHao()
    {
        studentList.sort([](const student &stuA, const student &stuB) {
            return stuA.showXueHao() < stuB.showXueHao();
        });

        sortXueHao = true;
    }

    void sortWithZongFen()
    {
        studentList.sort([](const student &stuA, const student &stuB) {
            return stuA.showZongFen() > stuB.showZongFen();
        });

        sortXueHao = false;
    }

    void pushStudent(const student &stu)
    {
        studentList.push_back(stu);

        sortXueHao = false;
    }

    void insertStudent(const student &stu)
    {
        if (sortXueHao)
        {
            studentList.insert(
                std::find_if(studentList.begin(), studentList.end(),
                             [&stu](const student &stuA) {
                                 return stu.showXueHao() < stuA.showXueHao();
                             }),
                stu);
        }
        else
        {
            sortWithXueHao();

            studentList.insert(
                std::find_if(studentList.begin(), studentList.end(),
                             [&stu](const student &stuA) {
                                 return stu.showXueHao() < stuA.showXueHao();
                             }),
                stu);
        }
    }

    void searchStudentWithXueHao(int xueHao_)
    {
        auto result = std::find_if(studentList.begin(), studentList.end(),
                                   [&xueHao_](const student &stu) {
                                       return xueHao_ == stu.showXueHao();
                                   });

        if (result != studentList.end())
        {
            result->print();
        }
    }

    void searchStudentWithXingMing(const std::string &xingMing_)
    {
        auto result = std::find_if(studentList.begin(), studentList.end(),
                                   [&xingMing_](const student &stu) {
                                       return xingMing_ == stu.showXingMing();
                                   });

        if (result != studentList.end())
        {
            result->print();
        }
    }

    void searchStudentWithEveryScoreUp80()
    {
        std::for_each(studentList.begin(), studentList.end(),
                      [](const student &stu) {
                          if (stu.everyScoreUp(80))
                          {
                              stu.print();
                          }
                      });
    }

    auto deleteStudent(int xueHao_) -> bool
    {
        auto result = std::find_if(studentList.begin(), studentList.end(),
                                   [&xueHao_](const student &stu) {
                                       return xueHao_ == stu.showXueHao();
                                   });

        if (result != studentList.end())
        {
            studentList.erase(result);
            return true;
        }

        return false;
    }

    void save()
    {
        std::ofstream file("studentVector.bak");

        std::for_each(studentList.begin(), studentList.end(),
                      [&file](const student &stu) { stu.printTo(file); });
    }

    void read()
    {
        std::ifstream file("studentVector.bak");

        student stu;

        while (file.read(reinterpret_cast<char *>(&stu), sizeof(student)))
        {
            studentList.push_back(stu);
        }
    }

  private:
    std::list<student> studentList;
    bool sortXueHao = false;
};

void pritnCaiDan()
{
    std::cout << "学生成绩管理系统, 请按相应数字进行操作\n";
    std::cout << "————————————————————命令概览—————————————————————\n";
    std::cout << "| 1 查看学生信息\t\t\t\t|\n";
    std::cout << "| 2 计算学生总分\t\t\t\t|\n";
    std::cout << "| 3 按照学号排序(由小到大)\t\t\t|\n";
    std::cout << "| 4 按照总成绩排序(由高到低)\t\t\t|\n";
    std::cout << "| 5 添加学生\t\t\t\t\t|\n";
    std::cout << "| 6 插入学生\t\t\t\t\t|\n";
    std::cout << "| 7 按学号查找学生信息\t\t\t\t|\n";
    std::cout << "| 8 按姓名查找学生信息\t\t\t\t|\n";
    std::cout << "| 9 查找每门高于80分学生信息\t\t\t|\n";
    std::cout << "| 10 删除指定学号的学生\t\t\t\t|\n";
    std::cout << "| 11 保存到指定文件\t\t\t\t|\n";
    std::cout << "| 12 从指定文件读取数据\t\t\t\t|\n";
    std::cout << "| 15 退出程序\t\t\t\t\t|\n";
    std::cout << "—————————————————————————————————————————————————\n";
}

auto main() -> int
{
#ifdef __WIN64
    SetConsoleOutputCP(CP_UTF8); // 终端输出的是UTF8编码
    SetConsoleCP(CP_ACP);        // 终端输入的是GBK编码
#endif

    studentVector stuVec;
    pritnCaiDan();
    int caiDanXuanXiang = 0;
    while (std::cin >> caiDanXuanXiang)
    {
        switch (caiDanXuanXiang)
        {
        case 1:
            stuVec.viewStudent();
            break;
        case 2:
            stuVec.viewZongFen();
            break;
        case 3:
            stuVec.sortWithXueHao();
            break;
        case 4:
            stuVec.sortWithZongFen();
            break;
        case 5:
            stuVec.pushStudent(student(std::cin));
            break;
        case 6:
            stuVec.insertStudent(student(std::cin));
            break;
        case 7: {
            std::cout << "请输入学号: " << std::endl;
            int xueHao_;
            std::cin >> xueHao_;
            std::cout << "学号\t姓名\t计算机\t数学\t物理\t外语\t总分\n";
            stuVec.searchStudentWithXueHao(xueHao_);
            break;
        }
        case 8: {
            std::cout << "请输入学生姓名: " << std::endl;

#ifdef __WIN64
            static char temp[BUFFERSIZE] = {};
            gbkToUtf8(std::cin, temp);
            std::cout << "学号\t姓名\t计算机\t数学\t物理\t外语\t总分\n";
            stuVec.searchStudentWithXingMing(temp);
            memset(reinterpret_cast<void *>(temp), 0, BUFFERSIZE);
            break;
#endif

#ifndef __WIN64
            std::string xingMing_;
            std::cin >> xingMing_;

            std::cout << "学号\t姓名\t计算机\t数学\t物理\t外语\t总分\n";
            stuVec.searchStudentWithXingMing(xingMing_);
            break;
#endif
        }
        case 9: {
            std::cout << "学号\t姓名\t计算机\t数学\t物理\t外语\t总分\n";
            stuVec.searchStudentWithEveryScoreUp80();
            break;
        }
        case 10: {
            std::cout << "请输入学号: " << std::endl;
            int xueHao_;
            std::cin >> xueHao_;
            stuVec.deleteStudent(xueHao_);
            break;
        }
        case 11:
            stuVec.save();
            break;
        case 12:
            stuVec.read();
            break;
        case 15:
            return 0;
            break;
        }
        std::cout << "\n\n";
        pritnCaiDan();
    }

    return 0;
}


总结

学生管理系统, 可以说是最简单, 只需要基础 IO 知识就可以编写的完整有用的程序, 如果不用跨平台, 那是相当简单. 简单程序也可以逐渐扩展, 比如socket编程, 远程调用, 比如连接数据库进行增删改查, 甚至多线程调用, 比如 qt 加图形菜单称为一个界面程序.

路漫漫其修远兮, 吾将上下而求索.

你可能感兴趣的:(笔记,Linux汉字编码,Windows汉字编码,C++汉字编码,C++GBK及UTF8转换)