前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++:43---派生类向基类转换、静态/动态的类变量

C++:43---派生类向基类转换、静态/动态的类变量

作者头像
用户3479834
发布2021-02-03 14:49:58
1.6K0
发布2021-02-03 14:49:58
举报
文章被收录于专栏:游戏开发司机游戏开发司机

一、继承中类的类型转换规则

  • 我们普通的编程规则规定,如果我们想把引用或指针绑定到一个对象上,则引用或指针的类型必须与所绑定的对象的类型一致或者对象的类型含有一种可接受的const类型转换规则。但是继承关系中的类比较例外,其规则如下:
  • ①我们可以将基类的指针或引用绑定到派生对象上
代码语言:javascript
复制
#include <iostream>class A {};class B:public A{};int main(){	A *a;	B b;	a = &b;	return 0;}
  • ②即使不是指针/引用类型,我们也可以将派生类转换为基类
代码语言:javascript
复制
#include <iostream>class A {};class B:public A{};int main(){	A a;	B b;	a = b;	return 0;}
  • ②不能将基类对象绑定到派生类的指针/引用上
代码语言:javascript
复制
A a;B *b;b = &a; //程序错误,不能将基类对象转换为派生类对象

二、转换的本质

  • 派生类可以转换为基类的本质是:
    • ①为什么派生类可以转换为基类:派生类从基类而来,因此派生类中包含了基类的方法和成员。此时基类可以通过指针或引用指向派生类(相当于将派生类从基类中继承的那部分方法和成员绑定到基类上了,相当于派生类被截断了),然后基类就可以将派生类假装是一个基类对象来使用(调用其中的成员/方法)
    • ②为什么基类不能转换为派生类:因为派生类可能会定义自己新的成员/方法,但是这些成员/方法是基类中所没有的。如果将一个基类对象绑定到派生类的指针/引用上,此时派生类通过指针/引用访问自己新定义的成员/方法时,发现找不到(因此不能将基类转换为派生类)
  • 例如:下面B继承于A,子类继承于父类,同时为父类的成员开辟了空间。将子类对象赋值给父类对象,相当于将子类中的父类成员变量赋值给父类

三、继承方式对类型转换的影响

遵循下面3个规则:

假设B继承于A

  • ①只有当B公有地继承A时,用户代码才能使用派生类向基类转换;如果B是受保护的/私有的继承于A,则不能使用派生类向基类转换
    • 因为保护或者私有继承,基类的成员/方法在子类中都变为保护或者私有的了,所以转换之后基类也无法通过指针访问
代码语言:javascript
复制
class A{};
class B :public A{};
class C :protected A{};
int main()
{
A *a;
B b;
C c;
a = &b; //正确
a = &c; //错误
return 0;
}
  • ②B不论以什么方式继承于A,B的成员函数和友元中可以将派生类对象向基类转换
代码语言:javascript
复制
class A{};
class B :protected A
{
void func() {
A *a;
B b;
a = &b;   //正确,成员函数内可以
a = this; //正确,成员函数内可以
}
friend void func2(); //友元函数
};
void func2()
{
A *a;
B b;
a = &b; //正确,友元函数内可以
}
int main()
{
A *a;
B b;
a = &b; //错误,因为为保护继承
return 0;
}
  • ③如果B继承于A的方式是公有的或者受保护的,则B的派生类的成员和友元可以使用B向A的类型转换;如果B继承于A的方式是私有的,则不能
代码语言:javascript
复制
class A{};
class B :protected A{};
class C :public B {
void func1() {
A *a;
B b;
a = &b;   //正确
a = this; //正确
}
};

四、一种出错的情景

  • 下面案例我们先将派生类转换为基类,然后再将基类转换为派生类,这样是错的
代码语言:javascript
复制
//假设B公有继承于A
A *a;
B b;
a = &b;   //将派生类转换为基类,正确
B *p = a; //将基类再转换为派生类,错误

五、类静态类型/类动态类型

  • 在上面我们介绍过,基类的指针或引用可以指向于基类对象也可以指向于派生类对象,因此一个类可以分为是动态类型的还是静态类型的:
    • 静态类型的类变量:在编译时就已经知道是什么类型的了
    • 动态类型的类变量:自己所指的类型不明确,直到运行时才知道
  • 如果表达式既不是引用也不是指针,那么其就没有静态类型和动态类型的概念,因为其只能与自己类型一致的对象绑定到一起

演示案例

  • 当我们使用基类的引用(或指针)时,我们并不清楚该引用(或指针)所绑定的对象的真实类型,该对象可能是基类的对象,也可能是派生类的对象。只有在程序运行的时候我们才知道所绑定的对象的真实类型
代码语言:javascript
复制
代码语言:javascript
复制
class A {};
class B:public A{};
int main()
{
A a;  //静态类型
B b;  //静态类型
A *p; //动态类型
p = &a; //p指向了a
p = &b; //p又指向了b
return 0;
}
class A {
protected:
int a;
public:
void setA(int num) { a = num; }
void cout_getA() { cout << "A" << a << endl; }
};
class B :public A {
public:
void setA(int num) { a = num; }
void cout_getA() { cout << "B" << a << endl; }
};
int main()
{
A a;
B b;
a.setA(10);
b.setA(20);
A *p1, *p2;
p1 = &a;
p2 = &b;
p1->cout_getA();
p2->cout_getA();
return 0;
}
代码语言:javascript
复制
  • 结果解析:
    • A 10:这个比较简单,因为a的类型为A,且指针也为A,因此调用A的getA()函数
    • A 20:虽然p2指针指向的类类型为B,但是访问规则只与指针/引用的类类型有关,而与指针/引用指向的类型无关。因此b已经被视为一个A对象来看了。此处p2指针的类型为A,因此调用A的getA()函数。又因为b对象使用setA()函数将整个继承体系中的a改为了20,因此打印出来的a为20

