虽然很难找到一本不讨论多态性的C++书籍或杂志,但是,大多数这类讨论使多态性和C++虚函数的使用看起来很难。我打算在这篇文章中通过从几个方面和结合一些例子使读者理解在C++中的虚函数实现技术。说明一点,写这篇文章只是想和大家交流学习经验因为本人学识浅薄,难免有一些错误和不足,希望大家批评和指正,在此深表感谢!

在类中,如果什么都没有,则类占用1个字节,一旦类中有其他的占用空间成员,则这1个字节就不在计算之内,如一个类只有一个int则占用4字节而不是5字节。

如果只有成员函数,则还是只占用1个字节,因为类函数不占用空间

虚函数因为存在一个虚函数表,需要4个字节,数据成员对象如果为指针则为4字节,注意有字节对齐,如果为13字节,则进位到16字节空间。

一、 基本概念

首先,C++通过虚函数实现多态."无论发送消息的对象属于什么类,它们均发送具有同一形式的消息,对消息的处理方式可能随接手消息的对象而变"的处理方式被称为多态性。"在某个基类上建立起来的类的层次构造中,可以对任何一个派生类的对象中的同名过程进行调用,而被调用的过程提供的处理可以随其所属的类而变。"虚函数首先是一种成员函数,它可以在该类的派生类中被重新定义并被赋予另外一种处理功能。

二、 虚函数的定义与派生类中的重定义

class 类名{
public:
virtual 成员函数说明;
} class 类名:基类名{
public:
virtual 成员函数说明;
}
 

三、 虚函数在内存中的结构

1.我们先看一个例子:

#include "iostream.h"
#include "string.h" class A {
public:
virtual void fun0() { cout << "A::fun0" << endl; }
}; int main(int argc, char* argv[])
{
A a;
cout << "Size of A = " << sizeof(a) << endl;
return ;
}

结果如下:Size of A = 4

2.如果再添加一个虚函数:virtual void fun1() { cout << "A::fun" << endl;}

得到相同的结果。如果去掉函数前面的virtual修饰符

class A {
public:
void fun0() { cout << "A::fun0" << endl; }
}; int main(int argc, char* argv[])
{
A a;
cout << "Size of A = " << sizeof(a) << endl;
return ;
}

结果如下:Size of A = 1

3.在看下面的结果:

class A {
public:
virtual void fun0() { cout << "A::fun0" << endl; }
int a;
int b;
};
int main(int argc, char* argv[])
{
A a;
cout << "Size of A = " << sizeof(a) << endl;
return ;
}

结果如下:Size of A = 12

其实虚函数在内存中结构是这样的:

图一

在window2000下指针在内存中占4个字节,虚函数在一个虚函数表(VTABLE)中保存函数地址。在看下面例子。

class A {
public:
virtual void fun0() { cout << "A::fun0" << endl; }
virtual void fun1() { cout << "A::fun1" << endl; }
int a;
int b;
};
int main(int argc, char* argv[])
{
A a;
cout << "Size of A = " << sizeof(a) << endl;
return ;
}
结果如下:结果如下:

Size of A = 4

虚函数的内存结构如下,你也可以通过函数指针,先找到虚函数表(VTABLE),然后访问每个函数地址来验证这种结构,在国外网站作者是:Zeeshan Amjad写的"ATL on the Hood中有详细介绍"

图二

4.我们再来看看继承中虚函数的内存结构,先看下面的例子

class A {
public:
virtual void f() { }
};
class B {
public:
virtual void f() { }
};
class C {
public:
virtual void f() { }
};
class Drive : public A, public B, public C {
};
int main() {
Drive d;
cout << "Size is = " << sizeof(d) << endl;
return ;
}
结果如下:Size is = 12 ,相信大家一看下面的结构图就会很清楚,

图三

5.我们再来看看用虚函数实现多态性,先看个例子:

class A {
public:
virtual void f() { cout << "A::f" << endl; }
};
class B :public A{
public:
virtual void f() { cout << "B::f" << endl;}
};
class C :public A {
public:
virtual void f() { cout << "C::f" << endl;}
};
class Drive : public C {
public:
virtual void f() { cout << "D::f" << endl;}
}; int main(int argc, char* argv[])
{
A a;
B b;
C c;
Drive d;
a.f();
b.f();
c.f();
d.f();
return ;
}
结果:A::f
B::f
C::f
D::f

