0引言:在学习C++时,碰到过以下四个以“虚”命名的概念,在系统理解这些高大上的术语后,才发现它们果真“名不虚传”。

  为了方便捋清楚这些概念和之间的相互关系,本人对其进行了系统的总结,欢迎讨论。

1.虚基类

(1)作用:间接派生类只保存共同基类的一份成员(数据成员/函数成员),优化存储空间。

(2)虚基类初始化方法:

  在基类的直接派生类中声明为虚函数(virtual public B / virtual public C),在最后的派生类中初始化直接基类和虚基类(这一点要特别注意,虚基类也是由最后的派生类执行,会屏蔽直接派生类对虚基类的初始化【避免虚基类多次初始化】,即D必须对A、B、C进行初始化,B和C对A的初始化不起作用。)

(3)栗子

 #include <iostream>
using namespace std; //虚基类
class A
{
public:
A(int da)
{
data_a = da;
cout << "A" << endl;
}
~A()
{
cout << "~A" << endl;
}
protected:
int data_a;
};
//直接派生类
class B :virtual public A
{
public:
B(int da, int db) :A(da)
{
data_b = db;
cout << "B" << endl;
}
~B()
{
cout << "~B" << endl;
}
protected:
int data_b;
}; //直接派生类
class C :virtual public A
{
public:
C(int da, int dc) :A(da)
{
data_c = dc;
cout << "C" << endl;
}
~C()
{
cout << "~C" << endl;
}
protected:
int data_c;
};
//间接派生类
class D : public B, public C
{
public:
D(int da, int db, int dc, int dd) :A(da), B(da, db), C(da, dc)
{
data_d = dd;
cout << "D" << endl;
}
~D()
{
cout << "~D" << endl;
}
void display()
{
cout << "data_a=" << data_a << "\t" << "data_b=" << data_b << "\t" << "data_c=" << data_c << "\t" << "data_d=" << data_d << endl;
}
protected:
int data_d;
}; int main()
{
D test_d(, , , );
test_d.display(); return ;
}

输出:

2.虚函数

(1)思考:在基类和派生类中存在两个函数不仅名字相同,参数个数也相同,但是功能不同即函数体不同【不是函数重载!】,如何实现两个函数的调用?

一般思路是采取同名覆盖原则,即派生类中同名函数覆盖掉基类同名函数,如果想调用基类的同名函数,必须用类作用域符::来进行区分。这种做法在派生结构复杂时使用不太方便,能否采用一种调用形式,既可以调用派生类也可以调用基类同名函数?

这就是虚函数大展手脚的时候了,虚函数允许在派生类中重新定义与基类同名的函数,并且允许通过基类指针或引用来访问基类和派生类的同名函数。

(2)栗子

//main.cpp
#include <iostream>
#include "virtual.h"
using namespace std;
int main()
{
Shape *pShape = new Shape();//定义基类指针,指向基类对象所在内存空间
pShape->PrintArea();
Retangle ret(, );
pShape = &ret;//将基类指针指向派生类类型对象内存
pShape->PrintArea();
Circle cir();
pShape = &cir;//将基类指针指向派生类类型对象内存
pShape->PrintArea(); return ;
} //virtual.h
#ifndef virtual_h
#define virtual_h
class Shape
{
public:
virtual void PrintArea();
virtual double CalculateArea();
protected:
double area;
}; class Retangle: public Shape
{
public:
Retangle(double len, double wid);
virtual void PrintArea();
virtual double CalculateArea();
private:
double length;
double width;
}; class Circle : public Shape
{
public:
Circle(double r);
virtual void PrintArea();
virtual double CalculateArea();
private:
double radius;
};
#endif //virtual.cpp
#include "virtual.h"
#include <iostream>
using namespace std;
void Shape::PrintArea()
{
cout << "当前没有形状设置!" << endl;
}
double Shape::CalculateArea()
{
return area;
}
Retangle::Retangle(double len, double wid)
{
length = len;
width = wid;
}
double Retangle::CalculateArea()
{
area = length * width;
return Shape::CalculateArea();
}
void Retangle::PrintArea()
{
CalculateArea();
cout << "矩阵面积为:" << area << endl;
}
Circle::Circle(double r)
{
this->radius = r;
}
double Circle::CalculateArea()
{
area = 3.14*radius*radius;
return Shape::CalculateArea();
}
void Circle::PrintArea()
{
CalculateArea();
cout << "圆的面积为:" << area << endl;
}

