了解继承的概念之后,我们就来学习一下如何实现继承。
在第14章中我们说到,成员函数或成员数据可以是公有或者私有的。如果是公有的,那么它们可以被直接访问;如果是私有的,那么它们无法被直接访问。同时,我们还提到一个protected保留字,在没有使用继承的时候,它与private的效果是一样的,即无法被直接访问。如果使用了继承,我们就能体会到protected和private的差别。
private(私有)和protected(保护)都能实现类的封装性。private能够对外部和子类保密,即除了成员所在的类本身可以访问之外,别的都不能直接访问。protected能够对外部保密,但允许子类直接访问这些成员。public、private和protected对成员数据或成员函数的保护程度可以用下表来描述:
所以,当我们使用到继承的时候,必须考虑清楚:成员数据或成员函数到底应该是私有的还是保护的。
首先我们以一个学生类为例,介绍继承的写法:(程序17.3.1)
//student.h
- #include <iostream>
- using namespace std;
- class student//学生类作为父类
- {
- public:
- student(char *n,int a,int h,int w);//带参数的构造函数
- student();//不带参数的构造函数
- void set(char *n,int a,int h,int w);//设置
- char * sname();
- int sage();
- int sheight();
- int sweight();
- protected:
- char name[10];//姓名
- int age;//年龄
- int height;//身高
- int weight;//体重
- private:
- int test;
- };
- char * student::sname()
- {
- return name;
- }
- int student::sage()
- {
- return age;
- }
- int student::sheight()
- {
- return height;
- }
- int student::sweight()
- {
- return weight;
- }
- void student::set(char *n,int a,int h,int w)
- {
- int i;
- for (i=0;n[i]!='\0';i++)
- {
- name[i]=n[i];
- }
- name[i]='\0';
- age=a;
- height=h;
- weight=w;
- return;
- }
- student::student(char *n,int a,int h,int w)
- {
- cout <<"Constructing a student with parameter..." <<endl;
- set(n,a,h,w);
- }
- student::student()
- {
- cout <<"Constructing a student without parameter..." <<endl;
- }
//undergraduate.h
- #include "student.h"
- class Undergraduate:public student//本科生类作为子类,继承了学生类
- {
- public:
- double score();
- void setGPA(double g);//设置绩点
- bool isAdult();//判断是否成年
- protected:
- double GPA;//本科生绩点
- };
- double Undergraduate::score()
- {
- return GPA;
- }
- void Undergraduate::setGPA(double g)
- {
- GPA=g;
- return;
- }
- bool Undergraduate::isAdult()
- {
- return age>=18?true:false;//子类访问父类的保护成员数据
- }
//main.cpp
- #include <iostream>
- #include "undergraduate.h"
- using namespace std;
- int main()
- {
- Undergraduate s1;//新建一个本科生对象
- s1.set("Tom",21,178,60);
- s1.setGPA(3.75);
- cout <<s1.sname() <<endl;
- cout <<s1.sage() <<endl;
- cout <<s1.sheight() <<endl;
- cout <<s1.sweight() <<endl;
- cout <<s1.score() <<endl;
- cout <<s1.isAdult() <<endl;
- return 0;
- }
运行结果:
Constructing a student without parameter...
Tom
21
178
60
3.75
1
在使用继承之前,我们必须保证父类是已经定义好的。如果父类是虚无的、没有被定义的,那么子类也就没什么好继承的了。定义一个子类的语法格式为:
class 子类名:继承方式父类名;
根据程序17.3.1的运行结果,我们可以清楚地看到,学生类里面的公有和保护成员都已经被继承到本科生类。本科生类可以使用学生类的成员函数,也可以访问学生类的保护成员。而本科生类中定义的成员则是对学生类的补充,并且也能够被使用。
在程序17.3.1中,我们选择的继承方式是public。和成员的类型一样,除了public之外,继承方式还有protected和private。那么,这三种继承方式到底有什么区别呢?
public是公有继承,或称为类型继承。它主要体现的是概念的延伸和扩展,父类所有的公有、保护成员都将按部就班地继承到子类中。父类的公有成员在子类中依然是公有的,父类的保护成员在子类中依然是保护的。比如程序17.3.1中的学生类和本科生类就是这样的关系。
private是私有继承,或称为私有的实现继承。它主要体现的是父类成员的重用。父类所有的公有、保护成员继承到子类时,类型会发生改变。父类的公有成员在子类中变成了私有成员,父类的保护成员在子类中也变成了私有成员。这时,我们可以利用从父类继承而来的成员函数来实现子类的成员函数,并且不必担心外部直接访问父类的成员函数,破坏了子类的秩序。比如我们认为栈是一种特殊的链表,它只能从链表尾部添加或删除结点,栈的压栈和退栈功能可以方便地由链表类的成员函数实现。但是,如果外部还能直接访问从链表类继承而来的成员函数,那么就可以在栈的任何位置插入结点,栈就会被破坏。
protected是保护继承,或称为保护的实现继承。与私有继承类似,它也是体现父类成员的重用。只不过父类的公有成员和保护成员在子类中都变成了保护成员。因此,如果有一个孙类继承了子类,那么父类中的成员也将被继承,成为孙类的保护成员。
public、private和protected三种继承方式可以用下表描述。其中右下角的九个单元格表示各种父类成员在对应的继承方式下,成为子类成员后的性质。
在使用继承的时候,我们必须根据实际需要选择合适的继承方式。下面我们以栈继承链表为例,理解一下私有继承方式:(程序17.3.2)
//node.h
- #include <iostream>
- using namespace std;
- class Node
- {
- friend class Linklist;//链表类作为友元类
- friend class Stack;//栈类作为友元类
- public:
- Node();
- Node(Node &n);
- Node(int i,char c='0');
- Node(int i,char c,Node *p,Node *n);
- ~Node();
- private:
- int idata;
- char cdata;
- Node *prior;
- Node *next;
- };
- Node::Node()
- {
- cout <<"Node constructor is running..." <<endl;
- idata=0;
- cdata='0';
- prior=NULL;
- next=NULL;
- }
- Node::Node(int i,char c)
- {
- cout <<"Node constructor is running..." <<endl;
- idata=i;
- cdata=c;
- prior=NULL;
- next=NULL;
- }
- Node::Node(int i,char c,Node *p,Node *n)
- {
- cout <<"Node constructor is running..." <<endl;
- idata=i;
- cdata=c;
- prior=p;
- next=n;
- }
- Node::Node(Node &n)
- {
- idata=n.idata;
- cdata=n.cdata;
- prior=n.prior;
- next=n.next;
- }
- Node::~Node()
- {
- cout <<"Node destructor is running..." <<endl;
- }
//linklist.h
- #include "node.h"
- #include <iostream>
- using namespace std;
- class Linklist
- {
- public:
- Linklist(int i=0,char c='0');
- Linklist(Linklist &l);
- ~Linklist();
- bool Locate(int i);
- bool Locate(char c);
- bool Insert(int i=0,char c='0');
- bool Delete();
- void Show();
- void Destroy();
- protected://原私有成员改为保护成员,以便于Stack类继承
- Node head;
- Node * pcurrent;
- };
- Linklist::Linklist(int i,char c):head(i,c)
- {
- cout<<"Linklist constructor is running..."<<endl;
- pcurrent=&head;
- }
- Linklist::Linklist(Linklist &l):head(l.head)
- {
- cout<<"Linklist Deep cloner running..." <<endl;
- pcurrent=&head;
- Node * ptemp1=l.head.next;
- while(ptemp1!=NULL)
- {
- Node * ptemp2=new Node(ptemp1->idata,ptemp1->cdata,pcurrent,NULL);
- pcurrent->next=ptemp2;
- pcurrent=pcurrent->next;
- ptemp1=ptemp1->next;
- }
- }
- Linklist::~Linklist()
- {
- cout<<"Linklist destructor is running..."<<endl;
- Destroy();
- }
- bool Linklist::Locate(int i)
- {
- Node * ptemp=&head;
- while(ptemp!=NULL)
- {
- if(ptemp->idata==i)
- {
- pcurrent=ptemp;
- return true;
- }
- ptemp=ptemp->next;
- }
- return false;
- }
- bool Linklist::Locate(char c)
- {
- Node * ptemp=&head;
- while(ptemp!=NULL)
- {
- if(ptemp->cdata==c)
- {
- pcurrent=ptemp;
- return true;
- }
- ptemp=ptemp->next;
- }
- return false;
- }
- bool Linklist::Insert(int i,char c)
- {
- if(pcurrent!=NULL)
- {
- Node * temp=new Node(i,c,pcurrent,pcurrent->next);
- if (pcurrent->next!=NULL)
- {
- pcurrent->next->prior=temp;
- }
- pcurrent->next=temp;
- return true;
- }
- else
- {
- return false;
- }
- }
- bool Linklist::Delete()
- {
- if(pcurrent!=NULL && pcurrent!=&head)
- {
- Node * temp=pcurrent;
- if (temp->next!=NULL)
- {
- temp->next->prior=pcurrent->prior;
- }
- temp->prior->next=pcurrent->next;
- pcurrent=temp->prior;
- delete temp;
- return true;
- }
- else
- {
- return false;
- }
- }
- void Linklist::Show()
- {
- Node * ptemp=&head;
- while (ptemp!=NULL)
- {
- cout <<ptemp->idata <<'\t' <<ptemp->cdata <<endl;
- ptemp=ptemp->next;
- }
- }
- void Linklist::Destroy()
- {
- Node * ptemp1=head.next;
- while (ptemp1!=NULL)
- {
- Node * ptemp2=ptemp1->next;
- delete ptemp1;
- ptemp1=ptemp2;
- }
- head.next=NULL;
- }
//stack.h
- #include "linklist.h"
- class Stack:private Linklist//私有继承链表类
- {
- public:
- bool push(int i,char c);
- bool pop(int &i,char &c);
- void show();
- };
- bool Stack::push(int i,char c)
- {
- while (pcurrent->next!=NULL)
- pcurrent=pcurrent->next;
- return Insert(i,c);//用链表类的成员函数实现功能
- }
- bool Stack::pop(int &i,char &c)
- {
- while (pcurrent->next!=NULL)
- pcurrent=pcurrent->next;
- i=pcurrent->idata;
- c=pcurrent->cdata;
- return Delete();//用链表类的成员函数实现功能
- }
- void Stack::show()
- {
- Show();//用链表类的成员函数实现功能
- }
//main.cpp
- #include <iostream>
- #include "stack.h"
- int main()
- {
- Stack ss;
- int i,j;
- char c;
- for (j=0;j<3;j++)
- {
- cout <<"请输入一个数字和一个字母:" <<endl;
- cin >>i >>c;
- if (ss.push(i,c))
- {
- cout <<"压栈成功!" <<endl;
- }
- }
- ss.show();
- while (ss.pop(i,c))
- {
- cout <<"退栈数据为i=" <<i <<" c=" <<c <<endl;
- }
- return 0;
- }
运行结果:
Node constructor is running...
Linklist constructor is running...
请输入一个数字和一个字母:1 a
Node constructor is running...
压栈成功!
请输入一个数字和一个字母:
2 b
Node constructor is running...
压栈成功!
请输入一个数字和一个字母:
3 c
Node constructor is running...
压栈成功!
0 0
1 a
2 b
3 c
Node destructor is running...
退栈数据为i=3 c=c
Node destructor is running...
退栈数据为i=2 c=b
Node destructor is running...
退栈数据为i=1 c=a
Linklist destructor is running...
Node destructor is running...
我们看到,Stack类私有继承了Linklist类之后,利用Linklist的成员函数,方便地实现了压栈和退栈功能。