进阶的iOSer程序员

多态的C++实现

2017-09-12  本文已影响31人  岁与禾

多态的C++实现

1 多态的原理

什么是多态?多态是面向对象的特性之一,只用父类指针指向子类的对象。

1.1 多态实现的三个条件

如下代码,如果并没有增加virtual关键字,并不会发生多态现象。


#include <iostream>
using namespace std;

class Parent {
    
    
public:
    
    Parent(int a, int b)
    {
        this->p1 = a;
        this->p2 = b;
    }
    
    //多态的重点:需要加virtual关键字,否则不会发生多态
    virtual void add()
    {
        cout<<"Parent: p1 + p2 "<<endl;
    }
    
private:
    int p1;
    int p2;
    
};

class Child :public Parent {
    
    
public:
    
    Child(int a, int b, int c, int d):Parent(c,d)
    {
        this->c1 = a;
        this->c2 = b;
    }
    
    void add()
    {
        cout<<"Child: c1 + c2 "<<endl;
    }
    
private:
    int c1;
    int c2;
    
    
};


void play(Parent *base)
{
    base->add();
}


int main(int argc, const char * argv[]) {
    
    Child c(1,2,3,4);
    
    Parent *pP = &c;
    Child  *pC = &c;
    
    play(pP);
    play(pC);
    
    return 0;
}


1.2 多态的实现原理

上面的例子中,在基类的函数增加了virtual关键字后,编译器会自动为子类对应的方法也会增加virtual关键字

1.2.1 虚函数表
1.2.2 vptr指针

如果存在virtual关键字,编译器在运行时的时候(动态联编)会自动为当前对象增加vptr指针,这个vptr指针指向了当前类的虚函数表。

判断vptr指针是否存在

 //如果vptr指针存在,则对象sizeof()之后,大小会发生变化
 #include <iostream>
 using namespace std;

 class Parent {
    
    
 public:
    
    Parent(int a, int b)
    {
        this->p1 = a;
        this->p2 = b;
    }
    
    virtual void add()
    {
        cout<<"Parent: p1 + p2 "<<endl;
    }
    
 private:
    int p1;
    int p2;
    
 };
 
 int main(int argc, const char * argv[]) {
    
    Parent p(1,2);
    //可以分别测试下,添加virtual关键字和不加的内存空间大小
    cout<<sizeof(p)<<endl;
 }

1.2.2 多态实现原理
  1. 编译器发现存在virtual关键字,则会为类生成一张虚函数表
  2. 编译器会在动态联编(运行时)时为对象添加一个vptr指针
  3. 有父类对象指向子类对象存在,且执行了父类方法
  4. 如果当前对象有vptr指针存在,则会通过vptr指针找到对应的虚函数表,在虚函数表中查找对应的方法地址,执行。

2 vptr指针的分步初始化

2.1 父类构造函数中调用父类的方法,会产生多态吗?

如果再父类的构造函数中,调用父类的虚函数。那么在子类对象初始化的时候,会不会产生多态现象呢,还是仍然调用父类的虚函数呢?

答案是:否。不会产生多态。因为vptr指针是分步初始化的

#include <iostream>
using namespace std;

class Parent {
    
    
public:
    
    Parent(int a, int b)
    {
        this->p1 = a;
        this->p2 = b;
        
        this->add();    //调用父类的虚函数,这个地方不会产生多态现象。
    }
    
    virtual void add()
    {
        cout<<"Parent: p1 + p2 "<<endl;
    }
    
private:
    int p1;
    int p2;
    
};

class Child :public Parent {
    
    
public:
    
    Child(int a, int b, int c, int d):Parent(c,d)
    {
        this->c1 = a;
        this->c2 = b;
    }
    
    virtual void add()
    {
        cout<<"Child: c1 + c2 "<<endl;
    }
    
private:
    int c1;
    int c2;
    
    
};


void play(Parent *base)
{
    base->add();
}


int main(int argc, const char * argv[]) {
    
    //虽然会调用父类的构造函数,但是仍然是调用父类的add方法
    Child c(1,2,3,4);
    
    
    return 0;
}

2.2 vptr指针的分步初始化

那么vptr指针是如何初始化的呢?

  1. 编译器编译时,基类会产生虚函数表,子类也会产生虚函数表
  2. 当初始化子类对象的时候,先调用父类的构造函数,同时将子类对象的vptr指针指向父类的虚函数表
  3. 接下来,调用自己的构造函数,同时将vptr指针指向子类的虚函数表

上面的例子中,当调用父类构造函数时,当前vptr指针仍然指向父类的虚函数表,调用的仍然是父类的add方法,不会产生多态。

3 多态带来的问题

可以使用父类指针指向子类的对象,但是指针的类型却改变了,最指针进行++或者--操作时,可能带来意向不到的后果。

#include <iostream>
using namespace std;

class Parent {
    
    
public:
    
    Parent(int a, int b)
    {
        this->p1 = a;
        this->p2 = b;
    }
    
    virtual void print()
    {
        cout<<"p1 = "<<p1<<"; p2 = "<<p2<<endl;
    }

    
private:
    int p1;
    int p2;
    
};

class Child :public Parent {
    
    
public:
    
    Child(int a, int b, int c, int d):Parent(c,d)
    {
        this->c1 = a;
        this->c2 = b;
    }
    
    virtual void print()
    {
        cout<<"c1 = "<<c1<<"; c2 = "<<c2<<endl;
    }
    
private:
    int c1;
    int c2;
    
    
};


int main(int argc, const char * argv[]) {
    
    
    
    Child array[] = {Child(1,2,3,4),Child(5,6,7,8),Child(9,10,11,12),Child(13,14,15,17),Child(18,19,20,21)};
    
    Child *c = array;
    c->print();
    c++;
    c->print();
    
    Parent *p = array;
    p->print(); //仍然打印的是子类的(没问题)
    p++;        //执行++操作
    p->print(); //执行之后,崩溃(因为p++和c++的步长不一样导致错误)
    
    
    return 0;
}

4 纯虚函数和虚基类(抽象类)

注意:

上一篇 下一篇

猜你喜欢

热点阅读