输出:

(3)关于使用虚函数的好处:

1) 基类里声明为虚函数函数体可以为空,它的作用就是为了能让这个函数在它的子类里面可以被同名使用,这样编译器就可以使用后期绑定来达到多态了;

2) 通常我们把很多函数加上virtual,是一个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性,因为你很难预料到基类里面的这个函数不在派生类里面不去修改它的实现。

3.纯虚函数

  纯虚函数就很好理解了,有些函数虽然并不是基类所需要的,但是派生类可能会用到,所以会在基类中为派生类保留一个函数名,以便以后派生类定义使用。纯虚函数不具备函数功能,是不能被调用的。

以上述代码为例,可以将Shape类中的 PrintArea()定义为纯虚函数, virtual void PrintArea() = 0;

4.虚析构函数

  在学习派生类的析构函数时,留了一个坑,就是析构函数的调用次序问题,在不使用虚函数的前提下,析构函数的调用次序是:先调用派生类构造函数清理新增的成员,再调用子对象析构函数(基类构造函数)清理子对象,最后再调用基类构造函数清理基类成员,过程正好与构造函数的调用过程相反。

在使用虚函数时,析构函数的调用会出现哪些情况?

同样是虚函数例子中的代码(加上构造和析构语句),进行以下测试:

 int main()
{
Shape *pShape = new Circle();//基类指针指向派生类对象内存空间
delete pShape;
pShape = NULL; return ;
}

输出:

测试结果表明:程序调用了两次构造函数,但是只调用了一次析构函数,造成了内存泄漏。

这是因为派生类析构函数无法从基类继承,在没有声明基类析构函数为虚函数时,基类指针释放时无法找到派生类析构函数地址,也就不能释放派生类对象所在内存空间。而将基类析构函数也声明为虚函数时,该基类所有派生类也将自动成为虚函数,所有虚析构函数的入口地址都会存放在一个虚函数表(指针数组)中,查找方便,这样就避免了无法调用派生类析构函数所造成的内存泄漏问题了。

5.总结:

(1).虚析构函数是建立在虚函数的基础之上的,即在想使用基类指针访问派生类对象时必须要声明基类虚析构函数,不管基类是否需要析构函数;

(2).因为虚函数表会占据一定的空间开销,在不存在上述1中情况时没有必要使用虚函数;

(3).多态性:因为编译器只做静态的语法检查,无法确定调用对象,运行时才确定关联关系,所以多态性又分为静态多态性和动态多态性。

  静态多态性(编译时的多态性,静态关联)是指在程序编译时就能够确定调用的是哪个函数,函数重载/运算符重载/通过对象名调用的虚函数都属于静态关联。

  动态多态性(运行时多态性,动态关联,滞后关联)是指只有在程序运行时才能够确定操作的对象,通过虚函数实现。

6.参考:

http://blog.chinaunix.net/uid-26851094-id-3327323.html

http://blog.sina.com.cn/s/blog_7c773cc50100y9hz.html

