多态原理

1、多态原理的实现
我们知道多态实现的三个条件:1.继承,2.虚函数重写,3.父类指针或引用指向子类对象
当类中有一个函数是虚函数,会在类中增加一个 虚函数指针,虚函数指针会指向一个虚函数表; 虚函数表是一个存储类成员函数指针的数据结构; 虚函数表是由编译器自动生成与维护的; virtual成员函数会被编译器放入虚函数表中; 存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr)

void func(){}
virtual void func ()  // print (A * const this)
调用:p->func();  // p->vfptr->func()

调用虚函数过程
最后在调用的时候,编译器就会根据func是否为虚函数来进行相应的解释: 1.如果不是虚函数:编译器可直接确定被调用的成员函数(也叫做静态联编) 2.如果是虚函数:编译器根据对象p的VPTR指针所指向的虚函数表中来查找func函数并调用(也叫做动态联编或者迟邦定),也可以说是通过该指针去取 这块地址上的 虚函数指针,通过虚函数指针 去 虚函数表中 去取同名函数
注意:通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数,而普通成员函数是在编译器时就确定了调用谁,因此虚函数的效率要低很多,所以不是虚函数越多越好。C++编译器,执行run函数,不需要区分是子类对象还是父类对象

2、构造函数中调用虚函数能否实现
构造的顺序是先构造父类、再构造子类
当调用父类的构造函数的时候,虚函数指针vfptr 指向父类的虚函数表
当父类构造完,调用子类的构造函数的时候,虚函数指针 vfptr 指向子类的虚函数表
结论:构造函数中无法实现多态

class A
{
public:
    A()
    {
        // 在构造函数中调用虚函数是不能实现多态的
        // 虚函数指针 是分步初始化,一开始指向父类的虚函数表,父类初始化完了以后 指向自己的虚函数表
        print();
    }

    virtual void print()
    {
        printf ("AAAAAAAAAAAAAAAAAAAAA\n");
    }
public:
    int a;
};

3、不要用父类指针指向子类数组
指针也是一种数据类型,C++类对象的指针p++/–,仍然可用。
指针运算是按照指针所指的类型进行的。
父类p++与子类p++步长不同;不要混搭,不要用父类指针++方式操作子类对象数组
下面这个案例可以看出:

class A
{
public:
    A(int a)
    {
        this->a = a;
    }

    virtual void print()
    {
        printf ("a = %d\n", a);
    }
public:
    int a;
};
class B:public A
{
public:
    B(int a, int b): A(a)
    {
        this->b = b;
    }

    virtual void print()
    {
        printf ("a = %d, b = %d\n",a, b);
    }

public:
    int b;
};


int main()
{
    B b[5] = {B(1,2), B(3,4), B(5,6), B(7,8), B(9,10)};

    B *pb = b;

    A *pa = b;

    // 一般来讲  基类 和 派生类 的指针步长是不一样的, 除非派生类没有增加任何成员
    // pa + 1 =  pa + sizeof(A)   pb + 1 = pb + sizeof(B)
    // 不要基类的指针 操作派生类的数组
    for (int i = 0; i < 5; i++)
    {
        // pb[i].print();

        // pa[i] ==> pa + i ==> (int)pa + sizeof(A) ==> (int)pa + 8
        // sizeof(B)  =  12
        pa[i].print(); 


        // long * p = 0x100000  ==> p + 1 = 0x10004  ==>  (long)p + 1 = 0x10001
    }

    return 0;
}

你可能感兴趣的:(c++)