多态

多态成立的三个条件:(面向对象的编程共同的特征)

  1、要有继承

  2、要有函数重写(函数名相同,参数相同)(c++中是虚函数重写)

  3、要有基类指针指向子类对象(基类引用指向子类对象)

 

动态联编和静态联编:

为什么隐藏关系下,调用函数要以指针或是引用类型作为标准呢,因为是静态联编,是编译器在编译时已经绑定好了的。

 

类和类的关系:

  包含关系:一个类的数据成员是另一个类

  部分包含:一个类的成员函数的参数类型是另一个类

 

c++怎么实现多态?

在类中使用virtual声明成员函数后,编译器就知道了这个类是具有多态的功能了,所以在声明虚函数时,就在类中生成一个虚函数表。虚函数表是一个存储类成员函数函数指针的数据结构。它是由编译器自动生成与维护的。virtual成员函数被编译器放入虚函数表中。在使用这个类定义对象时,编译器就默默地在对象中生成一个指向虚函数表的指针——vptr。

在调用时,编译器看到一个函数名,首先判断是不是虚函数,如果不是,那么采用静态联编的思想去选择调用的函数。如果是虚函数,那么就去对象下面的指针变量去找到虚函数表中的函数,从而完成调用(动态联编思想)。

vptr会被继承吗?什么时候初始化?子类虚函数表的内容与父类虚函数表的内容有什么关系? 一个对象只有一个vptr指针吗?

#include "stdafx.h"
#include <stdio.h>
#include<iostream>
#include"test.h"
#include <time.h>
#include <string.h>
#include <memory.h>
#include <assert.h>
using namespace std;


class Parent
{
public:
    virtual void func(){cout<<"A"<<endl;}
    virtual void func(int i){cout<<"A"<<endl;}
private:
    int a;
};

class Child :public Parent
{
public:
    void func(){cout<<"B"<<endl;}
    void func(int i){cout<<"B"<<endl;}
private:
    int b;
};

void main()
{
    Parent a1;
    Child b1;
    cout<<sizeof(Child)<<endl;
    getchar();
}

在定义a1时,调用默认的构造函数来初始化a1对象,并初始化生成的指向虚函数表的指针。所以sizeof(Parent) = 8;

在定义b1时,首先调用基类构造函数,初始化b1对象中的派生而来的数据成员——a和vptr指针,注意此时的vptr指针指向的是基类虚函数表;然后再在调用派生类构造函数的时候,初始化数据成员b和vptr,这次vptr指向派生类自己的虚函数表了。

只要基类存在了虚函数,那么基类及其派生类的对象都会存在一个vptr指针,vptr指向虚函数表,基类和子类的虚函数表也是不同的:

虚函数表示一张记录虚函数地址的表,基类定义对象,那么该对象对应一张虚函数表;派生类定义一个对象,那么该派生类对应另外一张表(针对于单继承关系),基类虚函数表和派生类虚函数表是两张不同的表,所以在基类对象中的vptr和派生类对象的vptr的值是不同的(指向不同的表)。派生类定义了新的虚函数,那么会把新的虚函数地址加入到虚函数表中(所以派生类虚函数表的内容就是继承而来的虚函数地址值,这部分跟基类虚函数表内容一样,外加新定义的虚函数地址)如果派生类重写了基类虚函数,那么派生类虚函数表中将被“覆盖”。

总结:

对于单继承而言,派生类对象与基类对象都只有一个vptr,派生类对象的vptr会先被基类构造函数初始化,然后再派生类构造函数初始化(换言之:派生类对象的vptr先指向基类虚函数表,然后再指向派生类虚函数表)。

对于单继承而言,vptr是在调用构造函数时,完成初始化的,初始化完成之后,vptr指向的虚函数表,从而才可能寻址到对应函数。派生类对象的vptr被一次初始化为父类虚函数表,最终被修改为派生类虚函数表。

vptr不是被继承,它是因为类的虚函数的存在而被编译器创建的(不管虚函数是自己定义还是被继承而来)。

如果一个类同时继承了多个父类,那么vptr的数量将不是唯一的了,这就要看具体有几个父类存在虚函数表了。

 

说明1:

通过虚函数表指针VPTR调用重写函数是在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。

说明2:

出于效率考虑,没有必要将所有成员函数都声明为虚函数。

 

谈谈你认为什么是多态?

1、现象

2、实现的三个条件

3、多态原理(动态联编)

 

在父类中构造函数调用虚函数能实现多态吗?构造函数能定义为虚函数吗?

class Parent
{
public:
    Parent()
    {
        func();
    }
    virtual void func(){cout<<"A"<<endl;}
private:
    int a;
};

class Child :public Parent 
{
public:
    void func(){cout<<"B"<<endl;}

private:
    int b;
};

void main()
{
    Child b1;
b1.fun();
Parent* p = &b1;
p->fun(); }

上述程序,在基类的构造函数中调用了基类的虚函数,同时在派生类中重写了该函数,那么在定义派生类对象时,是怎么调用的呢?

根据派生类对象的vptr初始化过程可以知道,b1的vptr先在调用基类构造函数时,被基类虚函数表初始化,所以在调用父类构造函数时,是会执行父类的fun函数的。然后父类构造函数指向完毕了,调用派生类构造函数时,再将vptr修改为派生类虚函数表。所以此时b1.fun();是执行派生类函数的。

构造函数是不能被定义为虚函数的,因为虚函数是被存在虚函数表中的,而虚函数表的地址是靠构造函数初始化时,传递给vptr指针的。所以要找到虚函数都是通过vptr寻址虚函数表才能得到的。那么构造函数如果被虚函数化了。vptr得不到初始化,甚至是成员变量的初始化都找不到构造函数来完成。所以是不允许的。

 

子类对基类的虚函数重写:

子类虚函数中可以不加virtual关键字。

 

虚析构函数:

在父类中将析构函数声明为虚函数,那么可以通过一个父类指针将所有的子类析构函数执行一遍。

class Parent
{
public:
    virtual ~Parent(){cout<<"析构Parent"<<endl;}
    virtual void func(){cout<<"A"<<endl;}
private:
    int a;
};

class Child :public Parent 
{
public:
    void func(){cout<<"B"<<endl;}
    ~Child(){cout<<"析构Child"<<endl;}
private:
    int b;
};

void main()
{
    Parent* p = new Child();
    delete p;
    getchar();
}

 

  

 

posted @ 2016-10-17 14:29  e-data  阅读(117)  评论(0)    收藏  举报