
本文共 29932 字,大约阅读时间需要 99 分钟。
程序设计
课程设计实验报告
学院: 计算机科学与网络工程学院
专业班级: XXX
姓名: XXX
学号: 190XXXXXXX
2020.6.29
一、课程设计题目及内容
题目:学生成绩管理系统
设计要求及提示如下:
(1)、设计一个学生类Student,包括数据成员:姓名、学号、二门课程(面向对象程序设计、高等数学)的成绩。
(2)、创建一个管理学生的类Management,包括实现学生的数据的增加、删除、修改、按课程成绩排序、保存学生数据到文件及加载文件中的数据等功能。
(3)、创建一个基于对话框的MFC应用程序,程序窗口的标题上有你姓名、学号和应用程序名称。使用(1)和(2)中的类,实现对学生信息和成绩的输入和管理。
(4)、创建一个单文档的MFC应用程序,读取(3)中保存的文件中的学生成绩,分别用直方图和折线方式显示所有学生某课程的成绩分布图。
二、每个功能模块的设计分析及算法描述
各个功能模块的核心功能在Management类完成:
- 在Management类里声明了一个动态数组all,默认初始化容量为10个,在添加数据时,首先进行数据合法性检查,如果检查不通过直接添加失败,通过则进行添加。如果数组容量满了,将进行自动扩容,扩容的方法是:再开辟一个更大容量的动态数组temp,然后逐一将all中的数据拷贝到temp,拷贝完成之后释放掉all的内存空间,再让all指向temp。如果数组容量再次满了,重复上述操作。
- 删除数据时,需要传入学号,是先逐一遍历all数组中的元素,当发现某个Student与传入的学号相同时,就将这这后面的所有Student向前挪一位,覆盖掉要被删除的Student。
- 修改数据,分为修改姓名、学号、程序课成绩、高数课成绩四个函数,这四个函数都是需要传入学号和修改后的数据,方法和删除数据一样,先根据id找到要修改的Student,然后修改。
- 排序功能:采用冒泡排序。
- 加载文件中的数据:在构造函数里初始化数组all,使用ifstream读取和程序相同目录下的“data.txt”文件,逐个读取后封装成Student对象,添加到数组all。
- 保存数据到文件:在析构函数里进行,先用ofstream打开“data.txt”文件,然后遍历all数组,分别文件输出姓名、学号、程序课成绩、高数课成绩,每个信息之间用空格分隔,每保存完一个Student后回车。
data.txt文件如图:
画统计图在View类完成:
- 排序之前,从Management类里获取要统计的信息(高数课成绩或者程序课成绩),vector类型,然后再使用这些数据来统计。
- 绘制条形图,先创建一个矩形,把vector中的数据分为5个区间,根据矩形的宽度、高度和区间的最高人数计算每个人应该占多少高度、每个区间占多少宽度,然后根据这些信息来绘制。
- 绘制折线图,也是先创建一个矩形,然后在这个矩形里画线(10行,n列,n为数据的数量),用矩形的宽度/数据的数量可以得到每个点相差的距离,用矩形的高度*数据的值/100得到高度,然后每个点的信息都记录在数组里,最后交给Polyline函数画线。
查找功能:首先要检查输入内容的合法性(分数区间不能为空,最大值不能大于100,最小值不能小于0,且最小值不能比最大值大),借助vector容器,遍历Management的all数组,如果满足条件,则将改Student添加到vector容器中,最后遍历完之后输入vector容器保存的结果。模糊查询的方法是用CString的Find()函数,如果返回结果不为-1,则代表这个CString里包含这个字符串。
三、程序中使用的数据及主要符号说明
Student 类: CString m_name;//姓名 CString m_id;//学号(唯一) int score1;//程序课成绩 int score2;//高数课成绩Management 类: Student* all; //存放学生数据的数组 int capacity; //数组当前容量 int size; //数组当前大小 (extern) Management m; //Management对象,在View.cpp和ADD.cpp都有出现,为了在两个cpp共用一个对象,所以在其中一个地方加了extern数据管理的对话框: ADD; //添加数据的对话框,类名为ADD CString m_name; //输入姓名的编辑框 CString m_id;//输入学号的编辑框 int score1;//输入程序课成绩的编辑框 int score2;//输入高数课成绩的编辑框 int Search_Score1_Min; //数据查找时,需要输入的程序课成绩最小值 int Search_Score1_Max; //数据查找时,需要输入的程序课成绩最大值 int Search_Score2_Min; //数据查找时,需要输入的高数课成绩最小值 int Search_Score2_Max; //数据查找时,需要输入的高数课成绩最大值 CString Search_Name; //数据查找时,需要输入的姓名 CString Search_Id; //数据查找时,需要输入的程序课成绩学号
四、带有详细注释的自己编写的源程序
//Student.h#pragma onceclass Student { public: CString m_name;//姓名 CString m_id;//学号(唯一) int score1;//程序课 int score2;//高数课 //全参构造 Student(const CString& m_name, const CString& m_id, int score1, int score2); //默认构造 Student();};Student.cpp#include "pch.h"#include "Student.h"Student::Student(const CString& m_name, const CString& m_id, int score1, int score2) : m_name(m_name), m_id(m_id), score1(score1), score2(score2) { }Student::Student() { }//Management.h#pragma once#include<fstream>#include "Student.h"#include <string>#include "ADD.h"#include <vector>using namespace std;constexpr auto INIT_SIZE = 10; //默认大小是10个;constexpr auto INCREASE_SIZE = 10; //数组满后扩充的容量;class Management { public: Student* all = NULL; //存放学生数据的数组,在构造函数中对其初始化 int capacity = 0; //数组当前容量 int size = 0; //当前大小 //-------------------------------------------------------- //基本功能 void add(Student stu); //添加数据 bool del(CString id); //根据ID删除数据 bool hasId(CString id); //判断id是否存在 void sortByScore1(bool = false); //按程序课成绩排序(false:降序,true:升序,采用了默认参数,默认降序) void sortByScore2(bool = false); //按高数课成绩排序(false:降序,true:升序,默认降序) void sortById(bool = false); //按学号排序(false:降序,true:升序,默认降序) //--------------------------------------------------------- //根据id获取名字、分数 CString getName(CString id); int getS1(CString id); //获取程序课成绩 int getS2(CString id); //获取高数课成绩 //--------------------------------------------------------- //根据id来修改信息 bool changeData(CString oldId, CString newName, CString newId, int S1, int S2) const; //--------------------------------------------------------- //构造和析构函数 Management(); ~Management(); //-------------------------------------------------------- //获取高数和程序课的vector集合(用于画统计图) vector<int> getMathScores() const; vector<int> getProgrammingScores() const;};//Management.cpp#include "pch.h"#include "Management.h"//添加数据void Management::add(Student stu) { if (size >= capacity) { //如果当前数组存满了 capacity += INCREASE_SIZE; Student* temp = new Student[capacity]; //重新创建一个容量更大的数组 for (int i = 0; i < size; i++) { temp[i] = all[i]; //把旧数组的数据迁移到新数组 } delete[] all; //把旧数组的空间释放掉 all = temp; //让all指针指向新数组 } all[size++] = stu; //插入数据}//删除数据bool Management::del(CString id) { for (int i = 0; i < size; i++) { //寻找指定id if (id == all[i].m_id) { //让后面的元素往前移,填补中间被删除后空缺的位置 for (int j = i; j < size - 1; ++j) { all[j] = all[j + 1]; } --size; return true; } } //如果找不到指定的id,删除失败 return false;}//判断是否存在指定的idbool Management::hasId(CString id) { for (int i = 0; i < size; i++) { if (id == all[i].m_id) return true; } return false;}//按照程序课成绩排序(false:降序,true:升序,采用默认参数,默认降序)void Management::sortByScore1(bool tag) { //采用冒泡排序算法 if (tag) { //升序 for (int i = 0; i < size; i++) { for (int j = 0; j < size - i - 1; ++j) { if (all[j].score1 > all[j + 1].score1) { Student temp = all[j]; all[j] = all[j + 1]; all[j + 1] = temp; } } } }else { //降序 for (int i = 0; i < size; i++) { for (int j = 0; j < size - i - 1; ++j) { if (all[j].score1 < all[j + 1].score1) { //和上面的不同是这里< ,上面 > Student temp = all[j]; all[j] = all[j + 1]; all[j + 1] = temp; } } } }}//按照高数课成绩排序(false:降序,true:升序,默认降序)void Management::sortByScore2(bool tag) { //采用冒泡排序算法(代码和上面的函数一样,只是把score1改成score2而已) if (tag) { //升序 for (int i = 0; i < size; i++) { for (int j = 0; j < size - i - 1; ++j) { if (all[j].score2 > all[j + 1].score2) { Student temp = all[j]; all[j] = all[j + 1]; all[j + 1] = temp; } } } } else { //降序 for (int i = 0; i < size; i++) { for (int j = 0; j < size - i - 1; ++j) { if (all[j].score2 < all[j + 1].score2) { //和上面的不同是这里< ,上面 > Student temp = all[j]; all[j] = all[j + 1]; all[j + 1] = temp; } } } }}//按学号排序(false:降序,true:升序,默认降序)void Management::sortById(bool tag){ //采用冒泡排序算法(代码和按程序课排序的函数一样,只是把score1改成m_id而已) if (tag) { //升序 for (int i = 0; i < size; i++) { for (int j = 0; j < size - i - 1; ++j) { if (all[j].m_id > all[j + 1].m_id) { Student temp = all[j]; all[j] = all[j + 1]; all[j + 1] = temp; } } } } else { //降序 for (int i = 0; i < size; i++) { for (int j = 0; j < size - i - 1; ++j) { if (all[j].m_id < all[j + 1].m_id) { //和上面的不同是这里< ,上面 > Student temp = all[j]; all[j] = all[j + 1]; all[j + 1] = temp; } } } }}CString Management::getName(CString id) { for (int i = 0; i < size; i++) { if (id == all[i].m_id) return all[i].m_name; } return TEXT("");}//获取程序课成绩int Management::getS1(CString id) { for (int i = 0; i < size; i++) { if (id == all[i].m_id) return all[i].score1; } return 0;}//获取高数课成绩int Management::getS2(CString id) { for (int i = 0; i < size; i++) { if (id == all[i].m_id) return all[i].score2; } return 0;}//修改信息(学号不存在返回false)bool Management::changeData(CString oldId, CString newName, CString newId, int S1, int S2) const { for (int i = 0; i < size; i++) { if (oldId == all[i].m_id) { all[i].m_name = newName; all[i].m_id = newId; all[i].score1 = S1; all[i].score2 = S2; return true; } } return false;}//构造函数Management::Management() { all = new Student[INIT_SIZE]; //初始化数组 capacity = INIT_SIZE; //更新容量 //读取文件 ifstream ifs("data.txt", ios::in); if (ifs.is_open() && ifs.peek() != EOF) { //确保文件已经打开,并且不是空 while (ifs.peek() != EOF) { //一直读到结尾为止 string name, id; int score1, score2; ifs >> name >> id >> score1 >> score2; //逐个读取数据 CString m_name(name.c_str()); CString m_id(id.c_str()); Student stu(m_name, m_id, score1, score2); //新建一个Student add(stu); //将Student添加到数组 } } ifs.close(); //释放资源}//析构Management::~Management() { //保存文件 ofstream ofs("data.txt", ios::out); if (ofs.is_open()) { for (int i = 0; i < size; i++) { Student stu = all[i]; string name(CT2A(all[i].m_name.GetBuffer())); //将CString转为string string id(CT2A(all[i].m_id.GetBuffer())); ofs << name << " " << id << " " << stu.score1 << " " << stu.score2; if (i != size - 1) ofs << endl; //最后一个数据之后不要换行,否则下次读取的时候会读取多一条空数据 } } ofs.close(); delete[] all;}vector<int> Management::getMathScores() const { vector<int> datas; for (int i = 0; i < size; i++) { datas.push_back(all[i].score2); } return datas;}vector<int> Management::getProgrammingScores() const { vector<int> datas; for (int i = 0; i < size; i++) { datas.push_back(all[i].score1); } return datas;}//ADD.h(对话框类)#pragma once// ADD 对话框class ADD : public CDialogEx{ DECLARE_DYNAMIC(ADD)public: ADD(CWnd* pParent = nullptr); // 标准构造函数 virtual ~ADD(); // 对话框数据#ifdef AFX_DESIGN_TIME enum { IDD = ADD_STUDENT };#endifprotected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 DECLARE_MESSAGE_MAP()public: CString m_name; CString m_id; int score1; int score2; afx_msg void OnBnClickedButton1(); CListBox DATA; afx_msg void OnBnClickedButton6(); afx_msg void OnBnClickedButton2(); afx_msg void OnLbnSelchangeList1(); afx_msg void OnBnClickedButton3(); afx_msg void OnBnClickedButton4(); afx_msg void OnBnClickedButton5(); afx_msg void OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized); afx_msg void OnBnClickedButton7(); afx_msg void OnBnClickedButton8(); afx_msg void OnBnClickedButton9(); afx_msg void OnBnClickedButton10(); afx_msg void OnBnClickedButton11(); afx_msg void OnBnClickedButton12(); afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); int Search_Score1_Min; int Search_Score1_Max; int Search_Score2_Min; int Search_Score2_Max; CString Search_Name; CString Search_Id; afx_msg void OnBnClickedButton13();};//ADD.cpp// ADD.cpp: 实现文件//#include "pch.h"#include "StudentManagerSystem.h"#include "afxdialogex.h"#include "ADD.h"#include "ChildView.h"#include<ctime>#include <vector>Management m; //创建Management对象// ADD 对话框IMPLEMENT_DYNAMIC(ADD, CDialogEx)ADD::ADD(CWnd* pParent /*=nullptr*/) : CDialogEx(ADD_STUDENT, pParent) , m_name(_T("")) , m_id(_T("")) , score1(60) , score2(60), Search_Score1_Min(0) , Search_Score1_Max(100) , Search_Score2_Min(0) , Search_Score2_Max(100) , Search_Name(_T("")) , Search_Id(_T("")) { }ADD::~ADD() = default;void ADD::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Text(pDX, IDC_EDIT1, m_name); DDX_Text(pDX, IDC_EDIT2, m_id); DDX_Text(pDX, IDC_EDIT3, score1); DDX_Text(pDX, IDC_EDIT4, score2); DDX_Control(pDX, IDC_LIST1, DATA); DDX_Text(pDX, IDC_EDIT7, Search_Score1_Min); DDX_Text(pDX, IDC_EDIT8, Search_Score1_Max); DDX_Text(pDX, IDC_EDIT9, Search_Score2_Min); DDX_Text(pDX, IDC_EDIT10, Search_Score2_Max); DDX_Text(pDX, IDC_EDIT5, Search_Name); DDX_Text(pDX, IDC_EDIT6, Search_Id);}BEGIN_MESSAGE_MAP(ADD, CDialogEx) ON_BN_CLICKED(IDC_BUTTON1, &ADD::OnBnClickedButton1) ON_BN_CLICKED(IDC_BUTTON6, &ADD::OnBnClickedButton6) ON_BN_CLICKED(IDC_BUTTON2, &ADD::OnBnClickedButton2) ON_LBN_SELCHANGE(IDC_LIST1, &ADD::OnLbnSelchangeList1) ON_BN_CLICKED(IDC_BUTTON3, &ADD::OnBnClickedButton3) ON_BN_CLICKED(IDC_BUTTON4, &ADD::OnBnClickedButton4) ON_BN_CLICKED(IDC_BUTTON5, &ADD::OnBnClickedButton5) ON_WM_ACTIVATE() ON_BN_CLICKED(IDC_BUTTON7, &ADD::OnBnClickedButton7) ON_BN_CLICKED(IDC_BUTTON8, &ADD::OnBnClickedButton8) ON_BN_CLICKED(IDC_BUTTON9, &ADD::OnBnClickedButton9) ON_BN_CLICKED(IDC_BUTTON10, &ADD::OnBnClickedButton10) ON_BN_CLICKED(IDC_BUTTON11, &ADD::OnBnClickedButton11) ON_BN_CLICKED(IDC_BUTTON12, &ADD::OnBnClickedButton12) ON_WM_CREATE() ON_BN_CLICKED(IDC_BUTTON13, &ADD::OnBnClickedButton13)END_MESSAGE_MAP()// ADD 消息处理程序// 添加按钮void ADD::OnBnClickedButton1() { //判断输入的内容是否为空 //我这样做的原因是:如果先UpdateData,那数字输入框如果为空,会弹窗 CString cstr1; CString cstr2; CString cstr3; CString cstr4; GetDlgItem(IDC_EDIT1)->GetWindowText(cstr1); GetDlgItem(IDC_EDIT2)->GetWindowText(cstr2); GetDlgItem(IDC_EDIT3)->GetWindowText(cstr3); GetDlgItem(IDC_EDIT4)->GetWindowText(cstr4); if (cstr1 == _T("") || cstr2 == _T("") || cstr3 == _T("") || cstr4 == _T("")) { MessageBox(TEXT("输入的内容不能为空!")); return; } //================================================================== UpdateData(TRUE); //将控件中的数据更新到变量 //================================================================== //判断输入的内容是否包含空格 int a = m_name.Find(TEXT(" ")); //m_name空格所在的索引 int b = m_id.Find(TEXT(" ")); //查找空格所在的索引 if (a != -1 || b != -1) { //如果索引不是-1,代表输入的东西一定包含空格 MessageBox(TEXT("输入的内容请不要包含空格!")); return; } //================================================================== //判断学号是否全为数字,并且长度为10 if (m_id.GetLength() == 10) { //如果长度为10,再判断是否每一位都是数字 for (int i = 0; i < 10; ++i) { if (!(m_id.GetAt(i) >= '0' && m_id.GetAt(i) <= '9')) { //只要碰到一个字符不是数字,添加失败 MessageBox(TEXT("学号必须由数字组成")); return; } } } else { //如果长度不为10,添加失败 MessageBox(TEXT("学号的长度必须为10")); return; } //================================================================== //判断成绩是否符合范围 if (score1 < 0 || score1 > 100 || score2 < 0 || score2 > 100) { MessageBox(TEXT("分数必须在0到100分之间")); return; } //================================================================== //判断要添加的学号是否存在 if (m.hasId(m_id)) { MessageBox(TEXT("该学号已重复")); return; } //================================================================== //以上条件都满足后,添加成功 Student stu(m_name, m_id, score1, score2); m.add(stu); DATA.AddString(m_id + "==" + m_name); //让ListBox添加一条数据 MessageBox(TEXT("添加成功")); DATA.SetCurSel(DATA.GetCount() - 1); //添加之后让ListBox选中刚刚添加的数据 OnLbnSelchangeList1(); //手动触发ListBox的Change事件}// 删除按钮void ADD::OnBnClickedButton2() { CString id; int i = DATA.GetCurSel(); //获取ListBox正在选中的索引 if (i != -1) { DATA.GetText(i, id); const int index = id.Find(_T("==")); //‘==’在字符串里的位置 id = id.Mid(0, index); //列表里显示的格式为 学号==姓名,读取==前面的,就是学号 if (m.del(id) /*删除数据,如果学号不存在将返回false*/) { DATA.DeleteString(i); //列表删除对应的项 MessageBox(TEXT("删除成功")); DATA.SetCurSel(i == 0 ? 0 : i - 1); //手动让ListBox选中被删除的前面一项 OnLbnSelchangeList1(); //手动触发ListBox的Change事件 } else { MessageBox(TEXT("该学号不存在!可能是由于数据未更新所致,请刷新列表!")); } } else { //-1代表没选中 MessageBox(TEXT("当前没有选中任何学生,请先在左边列表中选择!")); }}/** * ListBox的Change事件 * 功能:当列表框选中某些项的时候,右边显示这个学生的信息 */void ADD::OnLbnSelchangeList1() { // TODO: 在此添加控件通知处理程序代码 int i = DATA.GetCurSel(); //获取当前ListBox选中的索引 if (i != -1) { CString id; DATA.GetText(i, id); i = id.Find(_T("==")); id = id.Mid(0, i); //列表里显示的格式为 学号==姓名,读取==前面的,就是学号 if (m.hasId(id)) { m_name = m.getName(id); m_id = id; score1 = m.getS1(id); score2 = m.getS2(id); UpdateData(FALSE); //将变量的值同步到控件 } else { MessageBox(TEXT("该学号不存在!可能是由于数据未更新所致,请刷新列表!")); } }}//编辑按钮void ADD::OnBnClickedButton3() { //获取ListBox当前选中的内容,以获取学号信息 CString id; //学号 int i = DATA.GetCurSel(); //当前列表框选中的索引 if (i == -1) { MessageBox(TEXT("当前没有选中任何学生,请先在左边列表中选择!")); return; } DATA.GetText(i, id); const int index = id.Find(_T("==")); //‘==’在字符串里的位置 id = id.Mid(0, index); //列表里显示的格式为 学号==姓名,读取==前面的,就是学号 //================================================================== //检查空格和学号格式是否正确,这部分和添加数据里面的代码是一样的。 //================================================================== //判断输入的内容是否为空 //我这样做的原因是:如果先UpdateData,那数字输入框如果为空,会弹窗 CString cstr1; CString cstr2; CString cstr3; CString cstr4; GetDlgItem(IDC_EDIT1)->GetWindowText(cstr1); GetDlgItem(IDC_EDIT2)->GetWindowText(cstr2); GetDlgItem(IDC_EDIT3)->GetWindowText(cstr3); GetDlgItem(IDC_EDIT4)->GetWindowText(cstr4); if (cstr1 == _T("") || cstr2 == _T("") || cstr3 == _T("") || cstr4 == _T("")) { MessageBox(TEXT("输入的内容不能为空!")); return; } //================================================================== UpdateData(TRUE); //将控件中的数据更新到变量 //================================================================== //判断输入的内容是否包含空格 int a = m_name.Find(TEXT(" ")); //m_name空格所在的索引 int b = m_id.Find(TEXT(" ")); //查找空格所在的索引 if (a != -1 || b != -1) { //如果索引不是-1,代表输入的东西一定包含空格 MessageBox(TEXT("输入的内容请不要包含空格!")); return; } //================================================================== //判断学号是否全为数字,并且长度为10 if (m_id.GetLength() == 10) { //如果长度为10,再判断是否每一位都是数字 for (int i = 0; i < 10; ++i) { if (!(m_id.GetAt(i) >= '0' && m_id.GetAt(i) <= '9')) { //只要碰到一个字符不是数字,添加失败 MessageBox(TEXT("学号必须由数字组成")); return; } } } else { //如果长度不为10,添加失败 MessageBox(TEXT("学号的长度必须为10")); return; } //================================================================== //判断成绩是否符合范围 if (score1 < 0 || score1 > 100 || score2 < 0 || score2 > 100) { MessageBox(TEXT("分数必须在0到100分之间")); return; } //================================================================== //判断是否需要修改学号 //不需要:m_id和当前的学号一致,不作修改 //需要:m_id和当前的学号不一致,下一步还需判断修改后的学号是否重复 if (m_id != id && m.hasId(m_id)) { //如果m_id != id,代表希望修改学号,但是修改学号需要判断学号有没有重复 MessageBox(TEXT("该学号已重复")); return; } //=================================================================== //以上条件都满足之后, //1.原来的学号存在:修改成功 //2.原来的学号不存在:修改失败 if (m.changeData(id, m_name, m_id, score1, score2)) { //修改数据,如果学号不存在将返回false OnBnClickedButton6(); //手动触发“刷新列表”的单击事件 MessageBox(TEXT("修改成功")); //刷新列表之后,ListBox又会变为不选中状态 //自动选回刚刚选中的那个 for (int j = 0; j < DATA.GetCount(); j++) { CString cs; DATA.GetText(j, cs); if (cs.Mid(0, cs.Find(_T("=="))) == m_id) { DATA.SetCurSel(j); break; } } } else { MessageBox(TEXT("该学号不存在!可能是由于数据未更新所致,请刷新列表!")); }}//按程序课降序按钮void ADD::OnBnClickedButton4() { m.sortByScore1(); //排序 CString id; int i = DATA.GetCurSel(); //记住排序之前选中的索引 OnBnClickedButton6(); //刷新列表 //刷新列表之后,ListBox又会变为不选中状态 //需要自动选回刚刚选中的那个(如果i==-1,代表原来并没有选择) if (i != -1) { DATA.GetText(i, id); for (int j = 0; j < DATA.GetCount(); j++) { CString cs; DATA.GetText(j, cs); if (cs.Mid(0, cs.Find(_T("=="))) == m_id) { DATA.SetCurSel(j); break; } } } OnLbnSelchangeList1(); //触发ListBox的事件,让右边能显示选中的学生的个人信息}//按高数课降序按钮(这部分代码和按程序课降序的一样,唯一不同的地方是第一行的排序函数不同)void ADD::OnBnClickedButton5() { m.sortByScore2(); CString id; int i = DATA.GetCurSel(); OnBnClickedButton6(); if (i != -1) { DATA.GetText(i, id); for (int j = 0; j < DATA.GetCount(); j++) { CString cs; DATA.GetText(j, cs); if (cs.Mid(0, cs.Find(_T("=="))) == m_id) { DATA.SetCurSel(j); break; } } } OnLbnSelchangeList1();}//刷新列表按钮void ADD::OnBnClickedButton6() { DATA.ResetContent(); //清空列表 for (int i = 0; i < m.size; i++) { CString str; str = m.all[i].m_id; str += "=="; str += m.all[i].m_name; DATA.AddString(str); //读取数组,逐个插入数据 }}//窗口激活消息(显示窗口后刷新列表)void ADD::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) { CDialogEx::OnActivate(nState, pWndOther, bMinimized); // TODO: 在此处添加消息处理程序代码 srand((unsigned long)time(NULL)); //随机数种子 OnBnClickedButton6();}//按程序课升序(这部分代码和降序的一样,唯一不同的地方是第一行的排序函数给了true参数)void ADD::OnBnClickedButton7() { m.sortByScore1(true); //排序 CString id; int i = DATA.GetCurSel(); //记住排序之前选中的索引 OnBnClickedButton6(); //刷新列表 //刷新列表之后,ListBox又会变为不选中状态 //自动选回刚刚选中的那个(如果i==-1,代表原来并没有选择) if (i != -1) { DATA.GetText(i, id); for (int j = 0; j < DATA.GetCount(); j++) { CString cs; DATA.GetText(j, cs); if (cs.Mid(0, cs.Find(_T("=="))) == m_id) { DATA.SetCurSel(j); break; } } } OnLbnSelchangeList1(); //触发ListBox的事件,让右边能显示选中的学生的个人信息}//按高数课降序(这部分代码和降序的一样,唯一不同的地方是第一行的排序函数给了true参数)void ADD::OnBnClickedButton8() { m.sortByScore2(true); CString id; int i = DATA.GetCurSel(); OnBnClickedButton6(); if (i != -1) { DATA.GetText(i, id); for (int j = 0; j < DATA.GetCount(); j++) { CString cs; DATA.GetText(j, cs); if (cs.Mid(0, cs.Find(_T("=="))) == m_id) { DATA.SetCurSel(j); break; } } } OnLbnSelchangeList1();}//按学号降序(和上面的排序是一样的)void ADD::OnBnClickedButton9() { m.sortById(); //排序 CString id; int i = DATA.GetCurSel(); //记住排序之前选中的索引 OnBnClickedButton6(); //刷新列表 //刷新列表之后,ListBox又会变为不选中状态 //自动选回刚刚选中的那个(如果i==-1,代表原来并没有选择) if (i != -1) { DATA.GetText(i, id); for (int j = 0; j < DATA.GetCount(); j++) { CString cs; DATA.GetText(j, cs); if (cs.Mid(0, cs.Find(_T("=="))) == m_id) { DATA.SetCurSel(j); break; } } } OnLbnSelchangeList1(); //触发ListBox的事件,让右边能显示选中的学生的个人信息}//按学号升序void ADD::OnBnClickedButton10() { m.sortById(true); //排序 CString id; int i = DATA.GetCurSel(); //记住排序之前选中的索引 OnBnClickedButton6(); //刷新列表 //刷新列表之后,ListBox又会变为不选中状态 //自动选回刚刚选中的那个(如果i==-1,代表原来并没有选择) if (i != -1) { DATA.GetText(i, id); for (int j = 0; j < DATA.GetCount(); j++) { CString cs; DATA.GetText(j, cs); if (cs.Mid(0, cs.Find(_T("=="))) == m_id) { DATA.SetCurSel(j); break; } } } OnLbnSelchangeList1(); //触发ListBox的事件,让右边能显示选中的学生的个人信息}//面向对象分数随机void ADD::OnBnClickedButton11() { UpdateData(TRUE); score1 = rand() % 101; //0-100之间的随机数 UpdateData(FALSE); //数据更新到控件}//高数分数随机void ADD::OnBnClickedButton12() { UpdateData(TRUE); score2 = rand() % 101; //0-100之间的随机数 UpdateData(FALSE); //数据更新到控件}int ADD::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CDialogEx::OnCreate(lpCreateStruct) == -1) return -1; // TODO: 在此添加您专用的创建代码 return 0;}//查询按钮void ADD::OnBnClickedButton13() { //判断输入的成绩范围是不是为空的 CString cstr1; CString cstr2; CString cstr3; CString cstr4; GetDlgItem(IDC_EDIT7)->GetWindowText(cstr1); GetDlgItem(IDC_EDIT8)->GetWindowText(cstr2); GetDlgItem(IDC_EDIT9)->GetWindowText(cstr3); GetDlgItem(IDC_EDIT10)->GetWindowText(cstr4); if (cstr1 == _T("") || cstr2 == _T("") || cstr3 == _T("") || cstr4 == _T("")) { MessageBox(TEXT("输入的成绩的范围不能为空!")); return; } //============================================================== UpdateData(TRUE); //进行数据的合法性判断 if (Search_Score1_Max > 100 || Search_Score1_Max < 0 || Search_Score1_Min > 100 || Search_Score1_Min < 0 || Search_Score2_Max > 100 || Search_Score2_Max < 0 || Search_Score2_Min > 100 || Search_Score2_Min < 0) { MessageBox(TEXT("输入的成绩的范围必须在区间[0,100]内!")); return; } if (Search_Score1_Min > Search_Score1_Max || Search_Score2_Min > Search_Score2_Max) { MessageBox(TEXT("输入的成绩最小值不能比最大值大!")); return; } //=============================================================== //开始查询 std::vector<Student> result; for (int i = 0; i < m.size; ++i) { Student stu = m.all[i]; if (stu.m_name.Find(Search_Name) != -1 && stu.m_id.Find(Search_Id) != -1 && stu.score1 >= Search_Score1_Min && stu.score1 <= Search_Score1_Max && stu.score2 >= Search_Score2_Min && stu.score2 <= Search_Score2_Max) { //满足条件 result.push_back(stu); } } CString str; for (auto stu : result) { CString cScore1; CString cScore2; cScore1.Format(_T("%d"), stu.score1); cScore2.Format(_T("%d"), stu.score2); str += _T("学生姓名:") + stu.m_name + _T("\t学号:") + stu.m_id + _T("\t程序课成绩:") + cScore1 + _T("\t高数课成绩:") + cScore2 + _T("\n"); } CString cLength; cLength.Format(_T("%d"), result.size()); MessageBox(str,_T("共查询到") + cLength + _T("条结果"));}//ChildView.h// ChildView.h: CChildView 类的接口//#pragma once#include "ADD.h"#include "Management.h"#include<vector>using namespace std;// CChildView 窗口class CChildView : public CWnd{ // 构造public: CChildView(); ADD add;// 特性public:// 操作public:// 重写 protected: virtual BOOL PreCreateWindow(CREATESTRUCT& cs);// 实现public: virtual ~CChildView(); // 生成的消息映射函数protected: afx_msg void OnPaint(); DECLARE_MESSAGE_MAP()public: afx_msg void OnLButtonDown(UINT nFlags, CPoint point); void DrawBar(CDC *dc, std::vector<int> db,bool left = true) const; void DrawLine(CDC *dc, std::vector<int> db,bool left = true) const; void OnDraw(CDC* pDC);};//ChildView.cpp// ChildView.cpp: CChildView 类的实现//#include "pch.h"#include "framework.h"#include "ChildView.h"#ifdef _DEBUG#define new DEBUG_NEW#endifextern Management m; //需要和ADD.cpp共用一个Management对象,所以这里要声明extern// CChildViewCChildView::CChildView() { }CChildView::~CChildView() { }BEGIN_MESSAGE_MAP(CChildView, CWnd) ON_WM_PAINT() ON_WM_LBUTTONDOWN() ON_WM_DRAWITEM() ON_WM_DRAWITEM()END_MESSAGE_MAP()// CChildView 消息处理程序BOOL CChildView::PreCreateWindow(CREATESTRUCT& cs) { if (!CWnd::PreCreateWindow(cs)) return FALSE; cs.dwExStyle |= WS_EX_CLIENTEDGE; cs.style &= ~WS_BORDER; cs.lpszClass = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS, ::LoadCursor(nullptr, IDC_ARROW), reinterpret_cast<HBRUSH>(COLOR_WINDOW + 1), nullptr); return TRUE;}void CChildView::OnPaint() { CPaintDC dc(this); // 用于绘制的设备上下文 // TODO: 在此处添加消息处理程序代码 // 不要为绘制消息而调用 CWnd::OnPaint() OnDraw(&dc);}//画条状图,db:vector类的集合,里面存放所有要统计的数据void CChildView::DrawBar(CDC* dc, std::vector<int> db,bool left) const { //left:是否在左边 CRect rc; //外面的矩形 GetClientRect(rc); //获取CWnd客户区的尺寸。(让rc占满窗口) rc.DeflateRect(30, 15, 10, rc.Height() / 2+15); //x1,y1,x2,y2 if(left) { //如果是左边,相应修改一下x2的值 rc.right = rc.Width() / 2 - 15; dc->TextOutW(rc.left, 0, TEXT("高数课成绩统计")); }else { rc.left = rc.Width() / 2 + 15; dc->TextOutW(rc.left, 0, TEXT("程序课成绩统计")); } CBrush brush1(HS_BDIAGONAL, RGB(96, 192, 255)); //向下阴影(从左到右)在45度(右斜),蓝色 CBrush brush2(HS_FDIAGONAL, RGB(96, 192, 255)); //向上阴影(从左到右)在45度(左斜),蓝色 CPen pen(PS_INSIDEFRAME, 2, RGB(96, 192, 255)); // int n = 5; //定义有五个区间 int width = rc.Width() / n; //一个区间的宽度 int s[] = { 0, 0, 0, 0, 0}; //各区间人数 //统计各区间人数 for (int i = 0; i < db.size(); i++) { if (db[i] < 60) s[0] ++; else if (db[i] >= 60 && db[i] < 70) s[1]++; else if (db[i] >= 70 && db[i] < 80) s[2]++; else if (db[i] >= 80 && db[i] < 90) s[3]++; else s[4]++; } //找出人数最大的区间 int max_s = s[0]; for (int i = 0; i < n; i++) if (max_s < s[i]) max_s = s[i]; int per_Height = rc.Height() / max_s; //算出一个人代表的矩形高度 CRect ps_rect(rc); //直方图的矩形 ps_rect.right = ps_rect.left + width; dc->SelectObject(&pen); //选择画笔 CString str[5] = { _T("<60"),_T("60-70"),_T("70-80"),_T("80-90"),_T(">=90")}; //将每个区间的注释先存储 for (int i = 0; i < n; i++) { ps_rect.top = ps_rect.bottom - per_Height * s[i]; //该区间的高度 if (i % 2) dc->SelectObject(&brush2); else dc->SelectObject(&brush1); //每隔一个区间用不同的斜线,左斜或右斜 dc->Rectangle(ps_rect); //绘制矩形 if (s[i] > 0) { CString str1; str1.Format(_T("%d人"), s[i]); //设置str1的内容 dc->DrawText(str1, ps_rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); //在矩形中间显示人数 } //输出注释在矩形下面 dc->TextOutW((ps_rect.left + ps_rect.right) / 2 - 15, ps_rect.bottom+5 , str[i]); ps_rect.OffsetRect(width, 0); //移动矩形框的位置,水平方向移动width }}//折线图void CChildView::DrawLine(CDC* dc, std::vector<int> db,bool left) const { CRect rc; //最外面的矩形 GetClientRect(rc); //让矩形占满窗口 rc.DeflateRect(30, rc.Height() / 2 + 15, 10, 15); x1,y1,x2,y2 if (left) { //如果是左边,相应修改一下x2的值 rc.right = rc.Width() / 2 - 15; } else { rc.left = rc.Width() / 2 + 15; } int gridXnums = db.size(); //列数 int gridYnums = 10; //行数 int dx = rc.Width() / gridXnums; //每一个格子的宽度 int dy = rc.Height() / gridYnums; //每一个格子的高度 CRect gridRect(rc.left, rc.top, rc.left + dx * gridXnums, rc.top + dy * gridYnums); CPen gridPen(PS_DASHDOTDOT, 1, RGB(96, 192, 255)); //获取蓝色的点状画笔 dc->SelectObject(&gridPen); //选择画笔 //------------------------------------------------------------------- //画格子 for (int i = 0; i <= gridXnums; i++) { dc->MoveTo(gridRect.left + i * dx, gridRect.bottom); dc->LineTo(gridRect.left + i * dx, gridRect.top); } for (int j = 0; j <= gridYnums; j++) { dc->MoveTo(gridRect.left, gridRect.top + j * dy); dc->LineTo(gridRect.right, gridRect.top + j * dy); CString str; str.Format(_T("%d"), 100 - 10 * j); dc->TextOutW(rc.left - 25, gridRect.top + j * dy - 10, str); //标注纵坐标的值 } //------------------------------------------------------------------- gridPen.Detach(); //官方解释:从CGdiObject对象分离Windows GDI对象,并将句柄返回给Windows GDI对象。 //但是不加这一行会报异常 CPen linePen(PS_SOLID, 2, RGB(255, 0, 0)); //创建一个红色实线画笔,用于画线 dc->SelectObject(&linePen); //选择画笔 int nCount = db.size(); //一共多少个数据 int deta = gridRect.Width() / nCount; //每一个数据所占的宽度 POINT* pt = new POINT[nCount]; for (int i = 0; i < nCount; i++) { pt[i].x = gridRect.left + i * deta; pt[i].y = gridRect.bottom - (int)(db[i] / 100.0 * gridRect.Height()); CString score; score.Format(_T("%d"), db[i]); //设置score的内容 // dc->TextOutW(pt[i].x - 5, gridRect.bottom + 5, score); //显示分数(在下面) } dc->Polyline(pt, nCount); //画线 delete[] pt; pt = nullptr;}void CChildView::OnDraw(CDC* pDC) { vector<int> programmingScores = m.getProgrammingScores(); vector<int> mathScores = m.getMathScores(); if (!mathScores.empty() && !programmingScores.empty()) { //只有数据不为空了才去画图 DrawBar(pDC, mathScores,true); //左边条形图画高数 DrawBar(pDC, programmingScores,false); //右边条形图画程序 DrawLine(pDC, mathScores,true); //左边折线图画高数 DrawLine(pDC, programmingScores,false); //右边折线图画程序 }}//左void CChildView::OnLButtonDown(UINT nFlags, CPoint point) { CWnd::OnLButtonDown(nFlags, point); //弹出对话框 add.DoModal(); //重绘统计图 Invalidate(); // 重画窗口。 OnPaint();}
五、程序运行时的效果图
- 统计图
-
数据管理
-
添加学生时,姓名、学号或成绩为空
-
添加学生时,学号不是数字或者长度不为10
-
如果姓名或学号输入了空格
-
输入的成绩越界
-
成功添加数据
-
成功删除数据
-
成功修改数据(容错条件和添加数据一样,不再演示)
-
按成绩排序(从折线图看效果)
-
数据查询,查询全部
-
数据查询,查询高数课和程序课成绩都大于80分的
-
数据查询,查询姓名里带有“刘”字的
-
查询学号里带有2的
六、实验结果分析
我在实验中遇到的问题和解决方法
- 对话框初始化时程序崩溃,如下图:
本来我是打算在对话框一打开就自动加载数据,而不需要手动加载数据,所以我在对话框的OnCreate()函数里进行列表的初始化操作,才会发生这样子的程序奔溃,经过我后面的尝试,把初始化代码放到OnActivate()函数里才解决问题。问题产生的原因我不太清楚,我个人猜测是OnCreate函数在窗口还没完全显示的时候执行,而OnActivate会在窗口显示完成之后才执行,应该是因为窗口还没显示的时候把数据添加到列表导致的问题。
-
Management对象的问题
因为我所有的学生数据都存放在Management里的all数组里,所以我需要在对话框ADD类和单文档绘图的View类里共用一个Management对象,一开始我是把对象创建在.h文件中,结果造成了一些错误(无法解析的外部符号),于是我按照网上的办法,把对象放在.cpp文件中。但是新的问题出现了,我本以为多个cpp文件如果共用一个变量,只需在一个.cpp中包含另一个.cpp就行了,结果我在一个debug中,发现Management对象竟然创建了5次,这时我才意识到多个cpp中的Management并不是共用的,后来我的解决办法是在View.cpp中的Management加extern修饰符。 -
数据文件保存和读取问题
我在Management类的构造和析构函数中对数据文件进行读取和保存,我每向文件输出完一条学生信息,我都会换行,但是在读取的时候会莫名地多出一条空白数据,经过我的研究,是因为输出最后一行数据再回车导致的问题。我的解决办法是在向文件输出时加个if判断,如果这条数据不是最后一条数据,则再输出回车。 -
关于成绩输入框的弹窗问题
我成绩输入框创建变量的时候,类型设置为int,当执行UpdataData函数的时候,如果里面不是数字或者为空,会弹出这样的一个框
本来这样的提示是没什么问题的,但是弹出框了之后,如果是添加数据,则照样会添加成功,只是成绩都是默认值0,但这样子就不符合逻辑了!我的解决办法是:先在把编辑框的Number属性改成True,保证了输入框无法输入数字,然后就还剩输入为空的情况,我就通过以下代码,在UpdateData之前检查输入是否为空:
收获
- 更加熟悉MFC编程,学会了在View类的绘图方法,学会了基于对话框的很多内容。
- 重新复习了上个学期所学的冒泡排序、动态数组等知识。
- 领悟了CString与string的区别,并学会了CString与string的互相转换、其他基本数据类型转换成CString。
- 掌握了面向对象的编程思想。
发表评论
最新留言
关于作者
