OOP-实验6

实验任务1

源代码

contestent.hpp

点击查看代码
#pragma once
#include <iomanip>
#include <iostream>
#include <string>

struct Contestant {
    long   id;              // 学号
    std::string name;       // 姓名
    std::string major;      // 专业
    int    solved;          // 解题数
    int    penalty;         // 总罚时
};

// 重载<<
// 要求:姓名/专业里不含空白符
inline std::ostream& operator<<(std::ostream& out, const Contestant& c) {
    out << std::left;
    out << std::setw(15) << c.id
        << std::setw(15) << c.name
        << std::setw(15) << c.major
        << std::setw(10) << c.solved
        << std::setw(10) << c.penalty;

    return out;
}

// 重载>>
inline std::istream& operator>>(std::istream& in, Contestant& c) {
    in >> c.id >> c.name >> c.major >> c.solved >> c.penalty;

    return in;
}

utils.hpp

点击查看代码
#pragma once
#include <fstream>
#include <iostream>
#include <stdexcept>
#include <string>
#include <vector>
#include "contestant.hpp"

// ACM 排序规则:先按解题数降序,再按罚时升序
inline bool cmp_by_solve(const Contestant& a, const Contestant& b) {
    if(a.solved != b.solved)
        return a.solved > b.solved;
    
    return a.penalty < b.penalty;
}

// 将结果写至任意输出流
inline void write(std::ostream& os, const std::vector<Contestant>& v) {
    for (const auto& x : v) 
        os << x << '\n';
}

// 将结果打印到屏幕
inline void print(const std::vector<Contestant>& v) {
    write(std::cout, v);
}

// 将结果保存到文件
inline void save(const std::string& filename, const std::vector<Contestant>& v) {
    std::ofstream os(filename);
    if (!os) 
        throw std::runtime_error("fail to open " + filename);

    write(os, v);
}

// 从文件读取信息(跳过标题行)
inline std::vector<Contestant> load(const std::string& filename) {
    std::ifstream is(filename);
    if (!is) 
        throw std::runtime_error("fail to open " + filename);

    std::string line;
    std::getline(is, line);          // 跳过标题

    std::vector<Contestant> v;
    Contestant t;
    int seq;
    while (is >> seq >> t) 
        v.push_back(t);
        
    return v;
}

task1.cpp

点击查看代码
#include <algorithm>
#include <iostream>
#include <stdexcept>
#include <vector>
#include "contestant.hpp"
#include "utils.hpp"

const std::string in_file = "./data.txt";
const std::string out_file = "./ans.txt";

void app() {
    std::vector<Contestant> contestants;

    try {
        contestants = load(in_file);                                      
        std::sort(contestants.begin(), contestants.end(), cmp_by_solve); 
        print(contestants);      
        save(out_file, contestants);                         
    } catch (const std::exception& e) {
        std::cerr << e.what() << '\n';
        return;
    }
}

int main() {
    app();
}

data.txt

点击查看代码
序号	学号		姓名		专业		解题数(道)	总罚时(分钟)
1	204942005	Jeny		未来专业1		4		210	    
2	204942302	Alex	   	未来专业2		3		180
3	204942059	Bob		未来专业2		5		350			     
4	204942111	Hellen		未来专业3		2		90	 
5	204942017	chappie		未来专业4		4		260	 
6	204942075	Shaw		未来专业5		3		150		
7	204942076	Thomas		未来专业6		6		420	  
8	204942078	Jennie		未来专业7		1		30	  	
9	204942079	Tibby		未来专业7		3		200		
10	204942080	Vermont		未来专业8		5		310	

data_bad.txt

点击查看代码
序号	学号		姓名		专业		解题数(道)	总罚时(分钟)
1	204942005	Jeny		未来专业1		4		210	    
2	204942302	Alex	   	未来专业2		3		180

3	204942059	Bob		未来专业2		5		350			     
4	204942111	Hellen		未来专业3		2		90	 
5	204942017	chappie		未来专业4		4		260	 
6	204942075	Shaw		未来专业5		3		150		
7	204942076	Thomas		未来专业6			  
8	204942078	Jennie		未来专业7		1		30	  	
9	204942079	Tibby		未来专业7		3		200		
10	204942080	Vermont		未来专业8		5		310	

运行截图

image
生成文件ans.txt

点击查看代码
204942076      Thomas         未来专业6      6         420       
204942080      Vermont        未来专业8      5         310       
204942059      Bob            未来专业2      5         350       
204942005      Jeny           未来专业1      4         210       
204942017      chappie        未来专业4      4         260       
204942075      Shaw           未来专业5      3         150       
204942302      Alex           未来专业2      3         180       
204942079      Tibby          未来专业7      3         200       
204942111      Hellen         未来专业3      2         90        
204942078      Jennie         未来专业7      1         30        

