【C++】C++中的虚函数与纯虚函数
C++中的虚函数
先来看一下实际的场景,就很容易明白为什么要引入虚函数的概念。假设我们有一个基类Base,Base中有一个方法eat;有一个派生类Derived从基类继承来,并且覆盖(Override)了基类的eat;继承表明ISA(“是一个”)的关系,现在我们有一个基类的指针(引用)绑定到派生类对象(因为派生类对象是基类的一个特例,我们当然可以用基类指针指向派生类对象),当我们调用pBase->eat()的时候,我们希望调用的是Derived类的eat,而实际上调用的是Base类的eat,测试代码如下:
#include <iostream>
#include <string>
#include <vector>
using namespace std; class Base
{
public:
Base(){} void eat()
{
cout << "I am base eat()" << endl;
}
private: }; class Derived:public Base
{
public:
Derived(){} void eat()
{
cout << "I am derived eat()" << endl;
}
}; int main()
{
Base* pBase;
Base base;
Derived* pDerived;
Derived derived; // 如果基类声明为普通函数,基类指针不管指向基类对象还是派生类对象,基类指针都调用基类函数
pBase = &derived;
pBase->eat();
pBase = &base;
pBase->eat(); return ;
}
运行结果如下:

我们将基类指针分别绑定到派生类对象和基类对象,运行结果显示我们始终调用的是基类的函数,这个时候调用哪个函数是由指针的静态类型决定的,因为pBase为Base的指针,所以不管指向基类对象还是派生类对象,都会调用由指针静态类型所决定的函数。
为了能够实现,当基类指针指向派生类对象的时候,自动调用派生类对象自己实现的函数,C++引入了虚函数的概念。可以声明基类函数为虚函数,派生类对象覆盖了整个虚函数之后,根据派生类对象的类别自动调用派生类自己实现的函数。还是上面的例子,这次我们将基类的eat函数声明为虚函数(virtual)
#include <iostream>
#include <string>
#include <vector>
using namespace std; class Base
{
public:
Base(){} // 基类声明为虚函数
virtual void eat()
{
cout << "I am base eat()" << endl;
}
private: }; class Derived:public Base
{
public:
Derived(){} void eat()
{
cout << "I am derived eat()" << endl;
}
}; int main()
{
Base* pBase;
Base base;
Derived* pDerived;
Derived derived; // 基类指针指向派生类时,调用派生类的eat函数;指向基类时调用基类的eat函数
pBase = &derived;
pBase->eat();
pBase = &base;
pBase->eat(); return ;
}
运行结果如下:

从运行结果可以看到,基类指针在绑定基类对象时调用了基类的eat函数,在绑定派生类对象时调用了派生类的eat函数,具体调用哪个函数是运行时决定的。
小结:如果没有虚函数,无论基类指针指向的实际对象是什么,都会调用基类定义的函数,无法实现多态行为。为了实现多态行为,C++引入了虚函数。具体的方式是在基类(需要实现多态)的成员函数声明之前加上virtual关键字,派生类对象覆盖override该虚函数,然后将基类的指针(或者引用)的指针绑定到派生类对象或基类对象上,编译器在运行时确定指针所指向的具体对象,并自动调用具体对象的成员函数。
纯虚函数
前面讨论了我们C++通过引入虚函数来实现多态的特性。然而有时会出现这样的场景,基类的某些虚函数对于基类本身是没有意义的,它只是为了让派生类去继承这个函数并override自己的实现。比如:我们有一个平面图形Figure类,所有的平面图像都有面积Area函数来计算平面图形的面积,然而直接定义一个Figure对象却没有指明具体的类别,并且去调用Area函数是没有意义的。我们并不希望用户这样做,但是在仅仅引入虚函数的情况下,我们却没有办法避免用户这么做。代码实例如下:
#include <iostream>
using namespace std; class Figure
{
public:
Figure(){}
virtual double Area()
{
return 0.0;
}
}; class Rect:public Figure
{
public:
Rect(double w = 10.0, double h = 10.0):Figure(), width_(w), height_(h){}
double Area()
{
return width_ * height_;
}
private:
double width_;
double height_;
}; class Circle:public Figure
{
public:
Circle(double r = 10.0):Figure(), radius_(r){}
double Area()
{
return 3.14 * radius_ * radius_;
}
private:
double radius_;
}; int main()
{
// 这样的定义和调用并没有任何意义
// 我们并不清楚用户这样做要达到什么的行为,我们宁愿用户根本不能定义Figure类
Figure figure;
figure.Area(); return ;
}
为了避免用户定义Figure类并调用Area函数,C++引入了纯虚函数概念, 通过将Area函数声明为纯虚函数(pure virtual function),来避免创建Figure类对象,纯虚函数的声明是在虚函数后面加上=0,代码如下:
#include <iostream>
using namespace std; class Figure
{
public:
Figure(){} // 声明Area函数为纯虚函数
virtual double Area() = ;
}; class Rect:public Figure
{
public:
Rect(double w = 10.0, double h = 10.0):Figure(), width_(w), height_(h){}
double Area()
{
cout << "Rect Area: " << width_ * height_ << endl;
return width_ * height_;
}
private:
double width_;
double height_;
}; class Circle:public Figure
{
public:
Circle(double r = 10.0):Figure(), radius_(r){}
double Area()
{
cout << "Circle Area: " << 3.14 * radius_ * radius_ << endl;
return 3.14 * radius_ * radius_;
}
private:
double radius_;
}; int main()
{
// 创建包含纯虚函数的类对象会出现编译错误
// Figure figure;
// figure.Area(); // 再来体会一下多态特性
Figure* pFigure;
Rect rect;
Circle circle;
pFigure = ▭
pFigure->Area();
pFigure = &circle;
pFigure->Area(); return ;
}
运行结果如下:

