03 学生成绩管理系统

03 学生成绩管理系统


作者:abnb250/山中鹤鸣
博客:https://www.cnblogs.com/abnb250
交流群:1062464078
点击返回目录



前情提要

这一节用 Score + Student + std::unordered_map 搭一个命令行成绩管理小程序,支持增删改查、按总分/均分/单科排序、班级统计、文件读写。
注释版盯住几处易踩的坑:
get_score 用 -1 表示未找到,因为 0 是合法成绩;
set_score 必须按引用遍历 Score,否则改的是副本;
op4 的链式 set_* 调用依赖每个 setter 返回 this;
op5 的 classmin 初值不能是 -1,且 classsum / (1.0)
numss 实际是乘法而非除法。



代码(加注释版)

注释解释了两种场景:

  1. 代码有反直觉痕迹,自然写法就在旁边。
  2. 代码看起来自然,但背后有隐形决策。

Score.h

#ifndef SCORE_H
#define SCORE_H

#include <iostream>
#include <fstream>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
using std::string, std::vector;

class Score{
private:
    string kind;
    double grade;
public:
    Score(): kind("unknown"), grade(0.0){}
    Score(string k, double g) : kind(k), grade(g) {}
    string get_kind() const;
    double get_grade() const;
    void set_score(double newgra);
};

#endif

Score.cpp

#include "Score.h"

string Score::get_kind() const{
    return kind;
}

double Score::get_grade() const{
    return grade;
}

void Score::set_score(double newgra){
    grade = newgra;
    return;
}

Student.h

#ifndef STUDENT_H
#define STUDENT_H

#include <iostream>
#include <fstream>
#include <cmath>
#include <algorithm>
#include <string>
#include <vector>
#include "Score.h"
using std::string;
using std::vector;

class Student{
private:
    string num;
    string name;
    char sex;
    int age;
    string cla;
    vector<Score> v;
    double sum;
    double ave;
public:
    Student() : num("unknown"), name("unknown"), sex('b'), age(18), cla("7012") {}
    Student(string nu, string na, char se, int ag, string cl) : num(nu), name(na), sex(se), age(ag), cla(cl) {}
    void show_score();
    double get_score(string kindname) const;
    string get_name();
    void showall();
    Student& set_num(string newnum);
    Student& set_name(string newname);
    Student& set_sex(char newsex);
    Student& set_age(int newage);
    Student& set_cla(string newcla);
    bool set_score(string newkind, double newgrade);
    Student& add_sco(Score sc);
    double get_sum() const;
    double get_ave() const;
    void fileall(std::fstream& file);
    
};

bool cmpsum(const Student& a, const Student& b);

bool cmpave(const Student& a, const Student& b);


#endif

Student.cpp

#include "Student.h"

void Student::show_score(){
    using namespace std;
    if(v.size() == 0){
        cout << "no score" << '\n';
        return;
    }
    for(Score x : v){
        cout << x.get_kind() << ": " << x.get_grade() << '\n';
    }
    cout << "===========" << '\n';
    return;
}

double Student::get_score(string kindname) const{
    for(Score x : v){
        if(x.get_kind() == kindname){
            return x.get_grade();
        }
    }
    // 不能改成返回 0 表示未找到,0 是合法成绩,调用方无法区分“没这门课”和“考了 0 分”
    return -1;
}

string Student::get_name(){
    return name;
}

void Student::showall(){
    using namespace std;
    // string num;
    // string name;
    // char sex;
    // int age;
    // string cla;
    // vector<Score> v;
    // double sum;
    // double ave;
    cout << "========================" << '\n';
    cout << "number: " << num << '\n';
    cout << "name: " << name << '\n';
    cout << "sex: " << sex << '\n';
    cout << "age: " << age << '\n';
    cout << "class:" << cla << '\n';
    show_score();
    cout << "summary: " << get_sum() << '\n';
    cout << "average" << get_ave() << '\n';
    cout << "========================" << '\n';
}

Student& Student::set_num(string newnum) {
    num = newnum;
    return *this;
}

Student& Student::set_name(string newname){
    name = newname;
    return *this;
}

Student& Student::set_sex(char newsex){
    sex = newsex;
    return *this;
}

Student& Student::set_age(int newage){
    age = newage;
    return *this;
}

Student& Student::set_cla(string newcla){
    cla = newcla;
    return *this;
}

