多态的C++实现

作者: 岁与禾 | 来源:发表于2017-09-12 14:24 被阅读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 纯虚函数和虚基类(抽象类)

  • 纯虚函数是一个在基类中说明的虚函数,在基类中没有定义,要求任何派生类都定义自己的版本
  • 纯虚函数为各派生类提供了一个公共界面(接口的封装和设计、软件的模块功能划分)
  • 纯虚函数的声明virtual void print() = 0;
  • 一个具有纯虚函数的基类成为抽象类

注意:

  • 抽象类不能建立对象,但可以声明抽象类的指针
  • 抽象类不能作为返回来兴,也不能作为参数类型
  • 抽象类可以声明抽象类的引用

相关文章

  • 深刻剖析之c++博客文章

    三大特性 封装、继承、多态 多态 C++ 虚函数表解析C++多态的实现原理 介绍了类的多态(虚函数和动态/迟绑定)...

  • Swift 多态实现探究

    多态 父类指针指向子类对象 Swift 中多态的实现类似于 c++ 中的虚表 OC 多态实现利用的是 Runtim...

  • 实现golang语言的多态

    如何实现golang语言的多态? C++里面有多态是其三大特性之一,那么golang里面的多态我们该怎么实现? g...

  • 多态的C++实现

    多态的C++实现 1 多态的原理 什么是多态?多态是面向对象的特性之一,只用父类指针指向子类的对象。 1.1 多态...

  • C++ 多态的实现和原理

    多态的实现虚函数原理 一、在编译期间实现多态 多态是指在不同的条件下表现出不同的状态,C++中通过重载函数的方法可...

  • 技能

    C++ C++特性 C++11 多态和继承 构造函数 析构函数 手写代码实现string类 手写代码实现智能指针 ...

  • 语法

    virtual 1.virtual声明的函数实现多态就是通用的多态实现 2.纯虚函数C++的纯虚函数用于表示一个类...

  • C++编译期多态和运行期多态

    C++多态有多种实现方式,在面对对象编程时,采用的是运行期多态,也称动态多态。在泛型编程中,多态基于模板的具现化与...

  • pwnable.kr之uaf && c++虚函数

    c++的逆向还是要熟悉下。 一、关于c++虚函数 虚函数使得c++能够实现多态,每个类有一个虚表,每个对象在实现的...

  • Go interface详解

      如Go method中提及,Golang没有明确支持多态,但是通过其他手段可以实现类似C++中的多态特性,即本...

网友评论

    本文标题:多态的C++实现

    本文链接:https://www.haomeiwen.com/subject/fjycsxtx.html