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
运行截图

生成文件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)观察运行结果有什么问题?给出测试截图并分析原因。

答:有缺失值那一行出现数据问题,并且此行开始的数据没有读入。原因:读入使用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
#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;
}

实验任务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)



ans.txt文件:

运行截图(data_bad.txt)



ans.txt文件:

运行截图(读错文件)

实验总结
学会了重载流运算符和fstream、iostream的组合使用。

浙公网安备 33010602011771号