面向对象程序设计(object-oriented programming)的核心思想是数据抽象、继承、动态绑定通过数据抽象,可以使类的接口与实现分离,使用继承,可以更容易地定义与其他类相似但不完全相同的新类,使用动态绑定,可以在一定程度上忽略相似类的区别,而以统一的方式使用它们的对象。

虚函数的作用是实现多态性(Polymorphism),多态性是将接口与实现进行分离,采用共同的方法,但因个体差异而采用不同的策略。纯虚函数则是一种特殊的虚函数。虚函数联系到多态,多态联系到继承。所以本文中都是在继承层次上做文章。没了继承,什么都没得谈。

一、虚函数

1 . 定义

在C++中,基类必须将它的两种成员函数区分开来:一种是基类希望其派生类进行覆盖的函数;另一种是基类希望派生类直接继承而不要改变的函数。对于前者,基类通过在函数之前加上virtual关键字将其定义为虚函数(virtual)

class Base{  // 基类
public:
virtual int func(int n) const;
}; class Derive_Class : public Base{
public:
int func(int n) const; // 默认也为虚函数
};

当我们在派生类中覆盖某个函数时,可以在函数前加virtual关键字。然而这不是必须的,因为一旦某个函数被声明成虚函数,则所有派生类中它都是虚函数。任何构造函数之外的非静态函数都可以是虚函数。派生类经常(但不总是)覆盖它继承的虚函数,如果派生类没有覆盖其基类中某个虚函数,则该虚函数的行为类似于其他的普通成员,派生类会直接继承其在基类中的版本。

2 . 动态绑定

当我们使用基类的引用(或指针)调用一个虚函数时将发生动态绑定(dynamic binding)。因为我们直到运行时才能知道到底调用了哪个版本的虚函数,可能是基类中的版本也可能是派生类中的版本,判断的依据是引用(或指针)所绑定的对象的真实类型。与非虚函数在编译时绑定不同,虚函数是在运行时选择函数的版本,所以动态绑定也叫运行时绑定(run-time binding)

3 . 静态类型与动态类型

静态类型指的是变量声明时的类型或表达式生成的类型,它在编译时总是已知的;动态类型指的是变量或表达式表示的内存中的对象的类型,它直到运行时才可知。当且仅当通过基类的指针或引用调用虚函数时,才会在运行时解析该调用,也只有在这种情况下对象的动态类型才有可能与静态类型不同。如果表达式既不是引用也不是指针,则它的动态类型永远与静态类型一致。

4 . final和override

派生类中如果定义了一个函数与基类中虚函数同名但形参列表不同,编译器会认为这是派生类新定义的函数。如果我们的意图本是覆盖虚函数,则这种错误很难发现。通过在派生类中的虚函数最后加override关键字使得意图更加清晰。如果我们使用override标记了某个函数,但该函数并没有覆盖已存在的虚函数,编译器将报错。

class Base{  // 基类
public:
virtual int func(int a, int b) const;
}; class Derive_Class : public Base{
public:
int func(int a) const override; // 报错,没有覆盖虚函数
};

如果我们定义一个类,并不希望它被继承。或者希望某个函数不被覆盖,则可以把类或者函数指定为final,则之后任何尝试继承该类或覆盖该函数的操作将引发错误。

class Base final { /*   */ };     // 基类不能被继承
class Derive_Class : public Base { /* */ }; // 报错 void func(int) const final; // 不允许后续的其他类覆盖func(int)

5 . 回避虚函数的机制

在某些情况下,我们希望对虚函数的调用不要进行动态绑定,而是强迫其执行虚函数的某个特定版本。可以使用作用域运算符实现这一目的。

// 强行调用基类中定义的函数版本而不管baseP的动态类型是什么
int a = baseP->Base::func(42);

如果一个派生类虚函数需要调用它的基类版本,但是没有使用作用域运算符,则在运行时该调用将被解析为对派生类版本自身的调用,从而导致无限递归。

二、纯虚函数

1 . 定义

为了方便使用多态特性,我们常常需要在基类中定义虚函数。在许多情况下,在基类中不能对虚函数给出有意义的实现。为了让虚函数在基类什么也不做,引进了“纯虚函数”的概念,使函数无须定义。我们通过在函数体的位置(即在声明语句的分号之前)书写=0就可以将一个虚函数说明为纯虚函数(pure virtual)。其中,=0只能出现在类内部的虚函数声明语句处:

class Base{  // 抽象基类
public:
virtual int func(int n) const =0;
};

需要注意的是,我们也可以为纯虚函数提供定义,不过函数体必须定义在类的外部。

2 . 抽象基类

含有(或者未经覆盖直接继承)纯虚函数的类叫抽象基类(abstract base class)。抽象基类负责定义接口,而后续的其他类可以覆盖该接口。如果派生类中没有重新定义纯虚函数,而只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象基类。因为抽象基类含有纯虚函数(没有定义),所以我们不能创建一个抽象基类的对象,但可以声明指向抽象基类的指针或引用。

Base base;   // 错误,不能实例化抽象基类

