大一期末作业 学生管理系统(c++源码,利用map和multimap,索引思想,非链表 非数组,继承类)
文章目录
前言
大一期末大作业就是写个学生人员的管理小系统,本来打算是用简单的学生类链表数组写,但是大家都知道链表插入快,查找慢,数组就更不考虑了。然而,对于一个系统 查找是非常重要且频繁的操作。所以想着利用索引思想,就选用map和multimap,恰好一个可重复一个不可重复。
map不可重复,则需要一个基础类给学号类这种不可重复继承
multimap可重复,也需要一个基础类给给姓名类继承
当然,本身学生就是一个基础类
基本思想
map中元素是key-value配对的,要查找一个元素,需要提供它的key。map的内部是以平衡二叉树形式储存的,所以查找的速度为O(logn),速度很快。我们的学生一个属性就是一个key,value就是学生实体。多个属性就有多个map,多个key对应一个实体。虽然我们要多写几个map,但是空间的需求基本的可以解决的,而时间的需求有时候来的更加重要。
MyISAM引擎使用B+Tree作为索引结构,InnoDB一样,所以总是有可取之处。
提示:以下是本篇文章正文内容,下面案例可供参考
一、前提准备
如上述所说:需要准备两个基础类用于继承和一个学生基础类,见代码:
1.1 学生类:这里直接用结构体,因为懒
typedef struct student{
char num[8];
string name;
int classnum;
double gpa;
int math;
int english;
int chinese;
int index;
friend ostream& operator <<(ostream& os,const student& s)
{
os<<s.num<<" "<<s.name<<" "<<s.classnum<<" "<<setiosflags(ios::fixed)<<setprecision(2)<<s.gpa<<" "<<s.chinese<<" "<<s.math<<" "<<s.english;
os<<endl;
return os;
}
}Student;
此处利用友元函数将输出符号重载,以便后面使用(博主太懒了)
1.2 multimap类型的基础类
为了提高泛用性,使用类模板,在基础类里面实现show()和find()。xiugai()和delete()留着继承类实现。
show():翻页功能
find():寻找功能
template<class T>
class Mapbase{
public:
multimap<T,Student*> m;
public:
Mapbase(){
m.clear();
}
void show(){
int k=0;
typename multimap<T,Student*>::iterator iter;
cout<<endl;
for(iter = m.begin(); iter != m.end(); iter++) {
cout<<iter->first<<" | "<<*(iter->second);
k++;
if(k%10==0)
{
cout<<"< 上一页"<<"下一页 >"<<" "<<"esc 退出"<<endl;
int key, key2;
key = getch();
if (key==224) {
key2 = getch();
if (key2 == 72||key2== 75){
}
else if (key2== 80||key2== 77) {
continue;
}
}
else if(key==27)
{
return ;
}
else{
cout<<"请输入正确的操作数";
}
}
}
}
void find()
{
cout<<endl;
T temp;
cout<<kk<<"请输入你要查询的信息";
cin>>temp;
typename multimap<T,Student*>::iterator beg= m.lower_bound(temp);
typename multimap<T,Student*>::iterator end= m.upper_bound(temp);
while(beg != end)
{
cout<<beg->first<<*beg->second;
++beg;
}
};
void xiugai();
void deletes();
~Mapbase(){
m.clear();
}
};
1.3 map类型的基础类
如multimap一样 只需将multimap改成map,稍作修改即可,此处不贴代码,见谅。
1.4 cmp_str
当使用 char* 作为键值时,默认映射将比较指针,而不是指针的引用。 所以结果将不是我们的预期。 两种解决方案: 使用字符串作为键;或者为程序声明,如以下代码所示。
struct cmp_str
{
bool operator()(char const *a, char const *b)
{
return strcmp(a, b) < 0;
}
};
配合以下代码使用
map<char*,Student*,cmp_str> m;
二、类继承
按照上述学生结构体的属性(属性较少),设置5个新类继承两个基本类即可,此处展示NUM类,其他类以此类推,需要完整源码,见评论,见谅。
NUM num;
NAME na;
GPA gp;
CLASSNUM cn;
INDEX in;
NUM类
class NUM:public Mapbase2<char*>
{
public:
map<char*,Student*,cmp_str> m;
public:
NUM()
{
for(int i=0;i<n;i++)
{
m.insert(pair<char*,Student*>(s[i].num,&s[i]));
}
}
public:
void find()
{
char temp[10];
cout<<"请输入你要查询的学号";
cin>>temp;
if(m[temp]==0)
{
cout<<"can 't find";
}
else
{
cout<<"The result :";
cout<<*m[temp];
}
};
void xiugai(){
int choose,choose2;
string ss;
double dd;
int ii;
char temp[10];
cout<<"请输入你要修改用户的信息"<<endl;
cin>> temp;
typename map<char*,Student*>::iterator begin= m.find(temp);
cout<<endl<<*(begin->second)<<endl;
if(m[temp]==0)
{
cout<<"can 't find";
}
cout<<"请问你要修改什么信息"<<endl;
cout<<"[1]学号"<< "[2]姓名"<< "[3]班级"<< "[4]GPA"<<"[5]语文成绩"<< "[6]数学成绩"<< "[7]英语成绩"<<endl;
cin>>choose;
Student temps;
char *temp3=new char[10];
switch(choose)
{
case 1:
cout<<"学号不能修改";break;
case 2:
cout<<"请输入修改后的信息";
cin>>ss;
temps=*(begin->second);
s[temps.index].name=ss;
temp3=begin->first;
m.erase(begin);
m.insert(make_pair(temp3,&s[temps.index]));
break;
case 3:
cout<<"请输入修改后的信息";
cin>>ii;
temps=*(begin->second);
s[temps.index].classnum=ii;
temp3=begin->first;
m.erase(begin);
m.insert(make_pair(temp3,&temps));
break;
case 4:
cout<<"请输入修改后的信息";
cin>>dd;
temps=*(begin->second);
s[temps.index].gpa=dd;
temp3=begin->first;
m.erase(begin);
m.insert(make_pair(temp3,&s[temps.index]));
break;
case 5:
cout<<"请输入修改后的信息";
cin>>ii;
temps=*(begin->second);
s[temps.index].chinese=ii;
temp3=begin->first;
m.erase(begin);
m.insert(make_pair(temp3,&s[temps.index]));
break;
case 6:
cout<<"请输入修改后的信息";
cin>>ii;
temps=*(begin->second);
s[temps.index].math=ii;
temp3=begin->first;
m.erase(begin);
m.insert(make_pair(temp3,&s[temps.index]));
break;
case 7:
cout<<"请输入修改后的信息";
cin>>ii;
temps=*(begin->second);
s[temps.index].english=ii;
temp3=begin->first;
m.erase(begin);
m.insert(make_pair(temp3,&s[temps.index]));
break;
}
}
};
NAME类 GPA类 CLASSNUM类 见评论源码
INDEX类
index这个属性需要额外注意,本身在数据文档里面是没有这个属性的,是在输入时候自动添加的,最后不需要保存这个属性。
有何用:我们知道修改功能不同于删除功能,修改功能要求同步,需要你在第一时刻就完成改动并反馈数据库或文本文件。那么我们怎么知道这条数据在原本数据库中的哪个位置,而index恰好利用顺序输入的这个特性直接在输入时候记录原本的位置,那么在修改的时候就方便的多。
while(!in.eof())
{
in>>s[n].num>>s[n].name>>s[n].classnum>>s[n].gpa>>s[n].chinese>>s[n].math>>s[n].english;
s[n].index=n;
n++;
}
三 基本操作
添加功能
利用insert函数和pair方法添加,这里需要注意的一旦添加一个学生,那么五个属性类就都要添加相关信息。
cout<<"\n"<<"请输入相关学生信息";
cout<<"\n"<<"学号"<< "\t姓名"<< "\t班级"<< "\tGPA"<<"\t语文成绩"<< "\t数学成绩"<< "\t英语成绩"<<endl; cin>>s[n].num>>s[n].name>>s[n].classnum>>s[n].gpa>>s[n].chinese>>s[n].math>>s[n].english;
num.m.insert(pair<char*,Student*>(s[n].num,&s[n]));
na.m.insert(pair<string,Student*>(s[n].name,&s[n]));
gp.m.insert(pair<double,Student*>(s[n].gpa,&s[n]));
cn.m.insert(pair<int,Student*>(s[n].classnum,&s[n]));
in.m.insert(pair<int,Student*>(s[n].index,&s[n]));
删除
删除功能需要解决的问题有:
(1)按照什么删除?
(2)删除对象一次性有多个怎么办
问题(1)解决:
本程序选择以学号和姓名删除,因为这两个属性具有代表性,特征性够强烈,总不能用成绩删除吧,一次性出来一堆100分啊60分啊,就难受。
问题(2)解决,本文会予以提示:
名字叫刘伟的不只是一个人,所以本文也为此问题做出解决:
一旦出现多人:
请问你要删除第几个,假设我们选择删除 第二个
查找
这时候上面已经删除了,我们就利用查找功能找一下子
我们选择姓名,输入刘伟,其他学号班级下表什么的就不演示(见谅)
欸,我们发现还是有两个,但是有区别,我们把他的学号直接改成了no。为什么这么做?
删除有两种方法:
(1)马上删除,假设我们的数据在数据库里面或者txt文本里面,那么如果即可删除就需要调用一次数据库操作,或者文件操作。又或是,我们在程序初始化的时候先把所有数据加载到一个容器里面,可能也是map可能是数组可能是链表,但是我们要知道,删除本身就带着查询功能,较为繁琐的同时可能还涉及同步异步的问题(你删除之后,你是不是或者要不要马上更新啊此类问题)。
(2)所以我的方法是,学号唯一标识,标记为no。假设他已经删除了,我们只需要在查询修改增加等其他基本操作的时候注意此类问题,比如查询,刚刚我把删除的刘伟显示了,你也可以选择不显示,修改的时候遇到no直接提示无此人等操作来避免相关问题
在最后存储写回数据库或文本文件的时候将学号为no的去除。
cout<<kk<<"按照什么删除";
cout<<kk<<"[1]学号"<< "\t[2]姓名"<<endl;
cin>>choose;
cout<<kk<<"请输入删除信息";
switch(choose)
{
case 1:
cin>>numtemp;
temps=*num.m[numtemp];
cout<<temps.index;
strcpy(s[temps.index].num,"no");
cout<<s[temps.index];
break;
case 2:
cin>>temp;
typename multimap<string,Student*>::iterator beg= na.m.lower_bound(temp);
typename multimap<string,Student*>::iterator end= na.m.upper_bound(temp);
typename multimap<string,Student*>::iterator begin=beg;
if(end==beg)
{
cout<<"查无此人";
return ;
}
while(beg != end)
{
cout<<beg->first<<*beg->second;
++beg;
}
if(na.m.count(temp)>1)
{
cout<<endl<<"请问要删除上面的第几个";
cin>>choose2;
for(int i=0;i<choose2-1;i++)
{
begin++;
}
}
else {
}
temps=*(begin->second);
strcpy(s[temps.index].num,"no");
cout<<s[temps.index];
break;
}
修改
修改函数
各个属性类继承重写了基本类的修改函数。举个例子,NUM的修改函数
void xiugai(){
int choose,choose2;
string ss;
double dd;
int ii;
char temp[10];
cout<<"请输入你要修改用户的信息"<<endl;
cin>> temp;
typename map<char*,Student*>::iterator begin= m.find(temp);
cout<<endl<<*(begin->second)<<endl;
if(m[temp]==0)
{
cout<<"can 't find";
}
cout<<"请问你要修改什么信息"<<endl;
cout<<"[1]学号"<< "[2]姓名"<< "[3]班级"<< "[4]GPA"<<"[5]语文成绩"<< "[6]数学成绩"<< "[7]英语成绩"<<endl;
cin>>choose;
Student temps;
char *temp3=new char[10];
switch(choose)
{
case 1:
cout<<"学号不能修改";break;
case 2:
cout<<"请输入修改后的信息";
cin>>ss;
temps=*(begin->second);
s[temps.index].name=ss;
temp3=begin->first;
m.erase(begin);
m.insert(make_pair(temp3,&s[temps.index]));
break;
case 3:
cout<<"请输入修改后的信息";
cin>>ii;
temps=*(begin->second);
s[temps.index].classnum=ii;
temp3=begin->first;
m.erase(begin);
m.insert(make_pair(temp3,&temps));
break;
case 4:
cout<<"请输入修改后的信息";
cin>>dd;
temps=*(begin->second);
s[temps.index].gpa=dd;
temp3=begin->first;
m.erase(begin);
m.insert(make_pair(temp3,&s[temps.index]));
break;
case 5:
cout<<"请输入修改后的信息";
cin>>ii;
temps=*(begin->second);
s[temps.index].chinese=ii;
temp3=begin->first;
m.erase(begin);
m.insert(make_pair(temp3,&s[temps.index]));
break;
case 6:
cout<<"请输入修改后的信息";
cin>>ii;
temps=*(begin->second);
s[temps.index].math=ii;
temp3=begin->first;
m.erase(begin);
m.insert(make_pair(temp3,&s[temps.index]));
break;
case 7:
cout<<"请输入修改后的信息";
cin>>ii;
temps=*(begin->second);
s[temps.index].english=ii;
temp3=begin->first;
m.erase(begin);
m.insert(make_pair(temp3,&s[temps.index]));
break;
}
}
查找
查找功能同修改功能一样
各个属性类继承重写了基本类的查找函数。同上
存储
较简单,见源码
总结
记录一下大一大作业,800行有效代码的小程序
功能皆实现,但是界面做的丑: