【C/C++】基类和派生类的转换

基类与派生类对象之间有赋值兼容关系,由于派生类中包含从基类继承的成员,因此可以将派生类的值赋给基类对象,在用到基类对象的时候可以用其子类对象代替。具体表现在以下几个方面。


一、派生类对象可以向基类对象赋值

可以用子类对象对基类对象赋值。如

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中包含派生类中增加的成员

应当注意,子类型关系是单向的、不可逆的。B是A的子类型,不能说A是B的子类型。只能用子类对象对其基类对象赋值,而不能用基类对象对其子类对象赋值,理由是显然的,因为基类对象不包含派生类的成员,无法对派生类的成员赋值。同理,同一基类的不同派生类对象之间也不能赋值。



二、派生类对象可以替代基类对象向基类对象的引用进行赋值或初始化

如已定义了基类A对象a1,可以定义a1的引用变量:

A a1; 		// 定义基类A对象a1
B b1; 		// 定义公用派生类B对象b1
A& r= a1; 	// 定义基类A对象的引用变量r,并用a1对其初始化。

这时,引用变量r是a1的别名,r和a1共享同一段存储单元。


也可以用子类对象初始化引用变量r,将上面最后一行改为

A& r= b1;  	// 定义基类A对象的引用变量r,并用派生类B对象b1对其初始化

或者保留上面第3行“A& r=a1;”,而对r重新赋值:

r=b1;  		// 用派生类B对象b1,对a1的引用变量r赋值。

注意,此时r并不是b1的别名,也不与b1共享同一段存储单元。它只是b1中基类部分的别名,r与b1中基类部分共享同一段存储单元,r与b1具有相同的起始地址。

在这里插入图片描述



三、如果函数的参数是基类对象或基类对象的引用,相应的实参可以用子类对象。

如有一函数fun:

void fun(A& r)  //形参是类A的对象的引用变量
{
    cout<<r.num<<endl;
}  //输出该引用变量的数据成员num

函数的形参是类A的对象的引用变量,本来实参应该为A类的对象。由于子类对象与派生类对象赋值兼容,派生类对象能自动转换类型,在调用fun函数时可以用派生类B的对象b1作实参:

fun(b1);

输出b1中的基类数据成员num的值

与前相同,在fun函数中只能输出派生类中基类成员的值。



四、派生类对象的地址可以赋给指向基类对象的指针变量,也就是说,指向基类对象的指针变量也可以指向派生类对象。

定义一个基类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

前3行是学生stud1的数据,后3行是研究生grad1的数据,并没有输出pay的值。

问题在于pt是指向Student类对象的指针变量,即使让它指向了grad1,但实际上pt指向的是grad1中从基类继承的部分

通过指向基类对象的指针,只能访问派生类中的基类成员,而不能访问派生类增加的成员。所以pt->display()调用的不是派生类Graduate对象所增加的display函数,而是基类的display函数,所以只输出研究生grad1的num,name,score3个数据。

如果想通过指针输出研究生grad1的pay,可以另设一个指向派生类对象的指针变量ptr,使它指向grad1,然后用ptr->display()调用派生类对象的display函数。但这不大方便。


通过本例可以看到,用指向基类对象的指针变量指向子类对象是合法的、安全的,不会出现编译上的错误。但在应用上却不能完全满足人们的希望,人们有时希望通过使用基类指针能够调用基类和子类对象的成员。如果能做到这点,程序人员会感到方便。后续章节将会解决这个问题。办法是使用虚函数和多态性。



五、精简例子:

class Animal
{
public:
	void speak()
	{
		cout << "动物在说话" << endl;
	}
};

class Cat :public Animal
{
public:
	void speak()
	{
		cout << "小猫在说话" << endl;
	}
};


void DoSpeak(Animal & animal)
{
	animal.speak();
}

void test01()
{
	Cat cat;
	DoSpeak(cat);
}


int main() {
	test01();
	system("pause");
	return 0;
}

程序输出:

动物在说话

而不是小猫在说话。

派生类对象在对父类对象赋值时,其新增的成员会被舍弃。赋值之后的父类对象,也只能调用基类的数据成员。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值