C++很“虚”的更多相关文章

  1. linux ubuntu 浏览器 字 字体 虚 解决办法

    在刚装好的ubuntu上什么都好,只是浏览器字体很虚,就连百度都虚 解决办法很简单 安装雅黑字体 修改font conf sudo vi /etc/fonts/conf.avail/69-langua ...

  2. 从HR 到SBP其实还有很长的一段路要走

    战略性业务伙伴 Strategic business partners 关于这本书,一般是因为好奇,从HR到BP的角色,再到这个SBP,其实是一段没有走过的很虚的过程,不过总归是需要灯塔,即使偶尔只是 ...

  3. c++运算符重载和虚函数

    运算符重载与虚函数 单目运算符 接下来都以AClass作为一个类例子介绍 AClass{ int var } 区分后置++与前置++ AClass operator ++ () ++前置 一般设计为返 ...

  4. Linux 桌面系统字体配置要略

    字体显示效果测试 这一段是为了测试宋体字的显示效果,包括宋体里面自带的英文字体,“This is english,how does it look like?”.这一行是小字.后面几个字是加粗的宋体. ...

  5. a版本冲刺第十天

    队名:Aruba   队员: 黄辉昌 李陈辉 林炳锋 鄢继仁 张秀锋 章  鼎 408: 十天体会:完成冲刺很开心,大家一起为同一件事情努力的感觉还是很不错的,众人拾柴火焰高,而且冲刺的时候会有一种压 ...

  6. 20145204&20145212信息安全系统实验三报告

    实时系统的移植 实验目的与要求 1.根据实验指导书进行实时软件的安装 2.配置实验环境,并对软件进行测试. 3.正确使用连接线等仪器,注意保护试验箱. 实验内容与步骤 1.连接 arm 开发板 连接实 ...

  7. PKUSC2016

    day x(x<0) 外出培训倒数第二天晚上发烧了....逃过了第二天早上的考试,orz 抢到rank 1 的commonc神犇!! day 0 下午到了北大,发了两张50元饭卡.这是第三次来北 ...

  8. 记一次项目中的css样式复用

    本文同步至微信公众号:http://mp.weixin.qq.com/s?__biz=MzAxMzgwNDU3Mg==&mid=401616238&idx=1&sn=3c6e9 ...

  9. 第五章 运输层(UDP和TCP三次握手,四次挥手分析)

    序言   通过这章,可以知道其实三次握手和四次挥手其实真的好简单,通过这章的学习,我相信你也会同样的认为,以后在也不需要听到别人问三次握手的过程而自己一脸懵逼了,觉得人家好屌,其实也就是他懂你不懂,仅 ...

随机推荐

  1. Android 数据加密算法 Des,Base64详解

    一,DES加密: 首先网上搜索了一个DES加密算法工具类: import java.security.*;import javax.crypto.*; public class DesHelper { ...

  2. Linux中断 - GIC代码分析

    一.前言 GIC(Generic Interrupt Controller)是ARM公司提供的一个通用的中断控制器,其architecture specification目前有四个版本,V1-V4(V ...

  3. CheckedComboBoxEditExtension

    public static class CheckedComboBoxEditExtension { public static void BindData(this CheckedComboBoxE ...

  4. python标准库介绍——8 operator 模块详解

    ==operator 模块== ``operator`` 模块为 Python 提供了一个 "功能性" 的标准操作符接口. 当使用 ``map`` 以及 ``filter`` 一类 ...

  5. .net数据库连接池(转载)

    如何实现连接池? 确保你每一次的连接使用相同的连接字符串(和连接池相同):只有连接字符串相同时连接池才会工作.如果连接字符串不相同,应用程序就不会使用连接池而是创建一个新的连接. 优点 使用连接池的最 ...

  6. 页面返回刷新或H5监听(手机的)返回键

    1. pushHistory(); window.addEventListener("popstate", function(e) { alert("我监听到了浏览器的返 ...

  7. php 多进程解决代码常驻内存的问题php 多进程解决代码常驻内存的问题

    PHP不适合做常驻的SHELl进程,因为它没有专门的gc例程,也没有有效的内存管理途径. 如果用PHP做常驻SHELL,会经常被内存耗尽导致abort而unhappy. 而且,如果输入数据非法,而脚本 ...

  8. A-Frame WebVR开发新手教程

    WebVR和WebGL应用程序接口使得我们已经能够在浏览器上创建虚拟现实(VR)体验.但从project化的角度而言,开发社区还须要很多其它方便强大的开发库来简化编程.Mozilla的 A-Frame ...

  9. Gcc\MingW\Cygwin\Msys简介

    一.GCC的历史GCC是一个原本用于Unix-like系统下编程的编译器.不过,现在GCC也有了许多Win32下的移植版本.所以,也许对于许多Windows开发者来说,GCC还是一个比较陌生的东西.所 ...

  10. WCF入门学习3-配置文件与部署iis

    配置文件设置 --------------------------------------------------- 创建的时候都会有个配置文件,其实有一个WCF配置编辑器,右键就可以点出来设置. 需 ...