C++动态链表实现学生信息管理系统

       这个是我在大一暑假写的代码,也就刚刚学完了C++,所以我会很详细的讲解我的思路和代码,这篇文章应该比较适合和我一样C++入门的小白。因为水平有限,所以有很多不足之处,请大家多多指正。

        我是用动态链表处理这些学生信息的,每次都从存储文件中读取学生信息,用动态链表串起来,处理过后再存回文件中。所以这篇文章可以帮助大家复习动态链表。我们一个功能一个功能地来讲述:

C++动态链表实现学生信息管理系统_第1张图片

     

   我们先来介绍功能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的缓冲区。

好了,学生信息管理系统的基础版就完成了,请大家探讨指正。

你可能感兴趣的:(C,入门,详细讲解,完整代码,动态链表,读写文件)