不用解释,相信大家一看就明白什么道理!注意:多态不是函数重载

6.用虚函数实现动态连接在编译期间,C++编译器根据程序传递给函数的参数或者函数返回类型来决定程序使用那个函数,然后编译器用正确的的函数替换每次启动。这种基于编译器的替换被称为静态连接,他们在程序运行之前执行。另一方面,当程序执行多态性时,替换是在程序执行期进行的,这种运行期间替换被称为动态连接。如下例子:

class A{
public:
virtual void f(){cout < < "A::f" < < endl;};
}; class B:public A{
public:
virtual void f(){cout < < "B::f" < < endl;};
};
class C:public A{
public:
virtual void f(){cout < < "C::f" < < endl;};
};
void test(A *a){
a->f();
};
int main(int argc, char* argv[])
{
B *b=new B;
C *c=new C;
char choice;
do{
cout< < "type B for class B,C for class C:"< < endl;
cin>>choice;
if(choice==''b'')
test(b);
else if(choice==''c'')
test(c);
}while();
cout< < endl< < endl;
return ;
}
在上面的例子中,如果把类A,B,C中的virtual修饰符去掉,看看打印的结果,然后再看下面一个例子想想两者的联系。如果把B和C中的virtual修饰符去掉,又会怎样,结果和没有去掉一样。 

7.在基类中调用继承类的函数(如果此函数是虚函数才能如此)

还是先看例子:

class A {
public:
virtual void fun() {
cout << "A::fun" << endl;
}
void show() {
fun();
}
}; class B : public A {
public:
virtual void fun() {
cout << "B::fun" << endl;
}
}; int main() {
A a;
a.show(); return ;
}
打印结果:A::fun 

在6中的例子中,test(A *a)其实有一个继承类指针向基类指针隐式转化的过程。可以看出利用虚函数我们可以在基类调用继承类函数。但如果不是虚函数,继承类指针转化为基类指针后只可以调用基类函数。反之,如果基类指针向继承类指针转化的情况怎样,这只能进行显示转化,转化后的继承类指针可以调用基类和继承类指针。如下例子:

class A {
public:
void fun() {
cout << "A::fun" << endl;
}
};
class B : public A {
public:
void fun() {
cout << "B::fun" << endl;
}
void fun0() {
cout << "B::fun0" << endl;
}
};
int main() {
A *a=new A;
B *b=new B;
A *pa;
B *pb;
pb=static_cast(a); //基类指针向继承类指针进行显示转化
pb->fun0();
pb->fun();
return ;
}
 参考资料:

1.科学出版社 《C++程序设计》

2.Zeeshan Amjad 《ATL on the Hood》