bool Student::set_score(string newkind, double newgrade){
    using namespace std;
    if(v.size() == 0){
        cout << "nofind" << '\n';
        return false;
    }
    // 不能按值遍历 Score x,修改的是副本,原 vector 里的成绩不变
    for(Score &x : v){
        if(x.get_kind() == newkind){
            x.set_score(newgrade);
            return true;
        }
    }
    cout << "nofind" << '\n';
    return false;
}

Student& Student::add_sco(Score sc) {
    v.push_back(sc);
    return *this;
}

double Student::get_sum() const{
    double sum = 0;
    for(Score x : v){
        sum += x.get_grade();
    }
    return sum;
}

double Student::get_ave() const{
    double sum = get_sum();
    double nums = static_cast<double>(v.size());
    if(nums == 0){
        std::cout << "/ 0" << '\n';
        return 0.0;
    }
    return sum/nums;
}

bool cmpsum(const Student& a, const Student& b){
    return a.get_sum() > b.get_sum();
}

bool cmpave(const Student& a, const Student& b){
    return a.get_ave() > b.get_ave();
}

void Student::fileall(std::fstream& file){
    using namespace std;
    file << "========================" << '\n';
    file << "number: " << num << '\n';
    file << "name: " << name << '\n';
    file << "sex: " << sex << '\n';
    file << "age: " << age << '\n';
    file << "class:" << cla << '\n';
    if(v.size() == 0){
        file << "no score" << '\n';
    }
    else{
        for(Score x : v){
            file << x.get_kind() << ": " << x.get_grade() << '\n';
        }
    }
    file << "summary: " << get_sum() << '\n';
    file << "average" << get_ave() << '\n';
    file << "========================" << '\n';
}

system.cpp

#include "Student.h"
#include "Score.h"
#include <map>
#include <unordered_map>
#include <vector>
#include <fstream>

std::unordered_map<string, Student> maps;

int Showmain(){
    using namespace std;
    cout << "========================" << '\n';
    cout << "[1]show students" << '\n';
    cout << "[2]add a student" << '\n';
    cout << "[3]delete a student" << '\n';
    cout << "[4]set a student" << '\n';
    cout << "[5]show the class" << '\n';
    cout << "[6]read from file" << '\n';
    cout << "[7]save into a file" << '\n';
    cout << "[0]exit" << '\n';
    cout << "========================" << '\n';
    int choice;
    // 如果 cin>>choice 失败(如输入非数字),choice 未初始化,返回后 switch 使用未初始化值 UB;需验证输入校验
    cin >> choice;
    return choice;
}

void op1(){
    using namespace std;
    while(1){
        cout << "========================" << '\n';
        cout << "[a]just a student" << '\n';
        cout << "[b]show all with summary" << '\n';
        cout << "[c]show all with average" << '\n';
        cout << "[d]show all with subject" << '\n';
        cout << "[q]exit" << '\n';
        char choice;
        cin >> choice;
        if(choice == 'q') break;
        if(choice == 'a'){
            cout << "cin number and name" << '\n';
            string nums, nams;
            cin >> nums >> nams;    
            auto it = maps.find(nums);
            if(it == maps.end()){
                cout << "no find" << '\n';
                continue;
            }
            if(it->second.get_name() != nams){
                cout << "name is error" << '\n';
                continue;
            }
            it->second.showall();
        }
        if(choice == 'b'){
            vector<Student> vs;
            for(auto it : maps){
                vs.push_back(it.second);
            }
            sort(vs.begin(), vs.end(), cmpsum);
            for(auto x : vs){
                x.showall();
            }
        }
        if(choice == 'c'){
            vector<Student> vs;
            for(auto it : maps){
                vs.push_back(it.second);
            }
            sort(vs.begin(), vs.end(), cmpave);
            for(auto x : vs){
                x.showall();
            }
        }
        if(choice == 'd'){
            cout << "cin the subject" << '\n';
            string sub;
            cin >> sub;
            vector<Student> vs;
            for(auto it : maps){
                vs.push_back(it.second);
            }
            sort(vs.begin(), vs.end(),[&sub](const Student& a, const Student& b){
                return a.get_score(sub) > b.get_score(sub);
            });
            for(auto x : vs){
                x.showall();
            }
        }
    }
    
}

void op2(){
    using namespace std;
    cout << "cin the basic message: " << '\n';
    // string num;
    // string name;
    // char sex;
    // int age;
    // string cla;
    cout << "number(string), name(string), sex(char), age(int), class(string)." << '\n';
    string nums, nams, clas;
    char sexs;
    int ages;
    cin >> nums >> nams >> sexs >> ages >> clas;
    cout << "everything is ok" << '\n';
    Student tmp(nums, nams, sexs, ages, clas);
    maps[nums] = tmp;
    return;
}

