这个是我在大一暑假写的代码,也就刚刚学完了C++,所以我会很详细的讲解我的思路和代码,这篇文章应该比较适合和我一样C++入门的小白。因为水平有限,所以有很多不足之处,请大家多多指正。
我是用动态链表处理这些学生信息的,每次都从存储文件中读取学生信息,用动态链表串起来,处理过后再存回文件中。所以这篇文章可以帮助大家复习动态链表。我们一个功能一个功能地来讲述:
我们先来介绍功能1:录入信息
class Student
{
public:
Student(); //默认构造函数------67
Student(char n[20],char g[20],int number,char m[20],char b[5]); //有参构造函数 -----102
friend istream& operator>>(istream& ,Student&); //输入 -----116
friend ostream& operator<<(ostream& ,Student&); //输出 -----125
void show(); //输出学生信息 --------134
friend Student* find(int number,Student *p); //查询------------156
void insert(); //插入信息--------------169
void remove(); //删除 --------------- 195
void modify(); //修改 ---------------203
void chengji(); //录入成绩 ------------244
void xuanke();//选课系统 --------------292
int findpaiming(); //排名--------------360
friend Student* sortedmerge_1(Student *,Student *); //成绩排序---------
friend Student* sortedmerge_2(Student *,Student *); //学号排序---------
Student *next;
protected:
int bianhao; //选课编号
char name[20];
char gender[20];
int num;
char major[20];
char banji[5];
struct score ascore[6];
static struct schedule aschedule[10];
}*head=new Student(n,g,0,m,b);
Student::Student() //默认构造函数
{
bianhao=0;
cout<<"请输入学生姓名:"<>name;
cout<<"请输入学生性别:" <>gender;
while(1)
{
cout<<"请输入学生学号:" <>num) || cin.peek()!='\n' )
{
cin.clear();
cin.ignore( numeric_limits::max(), '\n' );
cout << "输入数据错误,请重新输入:" << endl;
}
if(find(num,head)!=NULL)
{
cout<<"您已录入过该学生,请检查学号是否正确"<>major;
cout<<"请输入学生班级:"<>banji;
for(int i=0;i<6;i++)
{
strcpy(ascore[i].subject,"C++");
ascore[i].fenshu=0;
ascore[i].gpa=0;
}
next=NULL;
}
Student::Student(char n[20],char g[20],int number,char m[20],char b[5]):num(number) //有参构造函数
{
strcpy(name,n);
strcpy(gender,g);
strcpy(major,m);
strcpy(banji,b);
for(int i=0;i<6;i++)
{
strcpy(ascore[i].subject,"C++");
ascore[i].fenshu=0;
ascore[i].gpa=0;
}
next=NULL;
}
这是我们的student类,有学生基本数据信息和成员函数。最后一行的head指针是全局变量,也是我们动态链表的头,录入信息即在动态链表的末端添加结点,代码如下,
void add() //录入
{
Student *p=head;
while(p->next !=NULL)
{
p=p->next;
}
p->next=new Student;
}
这一段代码很简单,先找到链表的最后一个结点,然后new一个student类的对象,同时调用student类的默认构造函数,就可以输入学生信息,最后让之前找到的最后一个结点的指针指向这个对象。因为在构造函数里已经将next设置为了null,所以不必担心出现野指针的问题。
在这个系统里,每次操作之前,我们都要将文件中的信息读取出来;每一次对学生信息的添加删除修改等任何操作之后,我们都会将新的信息写入文件中,这就涉及到两个函数
void read_from_file() //从文件中读取数据
{
Student *p=new Student(n,g,0,m,b);
head=p;
ifstream infile("si.txt");
if(!infile)
{
cerr<<"open error!"<next=new Student(n,g,0,m,b);
p=p->next;
infile>>*p;
infile.get();
if(infile.peek()=='\n')break;
}
p->next=NULL;
infile.close();
}
void mergesort_2(Student **headref);
void write_to_file() //将数据写入文件
{
mergesort_2(&head);
Student *p;
ofstream outfile("si.txt"); //,ofstream::binary
if(!outfile)
{
cerr<<"open error"<next;
while(p!=NULL)
{
outfile<<*p;
p=p->next ;
}
outfile.close();
}
这里有几点需要解释一下:
1.我为什么可以infile和outfile运算对象,因为我在这里用了运算符的重载
istream& operator>>(istream& input,Student& a) //输入
{
input>>a.name>>a.gender>>a.major>>a.banji>>a.num>>a.bianhao;
for(int i=0;i<6;i++)
{
input>>a.ascore[i].subject>>a.ascore[i].fenshu>>a.ascore[i].gpa;
}
return input;
}
ostream& operator<<(ostream& output,Student& a) //输出
{
output<
这两个函数都在student类里面申明为友元函数,就可以对类的数据进行操作。
2.infile.get():使文件指针下移一位,infile.peek!='\n'是为了避免文件末尾的换行符被读进链表中。
3.将链表写入文件之前,我们对学号进行了排序,这样会方便日后的查找。方法是归并排序,有兴趣的同学可以自己去查一查,由于篇幅有限,我在这里就不多说了。
Student* sortedmerge_2(Student *a,Student *b) //学号排序
{
Student *result=NULL;
if(a == NULL)
return (b);
else if(b == NULL)
return (a);
if(a->num num )
{
result=a;
result->next =sortedmerge_2(a->next, b);
}
else
{
result=b;
result->next =sortedmerge_2(a,b->next);
}
return (result);
}
void FrontBackSplit(Student *source,Student **frontref,Student **backref)
{
Student *fast,*slow;
if(source == NULL || source->next == NULL)
{
*frontref = source;
*backref = NULL;
}
else
{
fast=source->next ;
slow=source;
while(fast!=NULL)
{
fast=fast->next ;
if(fast!=NULL)
{
fast=fast->next ;
slow=slow->next ;
}
}
*frontref = source;
*backref = slow->next;
slow->next = NULL;
}
}
void mergesort_2(Student** headref) //void mergesort(Student** headref,int x) 排学号
{
Student *head=*headref;
Student *a,*b;
if((head == NULL) || (head->next == NULL))
{
return;
}
FrontBackSplit(head,&a,&b);
mergesort_2(&a);
mergesort_2(&b);
*headref=sortedmerge_2(a,b);
}
接下来介绍功能2:插入信息
这个功能其实是在链表的指定位置插入结点
所以第一步是找到要插入位置的前一个结点,我们通过学号来进行查找,调用函数p=find(number,head); p即前一个结点
Student *find(int number,Student *p) //查询
{
int i;
while(p!=NULL)//当p不等于null的时候就一直找下去
{
if(number==p->num)//p是一个类的指针,指向类的数据成员
{
return p;
}
p=p->next;
}
return NULL;
}
然后插入,调用函数p->insert()
void Student::insert() //插入新同学
{
Student *p=new Student;
p->next=this->next;
this->next=p;
}
其实这个功能有些鸡肋,因为我们每次存入信息之前都会对学号进行一次排序。只是在这里要讲动态链表的话,实现在链表中间插入是值得一提的。
再介绍功能3:显示信息
同样,我们需要先进行查询,仍是之前的find函数,p=find(number,head);通过学号找到该同学后,p->show();
void Student::show() //输出学生信息
{
cout<<"姓名:"<
功能4:删除信息
先查询其前一个结点的指针p=find(num-1,head); 然后用p->remove();
void Student::remove() //删除函数
{
Student *z;
z=this->next;
this->next=z->next;
delete(z);
}
在这个函数里,将要删除的结点指针赋给z,然后令p的next指向z的next,因为这是动态链表,所以需要delete(z),否则会导致内存溢出。
功能5:修改信息
同样需先查询到该结点,这里就不再赘言了,修改函数代码如下
void menu2() //修改菜单
{
cout<<"_______________________________________"<>m) || cin.peek()!='\n' )
{
cin.clear();
cin.ignore( numeric_limits::max(), '\n' );
cout << "输入数据错误,请重新输入:" << endl;
}
switch(m)
{
case 1:{
cout<<"您想把信息修改为"<>name;
break;
}
case 2:{
cout<<"您想把信息修改为"<>banji;
break;
}
case 3:{
cout<<"您想把信息修改为"<>major;
break;
}
case 4:{
cout<<"您想把信息修改为"<>gender;
break;
}
case 5:{
cout<<"您想把信息修改为"<>num;
break;
}
}
}
我在这里做了一个容错处理,如果用户输入不合法,就会报错,不至于使程序陷入死循环。
while( !(cin>>m) || cin.peek()!='\n' )
{
cin.clear();
cin.ignore( numeric_limits::max(), '\n' );
cout << "输入数据错误,请重新输入:" << endl;
}
如果输入不合法,就先用cin.clear()重置流的状态,使之有效
如果流处于错误的状态,cin.ignore();是不会执行的,最后用cin.ignore清空cin的缓冲区。
好了,学生信息管理系统的基础版就完成了,请大家探讨指正。