实验结论

问题1:流操作与代码复用
观察 print() 与 save() 的实现,均在内部调用write():
(1)write() 的参数类型是std::ostream& ,它为什么能同时接受std::cout 和std::ofstream 对象作为实参?
答:因为 std::cout 的类型是 std::ostream,而 std::ofstream 继承自 std::ostream。

(2)如果要把结果写到其他设备,只要该设备也提供 std::ostream 接口,还需改动write() 吗?
答:不需要。只要目标“设备”最终能以 std::ostream& 的形式提供输出能力,write() 代码无需改动,直接复用。

问题2:异常处理与捕获
在代码中找到两处 throw 语句,说明:

    if (!os) 
        throw std::runtime_error("fail to open " + filename);
if (!is) 
        throw std::runtime_error("fail to open " + filename);

(1)什么情况下会抛出异常;
答:第一个throw语句当输出文件无法成功打开时,os 处于失败状态,触发抛异常。第二个throw语句当输入文件无法打开时即is处于失败状态,触发抛异常。

(2)异常被谁捕获、做了哪些处理。
答:均在在 task1.cpp 的 app() 中被捕获,处理方式为处理方式:打印错误信息到标准错误输出,并 return 结束 app()不再继续后续流程。

    try {
        contestants = load(in_file);                                      
        std::sort(contestants.begin(), contestants.end(), cmp_by_solve); 
        print(contestants);      
        save(out_file, contestants);                         
    } catch (const std::exception& e) {
        std::cerr << e.what() << '\n';
        return;
    }

问题3:替代写法
函数app中std::sort(contestants.begin(), contestants.end(), cmp_by_solve); 参数cmp_by_solve 换成下面的lambda表达式是否可以?功能、性能、结果是否一致?

[](const Contestant& a, const Contestant& b) {
        return a.solved != b.solved ? a.solved > b.solved
                                    : a.penalty < b.penalty;}

答:可以,功能、结果完全一致,因为std::sort定义为inline函数,所以性能基本一致。

问题4:数据完整性与代码健壮性
把 in_file 改成 "./data_bad.txt" (内含空白行或字段缺失),重新编译运行:
(1)观察运行结果有什么问题?给出测试截图并分析原因。
image
答:有缺失值那一行出现数据问题,并且此行开始的数据没有读入。原因:读入使用in >> c.id >> c.name >> c.major >> c.solved >> c.penalty;,当遇到空白行时会自动跳过,但当遇到有缺失值行时,in >> c.solved 或 in >> c.penalty 会失败,流进入 failbit 状态,导致 is >> seq >> t 整体失败,while 循环终止。

(2)思考:如何修改函数std::vector load(const std::string& filename) ,使其提示出错行号并跳过有问题的数据行,同时让程序继续执行?(选答*)

#include <sstream>

inline std::vector<Contestant> load(const std::string& filename) {
    std::ifstream is(filename);
    if (!is)
        throw std::runtime_error("fail to open " + filename);

    std::string line;
    std::getline(is, line);  
    int line_no = 1;
    std::vector<Contestant> v;

    while (std::getline(is, line)) {
        ++line_no;

        std::istringstream ss(line);

        int seq;
        Contestant t;

        if (!(ss >> seq >> t.id >> t.name >> t.major >> t.solved >> t.penalty)) {
            std::cerr << "[load warning] bad line " << line_no
                      << ": " << line << '\n';
            continue; 
        }

        v.push_back(t);
    }

    return v;
}

image

实验任务2

源代码

student.hpp

点击查看代码
#pragma once

#include <iostream>
#include <string>

class Student {
public:
    Student() = default;
    ~Student() = default;
    
    const std::string get_major() const;
    int get_grade() const;

    friend std::ostream& operator<<(std::ostream& os, const Student& s);
    friend std::istream& operator>>(std::istream& is, Student& s);

private:
    int id;   
    std::string  name;
    std::string  major;
    int          grade;  // 0-100
};

student.cpp

点击查看代码
#include "student.hpp"

const std::string Student::get_major() const {
    return major;
}

int Student::get_grade() const {
    return grade;
}

std::ostream& operator<<(std::ostream& os, const Student& s) {
    os << s.id << ' ' << s.name << ' ' << s.major << ' ' << s.grade;
    return os;
}

std::istream& operator>>(std::istream& is, Student& s) {
    is >> s.id >> s.name >> s.major >> s.grade;
    return is;
}

stumgr.hpp

点击查看代码
#pragma once
#include <string>
#include <vector>
#include "student.hpp"

