一、 基类与派生类的转换
3种继承方式(公用、保护、私有继承)中,公用派生类才是基类真正的子类型,它完整地继承了基类的功能。
不同类型数据之间在一定条件下可以进行类型的转换。基类与派生类对象之间是否也有赋值兼容的关系,可否进行类型间的转换?回答是可以的。基类与派生类对象之间有赋值兼容关系,由于派生类中包含从基类继承的成员,因此可以将派生类的值赋给基类对象,在用到基类对象的时候可以用其子类对象代替 。
注意:有的数据类型是不可转换的;有的转换是不可逆的。
1、派生类对象可以向基类对象赋值。
可以用子类(即公用派生类)对象对其基类对象赋值。
如:
A al; //定义基类A对象al
B bl; //定义类A的公用派生类B的对象bl
a1=b1; //用派生类B对象bl对基类对象al赋值
实际上,所谓赋值只是对数据成员赋值,对成员函数不存在赋值问题。
注意:
1、赋值后不能企图通过对象a1去访问派生类对象bl的成员,因为bl的成员与al的成员是不同的。假设ase是派生类B中增加的公用数据成员,分析下面的用法:
a1.age=23;//错误,al中不包含派生类中增加的成员
b1.age=21;//正确,b1中包含派生类中增加的成员
2、子类型关系是单向的、不可逆的。B是A的子类型,不能说A是B的子类型。只能用子类对象对其基类对象赋值,而不能用基类对象对其子类对象赋值,理由是显然的,因为基类对象不包含派生类的成员,无法对派生类的成员赋值。同理,同一基类的不同派生类对象之间也不能赋值。
2、派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化 。
如已定义了基类A对象a1,可以定义a1的引用:
A al; //定义基类A对象al
B bl; //定义公用派生类B对象bl
A&r=al;//定义基类A对象的引用变量r,并用a1对其初始化
引用r是al的别名,r和a1共享同一段存储单元。可以用子类对象初始化引用r,将上面最后一行改为:
A&r=bl;//定义基类A对象的引用变量r,并用派生类B对象b1对其初始化或者保留上面第3行“A&r=al;”,而对r重新赋值:
r=bl; //用派生类B对象bl对a1的引用变量r赋值
注意 :此时r并不是bl的别名,也不与bl共享同一段存储单元。它只是b1中基类部分的别名,r与bl中基类部分共享同一段存储单元,r与b1具有相同的起始地址。
3、如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。
如有一函数fun:
void fun(A&r) //形参是类A的对象的引用
{ cout<<r.num<<endl;}//输出该引用所代表的对象的数据成员num
函数的形参是类A的对象的引用变量,本来实参应该为A类的对象。由于子类对象与派生类对象赋值兼容,派生类对象能自动转换类型,在调用fun函数时可以用派生类B的对象bl作实参: fun(b1);
输出类B的对象bl的基类数据成员num的值。
在fun函数中只能输出派生类中基类成员的值 。
4、派生类对象的地址可以赋给指向基类对象的指针变量,也就是说,指向基类对象的指针变量也可以指向派生类对象。
例10 定义一个基类Student(学生),再定义Student类的公用派生类Graduate(研究生),用指向基类对象的指针输出数据。
本例主要是说明用指向基类对象的指针指向派生类对象,为了减少程序长度,在每个类中只设很少成员。学生类只设num(学号),name(名字)和score(成绩)3个数据成员,Graduate类只增加一个数据成员pay(工资) :
#include <string>
using namespace std;
class Student //声明Student类
{ public:
Student(int,string,float); //声明构造函数
void display(); //声明输出函数
private:
int num;
string name;
float score; };
Student::Student(int n,string nam,float s)//定义构造函数
{ num=n;
name=nam;
score=s; }
void Student::display() //定义输出函数
{ cout<<endl<<"num:"<<num<<endl;
cout<<"name:"<<name<<endl;
cout<<"score:"<<score<<endl; }
class Graduate:public Student //声明公用派生类Graduale
{ public:
Graduate(int,string,float,float);//声明构造函数
void display(); //声明输出函数
private:
float pay; }; //工资
void Graduate::display() //定义输山函数
{ Student::display(); //调用Student类的display函数
cout<<"pay="<<pay<<endl; }
Graduate::Graduate(int n,string nam,float s,float p):Student(n,nam,s),pay(p){}
//定义构造函数int main()
{ Student stud1(1001,"Li",87.5); //定义Student类对象stud1
Graduate grad1(2001,"Wang",98.5,563.5);//定义Graduate类对象grad1
Student *pt=&stud1; //定义指向Student类对象的指针并指向studl
pt->display(); //调用studl.display函数
pt=&grad1; //指针指向gradl
pt->display(); //调用gradl.display函数
return 0; }程序的输出结果:
num:1001
name:Li
score:87.5
num:2001
nume:wang
score:98.5
为什么没有输出pay的值?
通过本例可以看到: 用 指向基类对象的指针变量指向子类对象是合法的、安全的,不会出现编译上的错误。但在应用上却不能完全满足人们的希望,人们有时希望通过使用基类指针能够调 用基类和子类对象的成员。如果能做到这点,程序人员会感到方便。在下一章就要解决这个问题。办法是使用虚函数和多态性。