本文较为深入的分析了C++中虚函数与纯虚函数的用法,对于学习和掌握面向对象程序设计来说是至关重要的。具体内容如下:
首先,面向对象程序设计(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; // 错误,不能实例化抽象基类
总结:
①.虚函数必须实现,不实现编译器会报错。
②.父类和子类都有各自的虚函数版本。由多态方式在运行时动态绑定。
③.通过作用域运算符可以强行调用指定的虚函数版本。
④.纯虚函数声明如下:virtual void funtion()=0; 纯虚函数无需定义。包含纯虚函数的类是抽象基类,抽象基类不能创建对象,但可以声明指向抽象基类的指针或引用。
⑤.派生类实现了纯虚函数以后,该纯虚函数在派生类中就变成了虚函数,其子类可以再对该函数进行覆盖。
⑥.析构函数通常应该是虚函数,这样就能确保在析构时调用正确的析构函数版本。
本文向大家介绍c++中虚函数和纯虚函数的作用与区别,包括了c++中虚函数和纯虚函数的作用与区别的使用技巧和注意事项,需要的朋友参考一下 虚函数为了重载和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的此函数! 纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数! 虚函数 引入原因:为了方便使用多态特性,我们常常需要在基类中定义虚函数。 纯
本文向大家介绍虚函数与纯虚函数之间的区别,包括了虚函数与纯虚函数之间的区别的使用技巧和注意事项,需要的朋友参考一下 在本文中,我们将了解虚拟和纯虚拟功能之间的区别。 虚函数 它在类中有自己的定义。 基类可以覆盖虚拟函数。 它没有派生类。 声明 纯虚函数 没有定义。 如果一个类至少具有一个虚函数,则可以将其声明为抽象。 派生类必须重写纯虚函数才能使用它。 通过在声明中放置“ = 0”来指定纯虚函数
本文向大家介绍C++ 虚函数和纯虚函数的区别分析,包括了C++ 虚函数和纯虚函数的区别分析的使用技巧和注意事项,需要的朋友参考一下 首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数。 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。 定义一个函数为纯虚函数,才代表函数没有被实现。 定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函
我正在学习面向对象的C,并有一个关于虚拟/纯虚拟和多级继承的问题。 假设我有这样的简单代码: 我的理解是,除非getWidth被指定为虚拟,否则多态将使用“Base”类的函数。我的意思是r-的最终调用 在这种情况下,我注意到如果我删除Shape中的纯虚拟声明,我们会得到我刚才描述的行为。在基类中有一个纯虚函数会自动使该函数的所有定义都是虚的吗?
本文向大家介绍C++之普通成员函数、虚函数以及纯虚函数的区别与用法要点,包括了C++之普通成员函数、虚函数以及纯虚函数的区别与用法要点的使用技巧和注意事项,需要的朋友参考一下 普通成员函数是静态编译的,没有运行时多态,只会根据指针或引用的“字面值”类对象,调用自己的普通函数;虚函数为了重载和多态的需要,在基类中定义的,即便定义为空;纯虚函数是在基类中声明的虚函数,它可以再基类中有定义,且派生类必须
在 C++中,可以将虚函数声明为纯虚函数,语法格式为: virtual 返回值类型 函数名 (函数参数) = 0; 纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上 ,表明此函数为纯虚函数。 最后的 并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。 包含纯虚函数的类称为抽象类(Abstract Class)。之所以说它抽象,是因为它无法实例化,也就是无法创建对象。