class StuMgr {
public:
    void load(const std::string& file);  // 加载数据文件(空格分隔)
    void sort();                         // 排序: 按专业字典序升序、同专业分数降序
    void print() const;                  // 打印到屏幕
    void save(const std::string& file) const; // 保存到文件

private:
    void write(std::ostream &os) const;  // 把数据写到任意输出流

private:
    std::vector<Student> students;
};

stumgr.cpp

点击查看代码
#include "stumgr.hpp"
#include <fstream>
#include <algorithm>
#include <sstream>
void StuMgr::write(std::ostream &os) const  // 把数据写到任意输出流
{
    for (const auto& student : students) {
        os << student << '\n';
    }
}

void StuMgr::load(const std::string& file)  // 加载数据文件(空格分隔)
{

    std::ifstream is(file);
    if (!is.is_open()) {
        throw std::runtime_error("无法打开文件: " + file);
    }

    students.clear();
    std::string line;
    std::getline(is, line); 

    Student t;
    int line_no = 1;

    while (std::getline(is, line)) {
        ++line_no;
        std::istringstream ss(line);
        if(!(ss >> t)) 
        {
            std::cerr << "[load warning] bad line " << line_no
                      << ": " << line << '\n';
            continue; 
        }else if(t.get_grade() < 0 || t.get_grade() > 100) 
        {
            std::cerr << "[load warning] invalid grade at line " << line_no
                      << ": " << line << '\n';
            continue; 
        }
        students.push_back(t);
    }

}

void StuMgr::sort()                         // 排序: 按专业字典序升序、同专业分数降序
{
    if (students.empty()) {
        throw std::runtime_error("没有加载任何学生数据,无法排序");
        return;
    }
    std::sort(students.begin(), students.end(),
              [](const Student& a, const Student& b) {
                  if (a.get_major() != b.get_major()) {
                      return a.get_major() < b.get_major();
                  }
                  return a.get_grade() > b.get_grade();
              });
}

void StuMgr::print() const                  // 打印到屏幕
{
    if (students.empty()) {
        throw std::runtime_error("没有加载任何学生数据,无法打印");
        return;
    }
    write(std::cout);
}

void StuMgr::save(const std::string& file) const // 保存到文件
{
    if(students.empty()) {
        throw std::runtime_error("没有加载任何学生数据,无法保存");
        return;
    }
    
    std::ofstream out(file);
    if(!out)
    {
        throw std::runtime_error("无法打开文件: " + file);
    }
    write(out);
}


data.txt

点击查看代码
学号	姓名	专业	成绩
1001	抖森	Acting	80
1002	宝爷	Music	97
1003	大眼仔	Acting	75
1004	本喵	Acting	99
1005	裘花	Acting	89
1006	小李子	Acting	92
1007	无脸男	Acting	85
1008	甜茶	Acting	91
1009	囧瑟夫	Acting	88
1010	霉霉	Music	96

data_bad.txt

点击查看代码
学号	姓名	专业	成绩
1001	抖森	Acting	80
1002	宝爷	Music	97
1003	大眼仔	Acting	
1004	本喵	Acting	99
1005	裘花	Acting	89
1006	小李子	Acting	92
1007	无脸男	Acting	105
1008	甜茶	Acting	91
1009	囧瑟夫	Acting	88
1010	霉霉	Music	96

task2.cpp

点击查看代码
#include <iostream>
#include <limits>
#include <string>
#include "stumgr.hpp"

const std::string in_file = "./data_bad.txt";
const std::string out_file = "./ans.txt";

void menu() {
    std::cout << "\n**********简易应用**********\n"
                 "1. 加载文件\n"
                 "2. 排序\n"
                 "3. 打印到屏幕\n"
                 "4. 保存到文件\n"
                 "5. 退出\n"
                 "请选择:";
}

void app() {
    StuMgr mgr;

    while(true) {
        menu();
        int choice;
        std::cin >> choice;

        try {
            switch (choice) {
            case 1: mgr.load(in_file); 
                    std::cout << "加载成功\n"; break;
            case 2: mgr.sort(); 
                    std::cout << "排序已完成\n"; break;
            case 3: mgr.print(); 
                    std::cout << "打印已完成\n"; break;
            case 4: mgr.save(out_file); 
                    std::cout << "导出成功\n"; break;
            case 5: return;
            default: std::cout << "不合法输入\n";
            }
        }
        catch (const std::exception& e) {
            std::cout << "Error: " << e.what() << '\n';
        }
    }
}

int main() {
    app();
}

运行截图(data.txt)

image
image
image

ans.txt文件:
image

运行截图(data_bad.txt)

image
image
image

ans.txt文件:

image

运行截图(读错文件)

image

实验总结

学会了重载流运算符和fstream、iostream的组合使用

posted @ 2025-12-17 09:22  MEIYBAO  阅读(8)  评论(0)    收藏  举报