如果定义Figure类对象figure会出现编译错误,编译显示不能定义抽象类实例对象。C++将包含一个或者多个虚函数的类称为抽象类,用户不能定义抽象类的具体对象,派生类必须override其继承的每一个纯虚函数,否则派生类也为抽象类,不能直接使用。注意纯虚函数通常情况下只有声明,并没有定义,留作派生类override。但是纯虚函数有定义也是可以的,表明基类希望派生类继承某些共性行为。
C++虚函数与纯虚函数总结如下:
【1】C++通过引入虚函数来实现多态行为。虚函数的声明方式如下: virtual returnType FunctionName(Parameters List) ,派生类可以override基类的虚函数实现自己的行为,当基类指针或引用绑定到派生类对象时,在运行时确定指针指向的对象具体类型,并自动调用相应类型的函数实现多态行为。
【2】析构函数应该是虚函数,这是因为假设虚构函数不是虚函数,如果基类指针指向派生类对象,如果没有虚函数提供多态机制,那么基类指针只是调用基类对应的析构函数,并没有析构派生类对象的派生类部分,因而出现资源并没有完全释放的情况;反之,如果基类的虚构函数是虚函数,则基类指针多态的调用派生类的析构函数,最后调用基类的析构函数,从而完成完全的析构过程。
【2】C++引入纯虚函数来规范派生类的行为,实际上等同于告诉派生类 “你必须提供这些纯虚函数的具体实现,我并不关心你具体是怎么实现的”。纯虚函数的声明方式如下: virtual returnType FunctionName(Parameter List) = ; 。
【3】定义了一个以上纯虚函数的类称为抽象类,抽象类不能定义具体的实例对象,但是可以定义抽象类的指针或引用。抽象类的派生类必须override其继承的所有纯虚函数,否则派生类也成为抽象类,不能直接使用。在具体的工程实践过程中,抽象类一般指生命纯虚函数,但是并不定义纯虚函数,而是由其派生类来override这些纯虚函数,这样做到了派生类只是继承了抽象类的接口,但是并不继承抽象类的实现(因为根本就没有实现)。
参考文献
[1] Wiki:Virtual function
[2] Hackbuteer1的专栏:虚函数与纯虚函数的区别
[3] 类别(class)继承的一些特性
【C++】C++中的虚函数与纯虚函数的更多相关文章
- C++中虚函数和纯虚函数的区别与总结
首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...
- c++中虚函数与纯虚函数的区别(转)
首先:强调一个概念定义一个函数为虚函数,不代表函数为不被实现的函数.定义他为虚函数是为了允许用基类的指针来调用子类的这个函数.定义一个函数为纯虚函数,才代表函数没有被实现.定义纯虚函数是为了实现一个接 ...
- c++ 虚函数和纯虚函数
在你设计一个基类的时候,如果发现一个函数需要在派生类里有不同的表现,那么它就应该是虚的.从设计的角度讲,出现在基类中的虚函数是接口,出现在派生类中的虚函数是接口的具体实现.通过这样的方法,就可以将对象 ...
- C++ - 虚基类、虚函数与纯虚函数
虚基类 在说明其作用前先看一段代码 class A{public: int iValue;}; class B:public A{public: void bPrintf(){ ...
- C++ Primer--虚函数与纯虚函数的区别
首先:强调一个概念 定义一个函数为虚函数,不代表函数为不被实现的函数. 定义他为虚函数是为了允许用基类的指针来调用子类的这个函数. 定义一个函数为纯虚函数,才代表函数没有被实现. 定义纯虚函数是为了实 ...
- C++ 虚函数与纯虚函数
#include<iostream> #include<string> using namespace std; class A{ public: virtual void f ...
- C++ 虚函数 、纯虚函数、接口的实用方法和意义
也许之前我很少写代码,更很少写面向对象的代码,即使有写多半也很容易写回到面向过程的老路上去.在写面向过程的代码的时候,根本不管什么函数重载和覆盖,想到要什么功能就变得法子的换个函数名字,心里想想:反正 ...
- C++虚函数与纯虚函数用法与区别(转载)
1. 虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class) ...
- C++(九)— 虚函数、纯虚函数、虚析构函数
1.虚函数 原因:通过指针调用成员函数时,只能访问到基类的同名成员函数.在同名覆盖现象中,通过某个类的对象(指针及引用)调用同名函数,编译器会将该调用静态联编到该类的同名函数,也就是说,通过基类对象指 ...
随机推荐
- T分布(T-Distribution)
1.What is the T Distribution? T分布(也叫Student 's T分布)是一组与正态分布曲线几乎相同的分布,只是更短更胖一点.当有小样本时,使用t分布而不是正态分布.样本 ...
- CAP理论(摘)
先解释一下软件编程中常见的一些概念: 抽象先于具象.这个抽象并非虚无的抽象,而是指事物尚未分化为具象之前的那个前体存在.当那个前体存在分化成具象存在之后,前体存在就退化为背景,成为一种抽象. 结构是关 ...
- mysql异常
一.Can't connect to MySQL server on 'localhost' (10061)翻译:不能连接到 localhost 上的mysql分析:这说明“localhost”计算机 ...
- quartz2.2.1 web配置
首先到http://www.quartz-scheduler.org/downloads/catalog下载工具包 将下载包中lib目录下的所有jar包导入到项目中 因为quartz依赖log4j,需 ...
- springmvc el表达式取值顺序问题
最近遇到一个问题,两次访问同一个controler, 第二次根据判断条件,明明没有设置model的值,在jsp中通过${data}还是能取到值,最后找到原因是 我为了能够加快响应速度,在session ...
- 探讨 .NET 4 新增的 SortedSet 类
http://www.cnblogs.com/WizardWu/archive/2010/06/17/1759297.html
- win10下docker安装和配置镜像仓库
初学docker记录一下流程 1.首先安装直接官网下载 DockerToolbox 即可,安装过程傻瓜式下一步即可.(这个集成了虚拟机,果然安装过的可以去掉) 2.安装好后双击Docker Quick ...
- MVC4升级到MVC5未能加载文件或程序集System.Web.WebPages.Razor, Version=3.0.0.0
首先,我并没有升级他,头一天还是好好的,用的都是2.0.0.0版本的,今天来打开就出现了这个错误: 未能加载文件或程序集“System.Web.WebPages.Razor, Version=3.0. ...
- HTML5新特性:范围样式
原文出处:http://blog.csdn.net/hfahe/article/details/7381141 Chromium 最近实现了一个HTML5的新特性:范围样式,又叫做< ...
- boosting_bagging
boosting(提升法) 对于训练集中的每个样本建立全职W(i),当某个样本被错误分类概率很高时,样本的权重加大: 在迭代过程中,每一个迭代器都是一个弱分类器,我们需要用某种策略将其组合,作为最终模 ...