六、转换之后数据与方法的访问规则

  • 当我们使用一个指针或引用访问类数据与方法的时候,实际上取决于这个指针或引用的类类型,而不是指针所指向或引用的类型(这是在编译阶段决定的)
  • 当然,如果是普通类型下将派生类转换为子类的话,那么调用的时候也取决于左边的类型
  • 转换之后,基类只能通过派生类访问属于自己(基类)的那一部分,而不能访问属于派生类的数据成员(见下面演示案例③)
  • 虚函数的调用是个例外:虚函数的调用是取决于指针或引用所指向的类型,并且多态只能发生在指针/引用指向于派生类的情况下,普通类型之间的转换不会发生。

演示案例①

代码语言:javascript
复制
class A
{
public:
int a = 10;
void show1()const { cout << "A:show1\n"; }
virtual void show2()const { cout << "A:show2\n"; }
};
class B :public A
{
public:
int a = 15;
void show1()const { cout << "B:show1\n"; }
virtual void show2()const { cout << "B:show2\n"; }
};
int main()
{
A a;
B b;
A *pa = &b;
cout << pa->a << endl;
pa->show1();
pa->show2();
return 0;
}
  • 结果分析:
    • 打印10:因为B继承于A,将b转换为A类对象的指针,访问是跟指针的类型有关,而与指针所指的类对象类型无关,因此访问A的a,打印10
    • 打印“A:show1”:因为show1()不是虚函数,所以访问时跟指针的类型有关,此处指针的类型为A,因此访问A的show1函数
    • 打印“B:show2”:因为show2()函数为虚函数,所以根据虚函数的性质,使用基类的指针访问子类时,访问虚函数跟指针所指的类对象类型有关,此处指针所指的类类型为B,因此访问B的show2()函数

演示案例②

  • 我们修改演示案例①,上面是将基类的指针指向于派生类。但是这个演示案例中是将派生类对象赋值给基类对象(而不是指针形式)
代码语言:javascript
复制
class A
{
public:
int a = 10;
void show1()const { cout << "A:show1\n"; }
virtual void show2()const { cout << "A:show2\n"; }
};
class B :public A
{
public:
int a = 15;
void show1()const { cout << "B:show1\n"; }
virtual void show2()const { cout << "B:show2\n"; }
};
int main()
{
A pa;
B pb;
pa = pb;
cout << pa.a << endl;
pa.show1();
pa.show2();
return 0;
}
  • 结果分析:
    • 打印10:因为B继承于A,将B赋值给A,相当于把B中属于A的内容赋值给A,因此访问到A中的a,为10
    • 打印“A:show1”:因为show1()不是虚函数,所以访问时跟左边的类型有关,此时为A,就访问A中的show1()函数
    • 打印“A:show2”:虽然show2()函数为虚函数,但是多态只有发生在基类指针/引用指向于派生类的情况下才会发生,此处基类是普通对象,而不是引用/指针,因此访问的还是A中的show2()函数

演示案例③

代码语言:javascript
复制
class A {};
class B :public A {
public:
int num = 10;
};
int main()
{
B b;

A a = b; //基类指向与派生类
a.num;   //错误,num属于B,而A内不含有此成员
B *pa = new B;
A *pb = new B; //基类指向与派生类
pa->num;       //正确
pb->num;       //错误,num属于B,而A内不含有此成员
return 0;
}

七、其他情境下的类型转换

  • 当我们用一个派生类对象为一个基类对象初始化或赋值时,只有该派生类对象中的基类部分会被拷贝、移动或赋值,它的派生类部分会被忽略掉(截断了)

拷贝时的类型转换

代码语言:javascript
复制
class A {
public:
int a;
public:
A(int num) :a(num) {};
A(A const &other); //拷贝构造
};
A::A(A const &other)
{
this->a = other.a;
}
class B :public A {
public:
int b;
public:
B(int num) :A(num) {};
};
int main()
{
A a1(10); //定义A类对象
B b(20);  //定义B类对象
A a2(a1); //拷贝构造,使用与A类类型a1对象
A a3(b);  //拷贝构造,使用B类类型的b对象,b对象的内容被截断
return 0;
}

赋值运算符时的类型转换

代码语言:javascript
复制
#include <iostream>
using namespace::std;
class A {
public:
int a;
public:
A(int num) :a(num) {};
public:
A& operator=(A const& other); //赋值运算符
};
A& A::operator=(A const& other)
{
this->a = other.a;
}
class B:public A{
public:
int b;
public:
B(int num) :A(num) {};
};
int main()
{
A a1(10); //定义A类对象
B b(20);  //定义B类对象
A a2(30);
a1 = b;   //将b对象赋值给a1,b对象的内容被截断
a1 = a2;  //将a2对象赋值给a1
return 0;
}
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-12-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 游戏开发司机 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、继承中类的类型转换规则
  • 二、转换的本质
  • 三、继承方式对类型转换的影响
    • 遵循下面3个规则:
    • 四、一种出错的情景
    • 五、类静态类型/类动态类型
      • 演示案例
      • 六、转换之后数据与方法的访问规则
        • 演示案例①
          • 演示案例②
            • 演示案例③
            • 七、其他情境下的类型转换
              • 拷贝时的类型转换
                • 赋值运算符时的类型转换
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档