C++复习——虚函数与多态

1. 虚函数

虚函数是应在派生类中重新定义的成员函数。 当使用指针或对基类的引用来引用派生的类对象时,可以为该对象调用虚函数并执行该函数的派生类版本。
在C++中用virtual关键字来表示。在使用继承时,基类的析构函数必须是虚函数,否则在调用析构函数的时候会出现指针错误,下述代码给出原因。

另外强调:

  • 定义一个函数为虚函数,不代表函数为不被实现的函数。
  • 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。
  • 定义一个函数为纯虚函数,才代表函数没有被实现。
  • 定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

示例代码:

#include <iostream>
using namespace std;
class CBase
        {
        public:
            CBase() { cout << "CBase Constructor...\n"; }
            virtual ~CBase() { cout << "CBase Destructor...\n"; } //指定基类析构函数为虚的
            virtual void display() { cout << "Hello, World! CBase \n"; } //虚函数
            void Hello() { cout << "Call CBase Hello.\n"; } //非虚函数
        };
class CDerived: public CBase
        {
        public:
            CDerived() { cout << "CDerived Constructor...\n"; }
            ~CDerived() { cout << "CDerived Destructor...\n"; } //这里也是虚析构函数,因为基类中析构函数是虚的
            void display() { cout << "Hello, World! CDerived \n"; } //虚函数,覆盖基类中的虚函数。
            void Hello() { cout << "Call CDerived Hello.\n"; } //非虚函数
        };
int main()
{
    CDerived Derived; //栈中对象
    CBase *pBase = new CDerived; // 以基类指针指向派生类对象。这里是堆中对象

    cout << "Derived.Hello(); \n\t";
    Derived.Hello();    // 直接调用派生类的函数
    cout << "pBase->Hello(); \n\t"; // 因为 Hello() 为 非虚函数,只能访问基类中的方法。
    pBase->Hello();     // 此时只能调用基类中的Hello
    cout << "Derived.display(); \n\t";
    Derived.display();  // 直接调用派生类的dislay
    cout << "pBase->display(); \n\t"; // 因为虚函数的关系,派生类中的方法被正确调用。
    pBase->display();   // 调用派生类的display
    cout << "delete pBase; \n\t";
    /*
	问题就在这里,如果基类的析构函数不是虚的,那么这里只能析构 CBase 对象,和指针的类型相同。
	基类使用虚析构函数后,基类指针才可以(通过虚函数表)访问派生类的虚析构函数,调用派生类的析构函数析构派生类本身,可以看到整个派生类依次被析构掉了。
	*/
    delete pBase; // 基类是虚析构函数,进而派生类析构函数也是虚的,整个派生类被正确析构。

    cout << "----------------\n";
    return 0;
}

也就是说,当类A中的一个类函数是虚函数,其派生类为B。此时一个类A的指针指向B: A* ptr = new B; 该指针ptr按理说应该是指向B的基类,调用的都是基类A的函数。但是有了虚函数,指针ptr就可以调用派生类B的函数。这样就是虚函数的用法。

虚函数,它虚就虚在所谓"推迟联编"或者"动态联编"上,一个类函数的调用并不是在编译时刻被确定的,而是在运行时刻被确定的。由于编写代码的时候并不能确定被调用的是基类的函数还是哪个派生类的函数,所以被成为"虚"函数。

需要注意:

  1. 只有类的成员函数才能声明为虚函数,虚函数仅适用于有继承关系的类对象。普通函数不能声明为虚函数。
  2. 静态成员函数不能是虚函数,因为静态成员函数不受限于某个对象。
  3. 内联函数(inline)不能是虚函数,因为内联函数不能在运行中动态确定位置。
  4. 构造函数不能是虚函数。
  5. 析构函数可以是虚函数,而且建议声明为虚函数。

1.1 纯虚函数

  1. 定义
    纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加 =0:
  2. 作用
    纯虚函数的存在就是为了规定其派生类都得重写该函数,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
  3. 定义
virtual void funtion1()=0
  1. 引入原因:
    1. 为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
    2. 在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
      为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。

声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。

纯虚函数最显著的特征是:它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。

定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。

纯虚函数的意义,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的默认实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。

参考: C++ 虚函数和纯虚函数的区别

2. 多态

多态性(polymorphism)可以简单地概括为“一个接口,多种方法”,它是面向对象编程领域的核心概念。
通俗的讲,就是相同对象收到不同消息 或 不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。

  • 编译时多态性(静态多态):通过重载函数实现:先期联编 early binding
  • 运行时多态性(动态多态):通过虚函数实现 :滞后联编 late binding

C++运行时多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(Override),或者称为重写。

多态的目的:封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了“接口重用”。也即,不论传递过来的究竟是类的哪个对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。

多态最常见的用法就是声明基类类型的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是固定的,因此将始终调用到同一个函数,这就无法实现“一个接口,多种方法”的目的了。

2.1 重写与重载

重写可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性。
而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。

隐藏

隐藏是指派生类的函数屏蔽了与其同名的基类函数。隐藏规则如下:

  1. 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual 关键字,基类的函数将被隐藏(注意别与重载混淆,重载是在同一个类中发生)。
  2. 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆,覆盖有virtual关键字)。

隐藏后,派生类将只能调用派生类中的同名函数,无法调用基类中的同名函数。这也是第一种规则。
第二种规则中,如果基类函数有virtual关键字,那么就可以使用基类指针来调用基类函数,此时基类函数未被隐藏。

总结

总结为:在基类与子类函数名相同的前提下,根据参数是否相同、是否具有vritual关键字,可分为4种情况:

  1. 参数相同、有virtual关键字:多态重写;
  2. 参数相同、无virtual关键字:隐藏;与重写区分。
  3. 参数不同、有virtual关键字:隐藏;与重载区分。
  4. 参数不同、无virtual关键字:隐藏;与重载区分。

参考:C++多态

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值