void op3(){
    using namespace std;
    cout << "cin number" << '\n';
    string tmp;
    cin >> tmp;
    maps.erase(tmp);
}

void op4(){
    using namespace std;
    while(1){
        cout << "========================" << '\n';
        cout << "[a]basic message" << '\n';
        cout << "[b]subject score" << '\n';
        cout << "[q]exit" << '\n';
        cout << "========================" << '\n';
        char ch;
        cin >> ch;
        if(ch == 'q') break;
        if(ch == 'a'){
            cout << "cin the number and the basic message" << '\n';
            cout << "number(string), name(string), sex(char), age(int), class(string)." << '\n';
            string nums, nams, clas;
            char sexs;
            int ages;
            cin >> nums >> nams >> sexs >> ages >> clas;
            cout << "everything is ok" << '\n';
            // Student& set_num(string newnum);
            // Student& set_name(string newname);
            // Student& set_sex(char newsex);
            // Student& set_age(int newage);
            // Student& set_cla(string newcla);
            auto it = maps.find(nums);
            if(it == maps.end()){
                cout << "no find" << '\n';
                continue;
            }
            // 这里依赖 set_name/set_sex/set_age/set_cla 返回 *this,改成 void 会编译失败
            it->second.set_name(nams).set_sex(sexs).set_age(ages).set_cla(clas);
        }
        if(ch == 'b'){
            cout << "cin the number" << '\n';
            string tname;
            cin >> tname;
            auto it = maps.find(tname);
            if(it == maps.end()){
                cout << "no find" << '\n';
                continue;
            }
            cout << "cin the subject name and score" << '\n';
            string sname;
            double sc;
            cin >> sname >> sc;
            bool ok = it->second.set_score(sname, sc);
            if(!ok){
                Score ns(sname, sc);
                it->second.add_sco(ns);
            }
        }
    }
    
}

void op5(){
    using namespace std;
    cout << "========================" << '\n';
    //支持统计班级总分、平均分、最高分、最低分
    double classsum = 0.0, classave, classmax = -1.0, classmin = -1.0;
    int numss = 0;
    for(auto it : maps){
        classsum += it.second.get_sum();
        classmax = max(classmax, it.second.get_sum());
        // 不能把 classmin 初始化为 -1,总分非负,min(-1, sum) 恒为 -1,最小值统计错误
        classmin = min(classmin, it.second.get_sum());
        numss++;
    }
    // 不能写成 classsum / (1.0)*numss,乘除同优先级左结合,实际是 classsum*numss,平均分错误
    classave = classsum / (1.0)*numss;
    cout << "class summary: " << classsum << '\n';
    cout << "class average: " << classave << '\n';
    cout << "class max: " << classmax << '\n';
    cout << "class min: " << classmin << '\n';
    cout << "========================" << '\n';
}

void op6(){
    using namespace std;
    cout << "cin the file name" << '\n';
    string fname;
    cin >> fname;
    fstream file(fname, ios::in | ios:: out);
    if(!file.is_open()){
        cout << "file open filed" << '\n';
        return;
    }
    string tmp;
    while(getline(file, tmp)){
        cout << tmp << '\n';
    }
    file.close();
}

void op7(){
    using namespace std;
    cout << "cin the file name" << '\n';
    string fname;
    cin >> fname;
    // 用 ios::in|ios::out 打开,文件不存在时不会创建;若改成 ios::out 会截断旧内容,需验证保存语义
    fstream file(fname, ios::in|ios::out);
    if(!file.is_open()){
        cout << "file open is error" << '\n';
        return;
    }
    for(auto it : maps){
        it.second.fileall(file);
    }
    file.close();
}

void op0(){
    using namespace std;
    cout << "========================" << '\n';
    cout << "welcome to use next time" << '\n';
    cout << "wish you have a goot day" << '\n';
    cout << "========================" << '\n';
}

int main(){
    using namespace std;
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    while(1){
        int choice = Showmain();
        switch(choice){
            case 1: op1(); break;
            case 2: op2(); break;
            case 3: op3(); break;
            case 4: op4(); break;
            case 5: op5(); break;
            case 6: op6(); break;
            case 7: op7(); break;
            case 0: op0(); break;
        }
        if(choice == 0) break;
    }
    
    return 0;
}








posted @ 2026-11-01 17:35  abnb250  阅读(2)  评论(0)    收藏  举报