将派生类指针赋值给基类的指针

除了可以将派生类对象赋值给基类对象(对象变量之间的赋值),还可以将派生类指针赋值给基类指针(对象指针之间的赋值)。我们先来看一个多继承的例子,继承关系为:

#include <iostream>


using namespace std;

//基类

class A{



public:
    A(int a);
    int m_a;
public:
    void display();

};

A::A(int a):m_a(a) {}

void A::display() {
    cout<<"Class A:m_a="<<m_a<<endl;
}


//派生类

class B:public A{

public:
    B(int a1,int b);

public:
    void display();

protected:
    int m_b;
};

B::B(int a, int b):A(a),m_b(b) {}

void B::display() {

    cout<<"Class B:m_a="<<m_a<<", m_b"<<m_b<<endl;

}

class C{
public:
    C(int c);

public:
    void display();

protected:
    int m_c;

};

C::C(int c):m_c(c){}

void C::display() {

    cout<<"Class C: m_c="<<m_c<<endl;

}


class D:public B,public C{

public:
    D(int a,int b,int c,int d);

public:
    void display();

private:
    int m_d;

};

D::D(int a, int b, int c, int d) :B(a,b),C(c),m_d(d){}

void D::display() {

    cout<<"Class D: m_a="<<m_a<<", m_b="<<m_b<<", m_c="<<m_c<<", m_d="<<m_d<<endl;
}



int main(){
    A *pa =new A(1);
    B *pb =new B(2,20);
    C *pc =new C(3);
    D *pd =new D(4,40,400,4000);

    pa=pd;
    pa ->display();

    pb=pd;
    pb ->display();

    pc=pd;
    pd ->display();

    cout<<"-----------------------"<<endl;
    cout<<"pa="<<pa<<endl;
    cout<<"pb="<<pb<<endl;
    cout<<"pc="<<pc<<endl;
    cout<<"pd="<<pd<<endl;


    return 0;

}
Class A:m_a=4
Class B:m_a=4, m_b40
Class D: m_a=4, m_b=40, m_c=400, m_d=4000
-----------------------
pa=0x55a33ea0ced0
pb=0x55a33ea0ced0
pc=0x55a33ea0ced8
pd=0x55a33ea0ced0

本例中定义了多个对象指针,并尝试将派生类指针赋值给基类指针。与对象变量之间的赋值不同的是,对象指针之间的赋值并没有拷贝对象的成员,也没有修改对象本身的数据,仅仅是改变了指针的指向。

  • 通过基类指针访问派生类的成员
    请读者先关注第 68 行代码,我们将派生类指针 pd 赋值给了基类指针 pa,从运行结果可以看出,调用 display() 函数时虽然使用了派生类的成员变量,但是 display() 函数本身却是基类的。也就是说,将派生类指针赋值给基类指针时,通过基类指针只能使用派生类的成员变量,但不能使用派生类的成员函数,这看起来有点不伦不类,究竟是为什么呢?第 71、74 行代码也是类似的情况。

pa 本来是基类 A 的指针,现在指向了派生类 D 的对象,这使得隐式指针 this 发生了变化,也指向了 D 类的对象,所以最终在 display() 内部使用的是 D 类对象的成员变量,相信这一点不难理解。

编译器虽然通过指针的指向来访问成员变量,但是却不通过指针的指向来访问成员函数:编译器通过指针的类型来访问成员函数。对于 pa,它的类型是 A,不管它指向哪个对象,使用的都是 A 类的成员函数,具体原因已在《C++函数编译原理和成员函数的实现》中做了详细讲解。

概括起来说就是:编译器通过指针来访问成员变量,指针指向哪个对象就使用哪个对象的数据;编译器通过指针的类型来访问成员函数,指针属于哪个类的类型就使用哪个类的函数。

  1. 赋值后值不一致的情况
    本例中我们将最终派生类的指针 pd 分别赋值给了基类指针 pa、pb、pc,按理说它们的值应该相等,都指向同一块内存,但是运行结果却有力地反驳了这种推论,只有 pa、pb、pd 三个指针的值相等,pc 的值比它们都大。也就是说,执行pc = pd;语句后,pc 和 pd 的值并不相等。

将派生类引用赋值给基类引用

引用在本质上是通过指针的方式实现的,这一点已在《引用在本质上是什么,它和指针到底有什么区别》中进行了讲解,既然基类的指针可以指向派生类的对象,那么我们就有理由推断:基类的引用也可以指向派生类的对象,并且它的表现和指针是类似的。

修改上例中 main() 函数内部的代码,用引用取代指针:

int main(){
    D d(4, 40, 400, 4000);
   
    A &ra = d;
    B &rb = d;
    C &rc = d;
   
    ra.display();
    rb.display();
    rc.display();
    return 0;
}
Class A: m_a=4
Class B: m_a=4, m_b=40
Class C: m_c=400

ra、rb、rc 是基类的引用,它们都引用了派生类对象 d,并调用了 display() 函数,从运行结果可以发现,虽然使用了派生类对象的成员变量,但是却没有使用派生类的成员函数,这和指针的表现是一样的。

引用和指针的表现之所以如此类似,是因为引用和指针并没有本质上的区别,引用仅仅是对指针进行了简单封装,读者可以猛击《引用在本质上是什么,它和指针到底有什么区别》一文深入了解。

最后需要注意的是,向上转型后通过基类的对象、指针、引用只能访问从基类继承过去的成员(包括成员变量和成员函数),不能访问派生类新增的成员。

上一篇:运用了js的登陆页面(残)


下一篇:iOS项目开发实战——学会使用TableView列表控件(四)plist读取与Section显示