先上题目:
设计单链表类,并基于单链表类实现栈类和队列类:
(1)设计学生信息类StudentRecord,要求包含公有数据成员:string stuName和int stuNo,设计用于输出学生信息的公有成员函数:void print(),输出格式为:Name: stuName, Number: stuNo。
(2)设计学生链表的结点类StudentNode,要求包含公有数据成员:StudentRecord data和StudentNode *next。
(3)设计学生链表类LinkedList,要求定义头插入、头删除、尾插入、遍历等公有成员函数。
(4)由LinkedList派生LinkedStack类,基于单链表类的功能实现压栈和出栈的成员函数:void Push(StudentRecord record)和bool Pop(StudentRecord &record)。
(5)由LinkedList派生LinkedQueue类,基于单链表类的功能实现入队和出队的成员函数:void EnQueue(StudentRecord record)和bool DeQueue(StudentRecord &record)。
在main函数中:
定义一个LinkedQueue类的对象queue和一个LinkedStack类的对象stack,并根据用户的输入
分别对queue和stack作出相应的操作。若为"Push",则压栈;若为"EnQueue",则入队;若
为"Pop",则出栈;若为"DeQueue",则出队;若为"Exit",则退出;若为其它,则给出提示信
息"Input error!"。入栈和入队时,输入学生姓名和学号。出栈和出队时,若非空,则输出
被删除的学生信息;若栈空,则输出Stack is empty!";若队空,则输出"Queue is empty!"。
接下来附上解题思路。
我在单链表中引入一个头结点作为表头,以避免对空表的处理,从而避免对边界情况做特别考虑。作为表中的一个虚结点,这个头结点的值被忽略,不被看作表中的实际元素。以下是带有头结点的空表示意图:
空表的临界条件为头指针head和尾指针tail值相等。
用单链表实现入栈,即是让单链表的插入结点方式为头插入。以下是示意图:
用单链表实现入队,即是让单链表的插入结点方式为尾插入。以下是示意图:
出栈和出队都统一成删除单链表里第一个结点(不包括头结点)。
出栈操作如下所示:
出队操作时要特殊考虑队中只有一个结点的情况:
出队时队中不止一个结点:
接下来给出具体实现的代码:
class StudentRecord{//学生数据类
private:
string stuName;
int stuNo;
public:
void print()
{
cout<<"Name: "<<stuName<<", Number: "<<stuNo<<endl;
}
StudentRecord& operator=(const StudentRecord &stu)//重载等号
{
stuName=stu.stuName;
stuNo=stu.stuNo;
return *this;
}
void setData(string str,int no)//赋值操作
{
stuName=str;
stuNo=no;
}
friend class LinkedList;
};
class StudentNode{//结点类
public:
StudentRecord data;
StudentNode *next;
StudentNode()
{
next=NULL;
}
};
在链表类中把出栈、出队统一为函数bool deleteNode(),把获取栈中、队中的一个结点数据统一为函数bool getData(StudentRecord &record)。
class LinkedList{//单链表类
protected:
StudentNode *head,*tail;
public:
LinkedList()//创建链表
{
head=tail=new StudentNode;
head->next=NULL;
(head->data).setData("head_node",0);
}
~LinkedList()//删除链表
{
StudentNode *temp;
while(head!=tail)
{
temp=head;
head=head->next;
delete temp;
}
delete head;
}
void headAdd(StudentNode &node)//头插入结点
{
node.next=head;
head=&node;
}
bool deleteNode()//从头删除一个结点
{
if(empty()){
return false;//删除失败
}
StudentNode *temp;
if((head->data).stuName=="head_node"){//如果是尾插
if((head->next)->next==NULL){//如果只有一个结点(除头结点)
tail=head;
temp=head->next;
head->next=NULL;
delete temp;
return true;//删除成功
}
else{//如果多于1个结点(除头结点)
temp=head->next;
head->next=(head->next)->next;
delete temp;
return true;//删除成功
}
}
else{//如果是头插
temp=head;
head=head->next;
delete temp;
return true;//删除成功
}
}
void tailAdd(StudentNode &node)//尾插入结点
{
tail->next=&node;
tail=&node;
}
bool empty()//判断链表是否为空
{
if(head==tail){
return true;
}
else return false;
}
bool getData(StudentRecord &record)//获取链表中第一个数据
{
if(empty()){
return false;//获取失败
}
if((head->data).stuName=="head_node"){
record=(head->next)->data;
return true;//获取成功
}
else{
record=head->data;
return true;//获取成功
}
}
};
实际使用时出栈操作还伴随着获取结点数据,故bool Pop()函数中结点出栈前要先启用函数bool getData(StudentRecord &record)来保存即将出栈的结点。
class LinkedStack:protected LinkedList{//单链表派生栈类
public:
void Push(StudentRecord record)
{
StudentNode *temp=new StudentNode;
temp->data=record;
headAdd(*temp);//入栈即头插入
}
bool Pop(StudentRecord &record)
{
if(!getData(record)){//保存将要出栈结点数据
return false;//栈空,无法进行出栈操作
}
else{//栈非空
deleteNode();//出栈
return true;//出栈成功
}
}
};
实际使用时出队操作还伴随着获取结点数据,故bool DeQueue()函数中结点出队前要先启用函数bool getData(StudentRecord &record)来保存即将出队的结点数据。
class LinkedQueue:protected LinkedList{//单链表派生队类
public:
void EnQueue(StudentRecord record)
{
StudentNode *temp=new StudentNode;
temp->data=record;
tailAdd(*temp);//入队即尾插
}
bool DeQueue(StudentRecord &record)
{
if(!getData(record)){//接收将要出队结点数据
return false;//队空,无法进行出队操作
}
else{//队非空
deleteNode();//出队
return true;//出队成功
}
}
};
最后是主函数main()
#include <iostream>
#include <string>
using namespace std;
int main()
{
LinkedQueue queue;
LinkedStack stack;
//signal用来获取操作,name用来接受学生姓名
string signal,name;
//number用来接收学生学号
int number;
//save用来保存结点数据
StudentRecord save;
cin>>signal;
while(signal!="Exit")
{
//操作为入栈或入队
if(signal=="Push"||signal=="EnQueue"){
cin>>name>>number;//接收数据
save.setData(name,number);//获得结点数据
if(signal=="Push"){//入栈操作
stack.Push(save);//结点入栈
}
else queue.EnQueue(save);//入队操作,结点入队
cin>>signal;//获取下一个操作
continue;
}
//操作为出栈或出队
if(signal=="Pop"||signal=="DeQueue"){
if(signal=="Pop"){//出栈操作
if(stack.Pop(save)){//成功出栈
save.print();//打印出栈结点数据
}
else cout<<"Stack is empty!"<<endl;
}
else{//出队操作
if(queue.DeQueue(save)){//成功出队
save.print();//打印出队结点数据
}
else cout<<"Queue is empty!"<<endl;
}
cin>>signal;//获取下一个操作
continue;
}
//不是入栈、入队,不是出栈、出队,也不是退出程序
cout<<"Input error!"<<endl;
cin>>signal;
}
return 0;
}
总结:
使用派生类时,三种继承方式的用途:
1.希望派生类仅能在类内访问父类protected成员,并能用对象来访问父类public成员,用public继承方式;
2.希望派生类仅能在类内访问父类public、protected成员,用protected继承方式;
3.希望派生类类内类外都无法访问父类protected、public成员,用private继承方式。
子类不继承父类构造函数,创建子类对象时,编译器将会先调用父类的构造函数,再调用子类的构造函数。