c++中虚函数的更多相关文章

  1. C++中虚函数的作用浅析

    虚函数联系到多态,多态联系到继承.所以本文中都是在继承层次上做文章.没了继承,什么都没得谈. 下面是对C++的虚函数这玩意儿的理解. 一, 什么是虚函数(如果不知道虚函数为何物,但有急切的想知道,那你 ...

  2. 关于C++与Java中虚函数问题的读书笔记

    之前一直用C++编程,对虚函数还是一些较为肤浅的理解.可近期由于某些原因搞了下Java,发现有些知识点不熟,于是站在先驱巨人的肩上谈谈C++与Java中虚函数问题. Java中的虚函数 以下是段别人的 ...

  3. C++中虚函数的作用是什么?它应该怎么用呢?(转)

    虚函数联系到多态,多态联系到继承.所以本文中都是在继承层次上做文章.没了继承,什么都没得谈. 下面是对C++的虚函数这玩意儿的理解. 一, 什么是虚函数(如果不知道虚函数为何物,但有急切的想知道,那你 ...

  4. C++中虚函数功能的实现机制

    要理解C++中虚函数是如何工作的,需要回答四个问题. 1.  什么是虚函数. 虚函数由于必须是在类中声明的函数,因此又称为虚方法.所有以virtual修饰符开始的成员函数都成为虚方法.此时注意是vir ...

  5. C++中虚函数的作用和虚函数的工作原理

    1 C++中虚函数的作用和多态 虚函数: 实现类的多态性 关键字:虚函数:虚函数的作用:多态性:多态公有继承:动态联编 C++中的虚函数的作用主要是实现了多态的机制.基类定义虚函数,子类可以重写该函数 ...

  6. C++中虚函数实现原理揭秘

            编译器到底做了什么实现的虚函数的晚绑定呢?我们来探个究竟.      编译器对每个包含虚函数的类创建一个表(称为V TA B L E).在V TA B L E中,编译器放置特定类的虚函 ...

  7. 【高级】C++中虚函数机制的实现原理

    多态是C++中的一个重要特性,而虚函数却是实现多态的基石.所谓多态,就是基类的引用或者指针可以根据其实际指向的子类类型而表现出不同的功能.这篇文章讨论这种功能的实现原理,注意这里并不以某个具体的编译器 ...

  8. c++中虚函数和纯虚函数定义

    只有用virtual声明类的成员函数,使之成为虚函数,不能将类外的普通函数声明为虚函数.因为虚函数的作用是允许在派生类中对基类的虚函数重新定义.所以虚函数只能用于类的继承层次结构中. 一个成员函数被声 ...

  9. C++ 在继承中虚函数、纯虚函数、普通函数,三者的区别

    1.虚函数(impure virtual) C++的虚函数主要作用是“运行时多态”,父类中提供虚函数的实现,为子类提供默认的函数实现. 子类可以重写父类的虚函数实现子类的特殊化. 如下就是一个父类中的 ...

随机推荐

  1. RabbitMQ 知识点

    mac下 rabbitmq 安装: brew命令 brew install rabbitmq 注意: rabbitmq的安装目录: /usr/local/Cellar/rabbitmq/3.6.6 r ...

  2. spring 核心思想:AOP 理解

    什么是AOP? AOP概念介绍 所谓AOP,即Aspect orientied program,就是面向方面(切面)的编程. 面向切面编程Aspect-Orlented-Programming,即AO ...

  3. c# ProgressBar进度条方向和美观

    protected override CreateParams CreateParams { get { CreateParams cp = base.CreateParams; cp.Style | ...

  4. js去除运营商或者路由器添加的广告脚本

    是不是偶尔发现在家里看网页的时候回插入一个广告,很烦人.开发的网站,上传到了阿里云 oss,设置了域名解析,但是在家里晚上访问的时候,总会在页面添加一个广告,导致页面卡主,一开始以为是路由器的问题,以 ...

  5. 如何检测NFC芯片型号?NFC手机即可!

    拿到了NFC标签之后,因为很多项目中的需求,用户需要对自已的NFC芯片进行选型,也就需要判断NFC芯片的类型?芯片是原装进口还是国产兼容的?芯片内存有多少?芯片存储内部结构如何......,而且用户还 ...

  6. js操作cookie(转载:经测试可用)

    /***js操作cookie,star***/ function addCookie(objName,objValue,objsec){//添加cookie  var str = objName + ...

  7. 小峰mybatis(3)mybatis分页和缓存

    一.mybatis分页-逻辑分页和物理分页: 逻辑分页: mybatis内置的分页是逻辑分页:数据库里有100条数据,要每页显示10条,mybatis先把100条数据取出来,放到内存里,从内存里取10 ...

  8. [转]NSIS:使用SectionSetFlags根据不同环境自动勾选特定区段

    转自: http://www.flighty.cn/html/bushu/20140526_232.html   在微软SQL2000+SP4集成安装版安装包中可以根据目标操作系统自动勾选对应的版本, ...

  9. [转]将字体嵌入程序资源中 C# Winform

    http://social.msdn.microsoft.com/Forums/officeapps/zh-CN/61b717ae-f925-443a-baad-2b85f2564826/cwinfo ...

  10. 将ESXI所有的端口组迁移到分布式交换机的步骤

    1.如果是DELL服务器,一般有2-4个网口,那么所有的网口都把网线插到交换机上:2.DELL安装ESXI系统,根据不同的DELL硬件,要安装不同的ESXI版本.原则上越高版本,支持的硬件越多向下兼容 ...