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. Python学习笔记014——迭代器 Iterator

    1 迭代器的定义 凡是能被next()函数调用并不断返回一个值的对象均称之为迭代器(Iterator) 2 迭代器的说明 Python中的Iterator对象表示的是一个数据流,被函数next()函数 ...

  2. zabbix客户端安装和配置(linux)

    zabbix源码安装客户端 # tar -xvf zabbix-.tar.gz # mv zabbix- zabbix # cd zabbix # ./configure --prefix=/usr/ ...

  3. Linux中断 - 驱动申请中断API

    一.前言 本文主要的议题是作为一个普通的驱动工程师,在撰写自己负责的驱动的时候,如何向Linux Kernel中的中断子系统注册中断处理函数?为了理解注册中断的接口,必须了解一些中断线程化(threa ...

  4. 删除vs中最近的项目的方法

    Microsoft Visual Studio中可以自行设置显示多少个最近打开的项目,但有些时候会建个项目做测试,用完了就删了,却总显示在“文件”-“最近的项目”菜单中以及“起始页”-“打开现有项目” ...

  5. bootstrap 多元素共用 popover

    <div class="popover fade bottom in small" role="tooltip" id="gPopover&qu ...

  6. mysql 返回多列的方式

    SELECT * FROM (SELECT 'success' as _result) a,(SELECT @gid as gid) b;

  7. RabbitMQ 远程 IP 访问 解决办法 -摘自网络

    刚刚安装的RabbitMQ-Server-3.3.5,并且也已经开启了Web管理功能,但是现在存在一个问题: 出于安全的考虑,guest这个默认的用户只能通过http://localhost:1567 ...

  8. perl内置特殊变量查询

    perl中有许多预定于的内置变量,想$_,$,,$>,等等,基本是记不住全部的用法,如果在因特网查阅,有很麻烦,信息不准啦,说的不细啦,但是,万能的perldoc早就帮我们准备好了. 你需要做的 ...

  9. 微信H5支付.NET版本备忘

    微信H5支付.NET版本备忘

  10. Xilinx ISE Design Suite 14.7 ISim 简单仿真

    1.创建完项目(以Xilinx ISE Design Suite 14.7开发流程的例子    led例子   为例),编译通过,我们就可以对这个项目进行仿真: 2.然后切换到simulation,然 ...