您的位置:首页 > 编程语言 > C语言/C++

关于C++中的虚函数

2014-04-10 16:10 183 查看
时间:2014.04.10

地点:基地二楼

----------------------------------------------------------------------

一、动态绑定;

动态绑定的思想使得我们能在同一段代码里边分别处理基类和派生类对象,在形参是基类的一个引用时,我们既能使用基类基类对象调用对应函数,也能使用派生类对象调用对应函数。实际传入的对象将决定到底执行那个版本的相应函数。也就是说在上述过程中函数的运行版本将由实参决定,即在运行时选择函数的版本,所以动态绑定有时也被称为运行时绑定。总的一点来说,在C++中,当使用基类的引用或指针调用一个虚函数时将发生动态绑定。

----------------------------------------------------------------------

二、派生类中的虚函数

派生类经常覆盖继承的虚函数,如果派生类没有覆盖其基类中的某个虚函数,则该虚函数的行为类似于普通成员,派生类会直接继承其在基类中的版本。C++11中允许显示地注明它使用某个成员函数覆盖了它继承的虚函数,方法是在函数最后加上关键字override 。

----------------------------------------------------------------------

三、虚函数的调用

当我们使用基类的引用或者指针调用一个虚成员函数时会执行动态绑定,因为我们直到运行时才知道到底调用了哪个版本的虚函数,所以所有虚函数都必须定义,通常情况下如果我们不需要使用某个函数,则无需定义,但必须为每一个虚函数提供定义,而不管是否被用到,因为编译器也无法确定到底会使用哪个虚函数。对虚函数的调用可能会在运行时才被解析。即当某个虚函数通过指针或者引用调用时,编译器产生的代码直到运行时才能确定应该调用哪个版本的函数。被调用的函数是与绑定到指针或引用上的对象的动态类型相匹配的那个。我们必须确定,动态绑定只有当我们通过指针或者引用调用虚函数时才会发生。

而当我们通过一个具有普通类型(非引用非指针)的表达式调用虚函数时,在编译时就会将调用版本确定下来,例如:

base =derived;    //把派生类对象的基类部分拷贝给基类对象base
base.function();  //调用的是基类对象的成员函数
在这里,我们可以改变base表示的对象的值,但是不会改变该对象的类型,因此编译器会解析调用基类成员函数。

----------------------------------------------------------------------

四、C++中的多态

OOP的核心思想就是多态,含义即”多种形式“,我们把继承关系的多个类型成为多态类型,因为我们能使用这些类型的”多种形式“而无须在意他们的差异。

当我们使用基类的引用或指针调用基类中定义的一个函数时,我们并不知道该函数真正作用的对象时什么类型,因为它可能是一个基类对象也可能是一个派生类对象。如果该函数是虚函数,则直到运行时才会决定到底执行哪个版本,判断的依据是引用或指针所绑定的对象的真实类型。

另一方面,对非虚函数的调用是在编译时进行绑定。通过对象进行的函数(虚的或者非虚的函数)调用也在编译时绑定。因为对象的类型是确定不变的,我们无法令对象的动态类型与静态类型不一致,因此通过对象进行的函数调用将在编译时绑定到该对象所属类中的函数版本上。

总的一点:当且仅当对通过指针或引用调用虚函数时,才会在运行时解析调用,也只有在这种情况下对象的动态类型才有可能和静态类型不同。

----------------------------------------------------------------------

五、虚函数的使用

当在派生类中使用覆盖某个虚函数时,可以再一次使用virtual关键字指明该函数的性质,这并非必须,一旦某个函数被声明成虚函数,则在所有派生类中它都是虚函数。一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须与被它覆盖的基类函数完全一致。返回类型也必须和基类函数匹配,一个例外是当类的虚函数返回类型是类本身的指针或引用时,基类虚函数可以返回基类指针或引用,而派生类对应函数可以返回派生类指针或引用。

----------------------------------------------------------------------

六、虚析构函数

继承关系对基类拷贝控制最直接的影响是基类通常应该定义一个虚析构函数,这样我们就能在动态分配继承体系中的对象了。当我们delete一个动态分配的对象的指针时将执行析构,如果指针指向继承体系中的某个类型,则有可能出现指针的静态类型与被删除对象的动态类型不符的情况。此时编译器应该要知道该操作是将执行基类析构函数还是派生类析构函数。这样和其他函数一样,我们通过在基类中将析构函数定义成虚函数以确保执行正确的析构函数版本。例如:

class MyClass
{
  public:
    //如果我们删除的是一个指向派生类对象的基类指针,则需要虚析构函数
  virtual ~MyClass()=default;  //动态绑定析构函数
};
和类的其他虚函数一样,析构函数的虚属性也会被继承,这是无论使用合成的析构还是自定义的析构都是虚析构,只要基类的析构是虚析构,就能确保delete基类指针时运行正确的析构函数版本。如果基类的析构不是虚析构,那么delete一个指向派生类对象的基类指针将产生未定义的行为。

经验准则:一个基类总是需要析构函数,而且它能将析构函数设定为虚函数。另外虚析构函数将组织合成移动操作。

----------------------------------------------------------------------

七、派生类析构函数

在析构函数体执行完后,对象的成员会被隐式销毁,类似的对象的基类部分也是隐式销毁。派生类析构函数只负责销毁由派生类自己分配的资源:

class D:public Base
{ 
  public:
    //Base::~Base   会被自动调用执行
    ~D(){/*用户自定义*/}
};
对象销毁的顺序与创建相反,派生类析构函数首先执行,然后是基类的析构函数。

----------------------------------------------------------------------

八、在构造函数和析构函数中调用虚函数

考虑当基类构造函数调用虚函数的派生类版本时的情况,这个虚函数可能会访问派生类成员,如果无需访问派生类成员的话,则派生类直接使用基类的虚函数版本就可以了,然而,当执行基类的析构函数时,他要用到的派生类成员尚未初始化,如果允许这样访问,程序可能会崩溃。一句话,如果构造函数或析构函数调用了某个虚函数,则我们应该执行于该构造函数或析构函数所属类型相对应的虚函数版本。也就是说在构造函数和析构函数中是可以调用虚函数的,但要注意只调用属于自己类型的版本。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: