当前位置:首页 > 资讯 > info5 > 正文

c++多态

发表于: 2016-10-31   作者:baidu_27435045   来源:转载   浏览:
摘要: 运行环境:win10+vs2015,数据以小端字节序存储多态,顾名思义“多种状态”。首先,要想知道多态的运行了机制,首先要了解一下对象的类型。对象的类型:静态类型:对象声明时的类型,是在编译时确定的动态类型:目前所指向对象的类型,是在运行时确定的举个例子:classBase{}; classDeri1:publicBase{}; classDeri2:publicBase{}; intmain(

运行环境:win10+vs2015,数据以小端字节序存储

多态,顾名思义“多种状态”。
首先,要想知道多态的运行了机制,首先要了解一下对象的类型。

对象的类型:

  • 静态类型:对象声明时的类型,是在编译时确定的
  • 动态类型:目前所指向对象的类型,是在运行时确定的

举个例子:

class Base{};
class Deri1:public Base{};
class Deri2:public Base{};

int main(){
    Deri1* p1 = new Deri1;//p1的静态类型是Deri1*,动态类型是Deri1*
    Base* pBase = p1;     //pBase的静态类型是Base*,动态类型是Deri1*
    Deri2* p2 = new Deri2;
    pBase = p2;           //pBase的动态类型是Deri2*
}

由上可知,多态就是一个指针拥有多种动态类型。

多态分为两种:

  • 静态多态:函数重载,泛型编程
  • 动态多态:虚函数

静态多态
编译器在编译期间完成的,编译器更具函数实参的类型(可能会进行隐式类型转换),可推断出要调用哪个函数,如果有对应的函数就调用该函数,没有就会报错

动态多态
动态绑定:在程序执行期间(非编译期)判断所引用函数的实际类型,通过其实际类型调用相应的方法
动态绑定条件:
1. 必须是虚函数
2. 通过基类类型的引用或者指针调用虚函数
使用virtual关键字修饰类的成员函数,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定
例如

class Base {
public:
    virtual void fun() {
        cout << "Base::fun()" << endl;
    }
};
class Deri :public Base {
public:
    virtual void fun() {
        cout << "Deri::fun()" << endl;
    }
};

int main() {
    Base* b=new Base;//此时指针b的动态类型是Base*,调用的是基类的fun()函数
    b->fun();
    b = new Deri;//此时指针b的动态类型是Deri*,调用的是派生类的fun()函数
    b->fun();
    return 0;
}

运行结果:
Base::fun()
Deri::fun()
这种就是只有程序运行到这里的时候才知道调用哪个函数,称为动态多态。

但是有引发了一个问题,基类和派生类的函数重名了,他们之间的关系是什么呢?
继承体系同名成员函数的关系
- 重载:在同一作用域,函数名相同、参数不同,返回值可以不同
- 重写(覆盖):不在同一作用域(分别在基类和派生类),函数名相同、参数相同、返回值相同(协变例外),基类函数必须有virtual关键字,访问修饰符可以不同
- 重定义(隐藏):在不同作用域中(分别在基类和派生类)、函数名相同、在基类和派生类中只要不构成重写的就是重定义

纯虚函数
在虚成员函数的形参后面写上“=0”,则成员函数为纯虚函数,包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象,纯虚函数在派生类中重定义以后,派生类才能实例化出对象。

虚表:
只要类中包含有虚函数,则这个类的对象就有一个虚表指针,指向虚表
虚表指针:是在类对象的头4个字节存入虚表的地址,里面包括该类的所有虚函数的地址,且由定义的顺序由声明顺序依次排序存于虚表中。在派生类中,前面是基类的虚函数,后面是派生类的虚函数。
派生类虚表的生成:

  1. 先拷贝基类的虚函数表
  2. 如果派生类重写了基类的某个虚函数,就会替换同位置的基类虚函数
  3. 跟上派生类自己新定义的虚函数

通过基类的引用或指针调用虚函数时,调用基类还是派生类的虚函数,要根据运行时所引用(指针)实际引用(指向)的类型确定;调用非虚函数时,则无论基类指向的是何种类型,都调用的是基类的函数

多态的内存结构:
单继承分为单虚继承和单非虚继承。
单非虚继承的内存结构没有偏移量表,其他内存分布与单虚继承相同
这里具体讨论单虚继承
单虚继承分为派生类重写基类虚函数、派生类新添虚函数、派生类既重写又新添虚函数。
例如:

class Base {
public:
    virtual void fun() {
        cout << "Base::fun()" << endl;
    }
    int b_data;
};
class Deri :public Base {
public:
    virtual void fun() {
        cout << "Deri::fun()" << endl;
    }
    int v_data;
};
int main() {
    Deri* d = new Deri;
    d->b_data = 1;
    d->v_data = 2;  
    return 0;
}

该代码执行后的d所指向地址空间为
0x0123EDD0 50 9b 13 01 //指向虚函数表
0x0123EDD4 01 00 00 00 //基类成员变量
0x0123EDD8 02 00 00 00 //自己的成员变量

将继承改成虚继承后,即使用virtual关键字将程序改为:

class Deri :virtual public Base {
public:
    virtual void fun() {
        cout << "Deri::fun()" << endl;
    }
    int v_data;
};

d所指向空间的内存分布为

0x00C1D370  58 9b ef 00       //跟进这个内存可知其存放的是派生类对基类和自己的偏移量表
    0x00EF9B58  00 00 00 00   //对自己的偏移量
    0x00EF9B5C  08 00 00 00  //对基类的偏移量
0x00C1D374  02 00 00 00  
0x00C1D378  50 9b ef 00        //跟进可知其指向的是一张虚表,由于满足重写的条件,所以只有一个函数指针
    0x00EF9B50  7f 13 ef 00    
    0x00EF9B54  00 00 00 00
0x00C1D37C  01 00 00 00

如果派生类的成员函数与基类的成员函数名不同,即

class Deri :virtual public Base {
public:
    virtual void fun1() {
        cout << "Deri::fun1()" << endl;
    }
    int v_data;
};

运行后,派生类的内存分布:

0x009DEFB8  50 9b 31 00       //指向一个虚函数表
    0x00319B50  83 14 31 00  
    0x00319B54  00 00 00 00 
0x009DEFBC  88 9c 31 00       //指向偏移量表
    0x00319C88  fc ff ff ff         //对自己的偏移量
    0x00319C8C  08 00 00 00   //对基类的偏移量 
0x009DEFC0  02 00 00 00  
0x009DEFC4  5c 9b 31 00       //指向一个虚函数表
    0x00319B5C  69 10 31 00  
    0x00319B60  00 00 00 00
0x009DEFC8  01 00 00 00

重写基类函数并且自己新添虚函数

class Deri :virtual public Base {
public:
    virtual void fun() {
        cout << "Deri::fun()" << endl;
    }
    virtual void fun1() {
        cout << "Deri::fun1()" << endl;
    }
    int v_data;
};

运行后派生类的内存结构

0x00B6BEF0  50 9b 2a 00         //指向虚函数的地址
    0x002A9B50  83 14 2a 00  
    0x002A9B54  00 00 00 00
0x00B6BEF4  88 9c 2a 00         //偏移量表
    0x002A9C88  fc ff ff ff  
    0x002A9C8C  08 00 00 00 
0x00B6BEF8  02 00 00 00  
0x00B6BEFC  5c 9b 2a 00        //虚函数表
    0x002A9B5C  88 14 2a 00  
    0x002A9B60  00 00 00 00
0x00B6BF00  01 00 00 00

多继承
多继承也可以分为
例如:

class Base1 {
public:
    virtual void fun() {
        cout << "Base1::fun()" << endl;
    }
    int b1_data;
};
class Base2 {
public:
    virtual void fun() {
        cout << "Base2::fun()" << endl;
    }
    int b2_data;
};
class Deri :public Base1,public Base2 {
public:
    int v_data;
};

int main() {
    Deri* d = new Deri;
    d->b1_data = 1;
    d->b2_data = 2;
    d->v_data = 3;  
    return 0;
}

程序运行后的d的内存结构

0x0111E6A8  a0 9c ff 00             //第一继承类虚表指针 
    0x00FF9CA0  a1 14 ff 00 
    0x00FF9CA4  00 00 00 00
0x0111E6AC  01 00 00 00  
0x0111E6B0  94 9c ff 00             //第二继承类虚表指针
    0x00FF9C94  9c 14 ff 00  
    0x00FF9C98  00 00 00 00
0x0111E6B4  02 00 00 00  
0x0111E6B8  03 00 00 00

添加virtual关键字后,变成虚继承

class Deri :virtual public Base1,virtual public Base2 {
public:
    int v_data;
};

运行后派生类的空间结构

0x007ADD48  88 9d a6 00         //派生类偏移量表
    0x00A69D88  00 00 00 00  
    0x00A69D8C  08 00 00 00
0x007ADD4C  03 00 00 00  
0x007ADD50  a0 9c a6 00             //第一继承虚函数表
    0x00A69CA0  a1 14 a6 00  
    0x00A69CA4  00 00 00 00
0x007ADD54  01 00 00 00  
0x007ADD58  94 9c a6 00             //第二继承虚函数表
    0x00A69C94  9c 14 a6 00  
    0x00A69C98  00 00 00 00
0x007ADD5C  02 00 00 00

在派生类里面加一个与基类同名的函数

class Deri :virtual public Base1,virtual public Base2 {
public:
    virtual void fun() {
        cout << "Deri::fun()" << endl;
    }
    int v_data;
};

运行后d的内存空间结构

0x0089F318  88 9d a9 00             //指向派生类偏移量表
    0x00A99D88  00 00 00 00  
    0x00A99D8C  08 00 00 00
0x0089F31C  03 00 00 00  
0x0089F320  a0 9c a9 00             //第一继承虚函数表
    0x00A99CA0  88 14 a9 00  
    0x00A99CA4  00 00 00 00 
0x0089F324  01 00 00 00  
0x0089F328  94 9c a9 00             //第二继承虚函数
    0x00A99C94  8d 14 a9 00  
    0x00A99C98  00 00 00 00
0x0089F32C  02 00 00 00

将派生类改为虚继承且新添虚函数后

class Deri :virtual public Base1,virtual public Base2 {
public:
    virtual void fun() {
        cout << "Deri::fun()" << endl;
    }
    virtual void fun1() {
        cout << "Deri::fun1()" << endl;
    }
    int v_data;
};
0x00BABE98  a0 9c 2f 00         //指向派生类虚表
    0x002F9CA0  83 14 2f 00  
    0x002F9CA4  00 00 00 00 
0x00BABE9C  94 9d 2f 00         //指向偏移量表
    0x002F9D94  fc ff ff ff  
    0x002F9D98  08 00 00 00
0x00BABEA0  03 00 00 00  
0x00BABEA4  94 9c 2f 00         //第一继承虚表
    0x002F9C94  88 14 2f 00  
    0x002F9C98  00 00 00 00
0x00BABEA8  01 00 00 00  
0x00BABEAC  8c 9d 2f 00         //第二继承虚表
    0x002F9D8C  8d 14 2f 00  
    0x002F9D90  00 00 00 00 
0x00BABEB0  02 00 00 00

菱形继承
菱形继承是一种特殊的继承,如图所示
c++多态_第1张图片

未使用虚继承的菱形继承,但是会产生二义性

class A {
public:
    virtual void fun() {
        cout << "A::fun()" << endl;
    }
    int a_data;
};

class Base1:public A {
public:
    virtual void fun() {
        cout << "Base1::fun()" << endl;
    }
    int b1_data;
};
class Base2:public A {
public:
    virtual void fun() {
        cout << "Base2::fun()" << endl;
    }
    int b2_data;
};
class Deri :public Base1,public Base2 {
public:
    int v_data;
};

int main() {
    Deri* d = new Deri;
    d->Base1::a_data = 4;
    d->Base2::a_data = 5;
    d->b1_data = 1;
    d->b2_data = 2;
    d->v_data = 3;  
    return 0;
}

运行后d的内存结构

0x00F1D300  84 9b 14 01
    0x01149B84  4c 14 14 01  
    0x01149B88  00 00 00 00  
0x00F1D304  04 00 00 00 
0x00F1D308  01 00 00 00 
0x00F1D30C  90 9b 14 01 
    0x01149B90  44 12 14 01  
    0x01149B94  00 00 00 00
0x00F1D310  05 00 00 00  
0x00F1D314  02 00 00 00  
0x00F1D318  03 00 00 00

使用虚函数且派生类添加自己的虚函数,则变成

class A {
public:
    virtual void fun() {
        cout << "A::fun()" << endl;
    }
    int a_data;
};

class Base1:virtual public A {
public:
    virtual void fun() {
        cout << "Base1::fun()" << endl;
    }
    int b1_data;
};
class Base2:virtual public A {
public:
    virtual void fun() {
        cout << "Base2::fun()" << endl;
    }
    int b2_data;
};
class Deri :public Base1,public Base2 {
public:
    virtual void fun() {
        cout << "Deri::fun()" << endl;
    }
    int v_data;
};

生成d的内存空间:

0x0078D3C0  9c 9b e6 00  
    0x00E69B9C  00 00 00 00  
    0x00E69BA0  14 00 00 00
0x0078D3C4  01 00 00 00  
0x0078D3C8  c8 9c e6 00 
    0x00E69CC8  00 00 00 00 
    0x00E69CCC  0c 00 00 00
0x0078D3CC  02 00 00 00 
0x0078D3D0  03 00 00 00  
0x0078D3D4  7c 9b e6 00  
    0x00E69B7C  92 14 e6 00  
    0x00E69B80  00 00 00 00
0x0078D3D8  05 00 00 00

每层继承都有新添加的虚函数,也重写了基类的虚函数

class A {
public:
    virtual void fun() {
        cout << "A::fun()" << endl;
    }
    int a_data;
};

class Base1:virtual public A {
public:
    virtual void fun() {
        cout << "Base1::fun()" << endl;
    }
    virtual void fun2() {
        cout << "Base1::fun2()" << endl;
    }
    int b1_data;
};
class Base2:virtual public A {
public:
    virtual void fun() {
        cout << "Base2::fun()" << endl;
    }
    virtual void fun3() {
        cout << "Base2::fun3()" << endl;
    }
    int b2_data;
};
class Deri :public Base1,public Base2 {
public:
    virtual void fun() {
        cout << "Deri::fun()" << endl;
    }
    virtual void fun4() {
        cout << "Deri::fun4()" << endl;
    }
    int v_data;
};

运行后d的内存空间

0x00BA9818  d4 9b 02 01  
    0x01029BD4  4c 14 02 01  
    0x01029BD8  01 14 02 01  
0x00BA981C  f8 9b 02 01 
    0x01029BF8  fc ff ff ff  
    0x01029BFC  18 00 00 00
0x00BA9820  01 00 00 00  
0x00BA9824  e4 9b 02 01  
    0x01029BE4  29 14 02 01  
    0x01029BE8  00 00 00 00  
0x00BA9828  04 9c 02 01  
    0x01029C04  fc ff ff ff  
    0x01029C08  0c 00 00 00
0x00BA982C  02 00 00 00  
0x00BA9830  03 00 00 00  
0x00BA9834  f0 9b 02 01  
    0x01029BF0  8e 13 02 01  
    0x01029BF4  00 00 00 00
0x00BA9838  05 00 00 00

上面的例子只是说明了一些分析方法的思想,还有很多种情况没有讨论,但是只要知道了这一种的分析方法,就可以得到其他的内存结构分布。
综上所述,派生类的结构与其继承的关系有关,但是整体位置思想是不变的,即:虚函数表–>偏移量表–>成员数据。
其他的都是一些基类与派生类的叠放次序问题。
如果想知道函数指针指向的的地址是什么,可用函数指针来访问,具体代码如下:

typedef void(*vpf)(); void PriFun(){ Deri* d=new Deri(); vpf* p = (vpf*)(*(int*)d);
    while (*p != 0) { (*p)(); p++; } }

该段代码可以查看派生类的第一张虚表里的函数,稍作修改即可查看派生类所有的虚表。
总结:

  1. 派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值相同(协变除外)
  2. 基类定义了虚函数,在派生类中该函数始终保持虚函数特性
  3. 只有类的非静态成员函数才能定义为虚函数,静态成员函数不能定义为虚函数
  4. 如果在类外定义虚函数,只能在声明函数时加virtual关键字,定义时不用加
  5. 如果在类外定义虚函数,虽然可以将poerator=定义为虚函数,但是最好不要这样做,使用时会容易混淆
  6. 不要再构造函数和析构函数中调用虚函数,在构造函数和析构函数中,对象是不完整的,可能会出现未定义的行为
  7. 最好将基类的析构函数声明为虚函数(析构函数比较特殊,因为派生类的析构函数跟基类的析构函数名称不一样,但是构成覆盖,这里编译器做了特殊处理)
  8. 虚表是所有类对象实例共用的

c++多态

  • 0

    开心

    开心

  • 0

    板砖

    板砖

  • 0

    感动

    感动

  • 0

    有用

    有用

  • 0

    疑问

    疑问

  • 0

    难过

    难过

  • 0

    无聊

    无聊

  • 0

    震惊

    震惊

编辑推荐
C++的多态 在我去深圳第一次接受华为的面试,我遇到这样的一个问题“大概讲一下C++的多态吧”。 我
类的多态特性是支持面向对象的语言最主要的特性,有过非面向对象语言开发经历的人,通常对这一章节
多态是c++中很重要的一环。多态可以分为以下几个层面来剖析: 1.对象的类型 2.多态 3.虚表 先说第一
先是在微博上看到了个微博和云风的评论,然后我回了“楼主对C的内存管理不了解”。 后来引发了很多
C++ 多态实现机制 2013-04-25 16:00 by 捣乱小子 本篇从 C++ 初学者遇到的一个有趣的问题开始。 有
C++的类机制中有支持多态的技术来解决抽象编程,它用的是一种滞后捆绑技术, 这种技术,通过预先设
一. 编译器对C++的多态是通过在Class里面插入一个vptr指针,同时生成一个virtual table, 让vptr指向
在C++中,有两种重载,一种是函数的重载,另一种是运算符的重载。 C++的重载最早出现在基本运算符上
本篇从 C++ 初学者遇到的一个有趣的问题开始。 考虑下面的 C++ 程序: class A { void func(){} };
http://www.cnblogs.com/kunhu/p/3631285.html 在程序设计领域,一个广泛认可的定义是“一种将不同
版权所有 IT知识库 CopyRight © 2009-2015 IT知识库 IT610.com , All Rights Reserved. 京ICP备09083238号