总结:

  1. 虚函数必须实现,不实现编译器会报错。
  2. 父类和子类都有各自的虚函数版本。由多态方式在运行时动态绑定。
  3. 通过作用域运算符可以强行调用指定的虚函数版本。
  4. 纯虚函数声明如下:virtual void funtion()=0; 纯虚函数无需定义。包含纯虚函数的类是抽象基类,抽象基类不能创建对象,但可以声明指向抽象基类的指针或引用。
  5. 派生类实现了纯虚函数以后,该纯虚函数在派生类中就变成了虚函数,其子类可以再对该函数进行覆盖。
  6. 析构函数通常应该是虚函数,这样就能确保在析构时调用正确的析构函数版本。

C++学习之虚函数与纯虚函数的更多相关文章

  1. C++ 虚函数 、纯虚函数、接口的实用方法和意义

    也许之前我很少写代码,更很少写面向对象的代码,即使有写多半也很容易写回到面向过程的老路上去.在写面向过程的代码的时候,根本不管什么函数重载和覆盖,想到要什么功能就变得法子的换个函数名字,心里想想:反正 ...

  2. C++ 虚函数与纯虚函数 浅析

    [摘要] 在虚函数与纯虚函数的学习中.要求理解虚函数与纯虚函数的定义,了解虚函数与纯虚函数在实例化上的差异.掌握两者在实现上的必要性.熟悉纯虚函数在子类与孙类的函数类型.本文即针对上述问题展开阐述. ...

  3. c++ 虚函数,纯虚函数的本质区别

    转载博客:https://mp.weixin.qq.com/s?__biz=MzAxNzYzMTU0Ng==&mid=2651289202&idx=1&sn=431ffd1fa ...

  4. c++ 虚函数和纯虚函数

    在你设计一个基类的时候,如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的.从设计的角度讲,出现在基类中的虚函数是接口,出现在派生类中的虚函数是接口的具体实现.通过这样的方法,就可以将对象 ...

  5. C++ - 虚基类、虚函数与纯虚函数

    虚基类       在说明其作用前先看一段代码 class A{public:    int iValue;}; class B:public A{public:    void bPrintf(){ ...

  6. C++ Primer--虚函数与纯虚函数的区别

    首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...

  7. C++ 虚函数与纯虚函数

    #include<iostream> #include<string> using namespace std; class A{ public: virtual void f ...

  8. c/c++ 基金会(七) 功能覆盖,虚函数,纯虚函数控制

    1.功能覆盖 ClassA , ClassB ,其中ClassB继承ClassA 类的定义如下面的: #ifndef _CLASSA_H #define _CLASSA_H #include < ...

  9. 【C++】C++中的虚函数与纯虚函数

    C++中的虚函数 先来看一下实际的场景,就很容易明白为什么要引入虚函数的概念.假设我们有一个基类Base,Base中有一个方法eat:有一个派生类Derived从基类继承来,并且覆盖(Override ...

  10. C++中虚函数和纯虚函数的区别与总结

    首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...

随机推荐

  1. python网络爬虫。第一次测试-有道翻译

    2018-03-0720:53:56 成功的效果如下 代码备份 # -*- coding: UTF-8 -*- from urllib import request from urllib impor ...

  2. Microsoft SQL Server学习(三)

    1.表:表示一个实体(客观存在的事物或抽象时间),可实现对实体的数据描述和数据操作. 2.表结构:二位平面(行.列) 3.数据类型: 类型名称 类型 整形 bit(只存储0.1) samllint i ...

  3. 动态设置缩放比例和html字体大小

    <!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8& ...

  4. CAD得到所有实体方法(网页版)

    主要用到函数说明: IMxDrawSelectionSet::AllSelect 得到当前空间的所有实体.详细说明如下: 参数 说明 [in,defaultvalue(NULL)] IMxDrawRe ...

  5. 梦想CAD控件关于比较问题

    全图比较 怎么比较两个CAD图纸文件修改前后的不同部分呢?在工程图纸设计中,我们更多情况下可能需要对同一张工程图的前后修改部分进行对比,以确定工程图纸的改动部分及追溯原因,本教程演示了几种常见的比较方 ...

  6. Appium 的xpath定位

    Appium 的xpath定位 1.如果元素text是唯一的,可以通过text文本定位 //*[@text=’text文本属性’] # 定位text driver.find_element_by_xp ...

  7. Linux常用命令——关机与重启命令

    1.shutdown命令 shutdown [选项] 时间 --使用shutdown进行关机或重启会正确保存正在使用的服务,其他命令有一定的危险性,建议最好使用shutdown命令进行关机重启 选项: ...

  8. 小程序wx:key = “{{*this}}”报错

    解决方案:改为 wx:key = "*this"

  9. 洛谷——P2846 [USACO08NOV]光开关Light Switching

    P2846 [USACO08NOV]光开关Light Switching 题目大意: 灯是由高科技——外星人鼠标操控的.你只要左击两个灯所连的鼠标, 这两个灯,以及之间的灯都会由暗变亮,或由亮变暗.右 ...

  10. Idea+maven+testng+reportng生成测试报告

    TestNG自带的测试报告不是很好用,所以一般结合reportng生成美观的测试报告. 首先,在pom.xml中添加testng和reportng相关依赖 <dependencies> & ...