C++小知识点(一):基类指针指向派生类对象、派生类指针指向基类对象

基类指针指向派生类对象、派生类指针指向基类对象

以下代码运行后的输出结果是()

#include using namespace std;
class A
  {
public:
  void virtual print()
    {
      cout << "A" << endl;
    }
};
 
class B : public A
{
public:
    void virtual print()
    {
        cout << "B" << endl;
    }
};
int main()
{
    A* pA = new A();
    pA->print();
    B* pB = (B*)pA;
    pB->print();
    delete pA, pB;
    pA = new B();
    pA->print();
    pB = (B*)pA;
    pB->print();
}
using namespace std;
class A{
    public:void virtual print(){
        cout << "A" << endl;}
    };
class B : public A{
    public:void virtual print(){
        cout << "B" << endl;}
    };
    int main(){
        A* pA = new A();//基类指针指向基类对象
        pA->print();
        B* pB = (B*)pA;//派生类指针指向基类对象
        pB->print();
        delete pA, pB;
        pA = new B();//基类指针指向派生类对象
        pA->print();
        pB = (B*)pA;//派生类指针指向派生类对象
        pB->print();
}

在这里插入图片描述

解答:

  1. A* pA = new A()//基类指针指向基类对象,毫无疑问调用的是A类的print
    输出A

  2. B* pB = (B*)pA;//派生类指针指向基类对象,这里疑问会比较大。首先是为什么这里不会报错,为什么派生类指针指向基类对象可以成立?理论上指针的可访问范围一定大于对象的大小,会指向一些未知区域导致运行出错,但是要注意的是,这个题目里面B类不存在新增数据成员,所以不会出错。还有就是由于是基类对象,还没有发生虚函数掩盖
    输出A

  3. pA = new B();//基类指针指向派生类对象。基类指针之所以可以访问派生类对象是因为指针的可访问范围一定小于对象的大小,所以做完切割即可,即切割一些派生类中存在而基类中不存在的成员即可。此时派生类对象的vptr指向的虚函数表中,派生类虚函数已经把基类同名虚函数掩盖掉了,所以指向的肯定是派生类虚函数
    输出B

  4. pB = (B*)pA;//派生类指针指向派生类对象。注意此时pA指向的是派生类对象
    输出B

总结:(在分析完运行不出错的前提下)看指向的对象是啥类型,而不是看指针的类型

这题涉及到的知识点

虚函数预备常识

需要知道一些常识,一个类所有的函数都是再code代码区中唯一的存放一份。而数据成员则是每个对象存储一份,并按照声明顺序依次存放。
类A中有了虚函数就会再类的数据成员的最前面添加一个vfptr指针(void** vfptr),这个指针用来指向一个vtable表(一个函数指针数组)(一个类只有一个该表),该表存储着当前类的所有 虚函数 的地址。这样vfptr就成为了一个类似成员变量的存在。访问虚函数的时候通过vfptr间址找到vtable表,再间址进而找到要调用的函数。这样就在一定程度上摆脱了类型制约。

示例:

(People类是基类,student类是People类的派生类, Senior类是Student派生类)
在这里插入图片描述
(在虚函数表中,基类的虚函数在 vtable 中的索引(下标)是固定的,不会随着继承层次的增加而改变,派生类新增的虚函数放在 vtable 的最后。如果派生类有同名的虚函数遮蔽(覆盖)了基类的虚函数,那么将使用派生类的虚函数替换基类的虚函数,这样具有遮蔽关系的虚函数在 vtable 中只会出现一次。)

只要vptr的值不同,那么访问函数成员的时候使用的vtable表就不同,就可能访问到不同类的函数成员。B类对象中的vptr指向B类自己的vtable。
当B类继承A类的时候,因为A中有虚函数,编译器就自动的给B类添加vfprt指针和vtable表。也可以理解为B类继承来了A类中的那个vptr指针成员
当A类指针指向B类对象时,发生假切割。要知道这个过程只是切掉A类中没有的那些成员。(即当People类指针指向Student类对象时,切割掉m_score这个People类中没有的成员)
由于vptr是从A类中继承来的,所以这个量仍将保留。而对于vptr的值则不会改变,仍然指向B类的vtable表。所以访问F1函数的时候是通过B类的vtable表去寻址的,自然就是使用子类的函数(拿图中的情况举例,子类的Student::display()函数已经覆盖了People::display()函数,此时A类指针访问虚函数display()时也是访问到子类的Student::display()函数)。

当B类的指针指向A类的对象时(当B类存在新增数据成员时可能出错),同理。

而对于普通函数则受类型的制约,(因为没有vptr指针)使用哪个类的指针调用函数,那么所调用的就是那个累的函数。
总而言之,普通函数通过对象或指针的类型来找所调用的函数,而虚函数是通过一个指针来找到所要调用的函数的。

示例

#include <iostream.h>

class A
{
public:
virtual void F1()
{
cout<<"A1"<<endl;
}
void F2()
{
cout<<"A2"<<endl;
}

};
class B :public A
{
public:
void F1()
{
cout<<"B1"<<endl;
}
void F2()
{
cout<<"B2"<<endl;
}

};
void main(){

A *pa;
B *pb;
B TB;
A TA;

pa = &TB;//基类指针指向派生类对象
pa->F1();
pa->F2();

pb =(B *) &TA;//派生类指针指向基类对象

pb->F1();
pb->F2();

}
输出:
B1
A2
A1
B2

基类指针指向派生类对象:如果基类声明的不是虚函数就调用基类的,如果是虚函数并在派生类中实现,就调用派生类的函数;
派生类指针指向基类对象:如果基类声明的是虚函数就调用基类的,如果不是虚函数并在派生类中实现,就调用派生类的函数.

理由见上

参考

【1】C++ 派生类指针指向基类对象

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值