声明:本博文内容仅供交流学习使用,部分代码来自网络
关于赋值兼容规则相信不少和我一样的初学者应该是一头雾水吧,下面我就写出个人的看法
开始之前我先要说一个概念“指类”,相信应该没有那个书本上这样说吧,这个概念在下面的第3点中会用到,下面举例说明
int *p; 在这里p是一个指针,它指向一个int型数据的4字节空间,我自己看来一个指针声明的时候已经说明了它指向的对象是什么多大空间(个人观点请大家指正),切记
Ok言归正传,赋值兼容规则的本质是——在需要基类对象的任何地方都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员,而且所有成员的访问控制属性也和基类完全相同。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。
赋值兼容规则中所指的替代包括以下的情况:
1·派生类的对象可以赋值给基类对象。如
A a1; //定义基类A对象a1
B b1; //定义类A的公用派生类B的对象b1
a1=b1; //用派生类B对象b1对基类对象a1赋值
在赋值时舍弃派生类自己的成员,只进行数据成员的赋值。
实际上,所谓赋值只是对数据成员赋值,对成员函数不存在赋值问题,内存中数据成员和成员函数是分开的。
请注意: 赋值后不能企图通过对象a1去访问派生类对象b1的成员,因为b1的成员与a1的成员是不同的。
假设age是派生类B中增加的公用数据成员,分析下面的用法:
a1.age=23;//错误,a1中不包含派生类中增加的成员
b1.age=21; //正确,b1中包含派生类中增加的成员
只能用子类对象对其基类对象赋值,而不能用基类对象对其子类对象赋值,理由是显然的,两种对象的大小是不同的,因为基类对象不包含派生类的成员,
无法对派生类的成员赋值。同理,同一基类的不同派生类对象之间也不能赋值。
2·派生类的对象可以初始化基类的引用。如
已定义了基类A对象a1,可以定义a1的引用变量:
A a1; //定义基类A对象a1
B b1; //定义公用派生类B对象b1
A &r=a1; //定义基类A对象的引用变量r(A的别名是r),并用a1对其初始化
这时,r和a1共享同一段存储单元。也可以用派生类对象初始化引用变量r,将上面最后一行改为
A& r=b1;//定义基类A对象的引用变量r,并用派生类B对象b1//对其初始化
注意: 此时r并不是b1的别名,也不与b1共享同一段存储单元。它只是b1中基类部分的别名(个人认为
这里的r定义为A类的引用,所以它的有效范围就只有A类那么大),r与b1中基类部分共享同一段存储单元,r与b1具有相同的起始地址。
如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。如有一函数
fun: void fun(A& r)//形参是类A的对象的引用变量
{
cout<<r.num<<endl;
} //输出该引用变量的数据成员num
函数的形参是类A的对象的引用变量,本来实参应该为A类的对象。由于子类对象与派生类对象赋值兼容,派生类对象能自动转换类型,
在调用fun函数时可以用派生类B的对象b1作实参: fun(b1); 输出类B的对象b1的基类数据成员num的值。与前相同,在fun函数中只能输
出派生类中基类成员的值。
3·派生类对象的地址可以赋给指向基类的指针。也就是说,指向基类对象的指针变量也可以指向派生类对象。
例定义一个基类Student(学生),再定义Student类的公用派生类Graduate(研究生), 用指向基类对象的指针输出数据。本例主要是说明
用指向基类对象的指针指向派生类对象,为了减少程序长度,在每个类中只设很少成员。学生类只设num(学号),name(名字)和score(成绩)
3个数据成员,Graduate类只增加一个数据成员pay(工资)。
程序如下(以下代码来自网络):
#include <iostream>
#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//声明公用派生类Graduate
{
public :
Graduate(int, string ,float ,float );//声明构造函数
void display( );//声明输出函数
private :
float pay;//工资
};
Graduate::Graduate(int n, string nam,float s,float p):Student(n,nam,s),pay(p){ }//定义构造函数
void Graduate::display() //定义输出函数
{
Student::display(); //调用Student类的display函数
cout<<″pay=″<<pay<<endl;
}
int main()
{
Student stud1(1001,″Li″,87.5); //定义Student类对象stud1
Graduate grad1(2001,″Wang″,98.5,563.5); //定义Graduate类对象grad1
Student *pt=&stud1;//定义指向Student类对象的指针并指向stud1
pt->display( ); //调用stud1.display函数
pt=&grad1; //指针指向grad1
pt->display( ); //调用grad1.display函数
}
很多读者会认为: 在派生类中有两个同名的display成员函数,根据同名覆盖的规则,被调用的应当是派生类Graduate对象的display函数,
在执行Graduate::display函数过程中调用Student::display函数,输出num,name,score,然后再输出pay的值。
事实上这种推论是错误的,先看看程序的输出结果:
num:1001
name:Li
score:87.5
num:2001
name:wang
score:98.5
并没有输出pay的值。
问题在于pt是指向Student类对象的指针变量,它的指类是Student类,即使让它指向了grad1,但实际上pt指向的是grad1中从基类继承的
部分(它指向的空间只能是基类中数据成员那么大的空间)。通过指向基类对象的指针,只能访问派生类中的基类成员,而不能访问派生
类增加的成员。所以pt->display()调用的不是派生类Graduate对象所增加的display函数,而是基类的display函数,所以只输出研究生grad1
的num,name,score3个数据。