<0x00> 前言
我的题目是幼儿园的信息管理系统(第29号)
运行在Cmake 3.25.3+mingw 12.2.0上
基本要求:
能从文件读出一个班级小朋友的信息,并能将管理信息保存到文件
小朋友的信息包括:姓名、出生年月(年龄)、性别、住址、身高、体重、父母联系人姓名、联系电话,备注信息等
功能要求:
1、系统以菜单方式工作;
2、增加修改和删除小朋友信息;
3、查询某个小朋友的父母联系人姓名及联系电话;查询小朋友住址;
4、显示所有小朋友信息:能根据小朋友的身高、体重分别排序,或者按身高体重比排序;
5、能按性别统计班级男女生人数比例;
6、可以进行小朋友信息的文件读写。
实现上面功能是挺简单,所以我加入了一些新功能
于是就实现了些杂七杂八的东西(之后也会重点介绍的):
- 双向模板链表
- 方向键界面控制
- csv的文件读取和导出
- 多文件管理
- 从临时文件恢复
本来想实现更多的,实际上摸了就没写😋
(主要是真没啥好写的)
你可以在本站gitea下载源代码
(课设报告就不发了,自己写写挺快的)
<0x01> 双向链表实现
//Mylist.cpp 仅列出声明,完整代码见源文件
#pragma once
#include <cstddef> //NULL的定义在这个头文件里(我用的是mingw)
using namespace std;
template <class T>
class MyList; //提前声明类型
template <class T>
class MyNode
{
T content; // 存放内容
MyNode<T> *next; // 指向下一个Node
MyNode<T> *back; // 指向前一个Node
friend class MyList<T>; // 为List类提供访问权限(方便)
friend class Tool; // 为Tool类提供访问权限(方便)
MyNode();
public:
MyNode(const T &content, MyNode *next = NULL, MyNode *back = NULL);
MyNode(const MyNode &n); //一些节点的构造函数
MyNode<T> &operator=(const MyNode &n); //保险起见,重载下赋值运算
};
template <class T>
class MyList
{
int length; // List的长度
MyNode<T> *head; // 指向头节点
MyNode<T> *end; // 指向尾节点
MyNode<T> *Find(int index) const; // 内部按index搜索用
friend class Tool; // 为Tool类提供访问权限(懒得写访问的函数了)
public:
MyList();
bool Insert(const T &c, int n = 0); // 插入List,可指定插入位置
bool Delete(int index); // 按index删除List元素
void Clear(); // 清空List内容
int GetLength() const; // 返回List的长度
MyList<T> &operator=(const MyList<T> &n); //保险起见,重载下赋值运算
T *operator[](int index) const; // 外部按index搜索用
~MyList(); // 析构是门学问
};
泛型类 MyNode<T>
主要就是存放内容物的节点,为了数据结构的可拓展性,就写了个泛型
基本就没啥好说的,关键就是用指针关系吧各个节点连接起来
我这里采用的是双向链表的数据结构,即每个节点可以找到它的前一个后一个
所以我这里的MyNode类需要两个指针,一个指前一个节点,一个指后一个节点
泛型类 MyList<T>
说白了,这部分的内容就是把各个节点拼起来
为了首尾插入效率与保险起见,这个双向列表带有空的首尾节点
(但最终代码没有完成对末尾插入的优化,忘了
总之,数据结构图如下
每个节点都可以找到它的前后节点
虽然对插入操作没什么优势,但可以加速删除操作
对于删除操作,仅需传入节点位置即可删除
相当于把要删除的东西抽出来,再两边节点建立连接
(单向链表的删除比较抽象)
对于Clear()操作,相当于走一遍链表,边走边删对象
走完了再回归到初始状态
析构就是Clear()后再把首尾节点也删了
剩下的都挺直观的,也不说了
<0x02> 方向键控制
方向键控制的代码写在UI.hpp里UI.hpp里面都是一堆静态函数,提供一些标准化输出实现
(压缩代码量)
//能实现方向控制的示例代码
#include <conio.h> //必要的头文件
using namespace std;
int main()
{
char c;
c = _getch();
swich(c)
{
case 72:
//上方向键的代码
case 80:
//下方向键的代码
case 75:
//左方向键的代码
case 77:
//右方向建的代码
case 13:
//回车键的代码
}
return 0;
}
关键就是_getch()这个函数,他会捕获命令行界面的按键,然后返回对应按键的编码
其他的按键编码网上应该都能找到的
顺带讲下UI.hpp里的东西
结构体 UIInfo
就是用来传递菜单信息的结构体
好让主函数知道进行了什么操作,选了哪一个
UI类
里面都是静态函数
Confirm(...)函数用来显示确认菜单yesText写确认的文字noText写取消的文字content写显示的内容
Select(...)函数显示选择菜单
有三个重载
总之可以显示标题,功能选择项,一般选择项,描述词,默认指针位置title标题function功能选择项content一般选择项head描述词index默认指针位置
一般选择项十个每页,功能选择项始终显示
Show(...)函数就是个简单的输出函数
没啥好说的,让输出好看点而已
<0x03> CSV文件的读写
CSV文件的读写代码写在ManageTool.hpp里
//导出为CSV文件
//原代码的逻辑已经嵌在相关函数里了,这里单独拿出来
#include <fstream>
#include <cstddef>
#include <string>
#include "MyClass.hpp"
#include "MyDate.hpp"
#include "UI.hpp"
using namespace std;
string temp; //当前文件路径
MyClass *selectClass; //MyClass是我写的班级类
ofstream writeFile; //写入文件流
void ExportCSVFile()
{
writeFile.open(temp.substr(0, temp.length() - 4) + "-" + selectClass->className + ".csv", ios::out);
writeFile << "Class name: " << selectClass->className << "," << selectClass->children.GetLength() << ",\n";
writeFile << "Name,Birthday,Age,Gender,Address,Height,Weight,Parent's name,Parent's phone,note,\n";
for (int i = 0; i < selectClass->children.GetLength(); i++)
{
writeFile << selectClass->children[i]->name << ",";
writeFile << selectClass->children[i]->birthday.GetDate() << ",";
writeFile << selectClass->children[i]->age << ",";
if (selectClass->children[i]->isBoy)
{
writeFile << "Boy,";
}
else
{
writeFile << "Girl,";
}
writeFile << selectClass->children[i]->address << ",";
writeFile << selectClass->children[i]->height << ",";
writeFile << selectClass->children[i]->weight << ",";
writeFile << selectClass->children[i]->parentName << ",";
writeFile << selectClass->children[i]->parentPhone << ",";
writeFile << selectClass->children[i]->note << ",";
writeFile << "\n";
}
writeFile.close();
UI::Show("CSV file has been generate");
break;
}
//由CSV文件导入
//原代码的逻辑已经嵌在相关函数里了,这里单独拿出来
#include <fstream>
#include <cstddef>
#include <string>
#include <vector>
#include "MyClass.hpp"
#include "MyDate.hpp"
#include "UI.hpp"
using namespace std;
string path; //路径
MyClass *selectClass; //MyClass是我写的班级类
ifstream readFile; // 读取文件流
Student Create(vector<string> &list)
{
//由传入的list来创建Student对象并返回
//代码略,见原代码
}
void ImportCSV()
{
readFile.open(path + "\\" + files[u.index], ios::in);
selectClass->children.Clear();
string temp;
getline(readFile, temp, ',');
getline(readFile, temp, ',');
readFile.get();
vector<string> list;
int num = atoi(temp.c_str());
for (int i = 0; i < 10; i++)
{
getline(readFile, temp, ',');
} //用来忽略表头的
for (int i = 0; i < num; i++)
{
list.clear();
readFile.get(); //吞上一行回车
for (int j = 0; j < 10; j++)
{
getline(readFile, temp, ',');
list.push_back(p.assign(temp));
}
selectClass->children.Insert(Create(list), selectClass->children.GetLength());
}
readFile.close();
}
CSV文件其实就是数值1,数值2,...这样组成的文件,这种文件格式可以被Excel读取
输出的时候先输出基本班级信息,然后按行输出每个人的信息
导入CSV时先读取基本班级信息,然后按行导入学生信息
输出一般不大会遇到问题,就是读取时文件指针的控制是个问题
这里在读取时用getline(ifstream,string,char)来读取,碰到,就截断
前面还有readFile.get()来吞上一行的空格
<0x04> 多文件管理
为了方便备份和多幼儿园管理(?),实现了多工程文件的选择
选择和访问都应该不成问题,但获取对应文件夹下指定类型的所有文件是个问题
//关键代码
#include <io.h>
#include <vector>
#include <string>
using namespace std;
void GetFiles(string path, vector<string> &files, string fileType) // 获取指定路径下指类型的所有文件名
{
intptr_t hFile = 0; // 句柄编号
struct _finddata_t fileInfo; // 文件信息结构体
if ((hFile = _findfirst((path + "\\*" + fileType).c_str(), &fileInfo)) != -1)
{
do
{
files.push_back(fileInfo.name); // 给files加入文件名
} while (_findnext(hFile, &fileInfo) == 0);
_findclose(hFile); // 关闭句柄
}
}
这段代码实现了获取指定路径下指定文件类型的所有文件名
通过句柄实现(也就是用系统api)
最后的结果会保存在传入的files中
注意,网上的多数代码会将句柄编号的类型写为long
但在win10环境下,使用long类型的句柄会导致精度丢失进而导致错
使用intptr_t就没这个问题
<0x05> 从临时文件恢复
为了防止用户系统崩溃(也可能程序崩溃)导致的数据丢失
管理系统在选择文件后会新建一个一样的同名的.tmp临时文件
后续的所有文件写入操作都会对这个.tmp文件操作
只有在用户正常退出系统时才会将.tmp文件保存为.dat文件
具体来说就是选择文件时并没有真正打开选择的文件,而是读取到文件名
在选择班级之后,正常读取文件
如果在班级管理界面里修改了相关数据,并不是修改原文件,而是创建同名临时文件,将修改后的结果写入
最后用户正常退出后,在主对象析构中把原文件删了,然后把临时文件的后缀改成.dat
代码就不单独拆出来了,原代码里有具体标明的
<0x06> 把所有东西拼起来
总体采用了UI与处理分离的设计UI.hpp负责所有的界面输出,ManageTool.hpp负责所有的数据处理和页面逻辑
main()函数里就建立了Tool对象然后调用MainDisplay()启动
之后不同的功能就是不同的函数,不同的页面也是不同的函数
页面函数调用下一个页面的函数来实现功能的切换
页面函数相当于不同功能的调度器,页面输出还是靠UI.hpp的函数
UI的选择结果由UIInfo传递
其他的文件只是定义所需要的不同的类型
这样就吧所有的东西都拼起来了
后记
课设的基本要求都不难,但是都要完成
如果要拿优秀,肯定是要扩充功能的
如果想不到能扩充啥功能,可以优化UI
课设一般也不大需要多少时间,熟练的话写完课设大概3天