深入理解C++虚函数和纯虚函数

虚函数正是为了对“如果你以一个基类之指针指向一个派生类之对象,那么通过该指针你就只能够调用基类所定义之成员函数”这条规则反其道而行的设计实现。

我们看下面一个简单例子,来获知虚函数的作用。
在这里插入图片描述
在这里插入图片描述
我们可以看到当未去掉virtual时,在main函数for循环内程序代码完全一样(因为一般化了,这种一般化将现在的、未来要派生的情况统统纳入考虑),但执行结果却不同,这就是虚函数的妙用。

从操作型定义来看,什么是虚函数呢?如果你预期派生类有可能重新定义某一个函数,那么你就在基类中把此函数设为virtual。

上例中我们以相同的指令却调用了不同的函数,这种性质称为Polymorphism(多态),意思是“the ability to assume many forms”(多态)。 编译器无法在编译时期判断pShape[i]->display()到底调用的哪一个函数,必须在执行期才能判断之,这称为后期绑定late binding 或动态绑定dynamic binding。至于C函数或C++ 的non- virtual函数,在编译时期就转换为一个固定地址的调用了,这称为前期绑定early binding或静态绑定static binding。

Polymorphism的目的,就是要让处理“基类之对象”的程序代码,能够完全无碍地继续适当处理“派生类之对象”。可以说,虚函数是了解多态(Polymorphism)以及动态绑定的关键。

再回到之前Shape例子。我们说CShape是个抽象类,现实中根本没有“形状”这个东西,是人们抽象出来的,所以它根本不该有display这个操作。也根本不能被CShape对象调用(CShape是抽象的)。我们根本就不应该定义它,不定义但又必须保留一块空间(spaceholder)给它,于是C++提供了所谓的纯虚函数概念。
纯虚函数如下写法: virtual void display() = 0;
纯虚函数不需要定义其实际操作,它的存在只是为了在派生类中被重新定义,只是为了提供一个多态接口。只要是拥有纯虚函数的类,就是一种抽象类,它是不能够被实例化(instantiate)的,也就是说,你不能根据它产生一个对象(你怎能说一种“形状”(Shape)的物体呢。)。

如果派生自抽象类的派生类也没有改写纯虚函数,那么派生类本身也就成为一个拥有纯虚函数的类,于是它也将成为一个抽象类。

对虚函数做如下总结:
1,如果你期望派生类重新定义一个成员函数,那么你应该在基类中把此函数设为virtual。
2,以单一指令调用不同函数,这种性质称为Polymorphism,意思是“the ability to assume many forms”,也就是多态。
3,虚函数是C++语言的Polymorphism性质以及动态绑定的关键。
4,既然抽象类中的虚函数不打算被调用,我们就不应该定义它,应该把它设为纯虚函数(在函数声明之后加上“=0”即可)
5,我们可以说,拥有纯虚函数者为抽象类(abstract Class),以别于所谓的具体类(concrete class)。
6,抽象类不能产生出对象实例,但是我们可以拥有执行抽象类的指针,以便于操作抽象类的各个派生类。
7,虚函数派生下去仍为虚函数,而且可以省略virtual关键字。

C++是怎么实现虚函数的呢?

为了达到动态绑定(后期绑定)的目的,C++编译器通过某个表格,在执行期“间接”调用实际上欲绑定的函数(注意“间接”这个字眼)。这样的表格成为虚函数表(常被成为vtable)。每一个“内含虚函数的类”,编译器都会为它做出一个虚函数表,表中的每一个元素都指向一个虚函数的地址。此外,编译器当然也会为类加上一项成员变量,是一个指向该虚函数表的指针(常被称为vptr)。

我的理解应该是虚函数表内存空间为类一级的,虚函数表指针为每个派生类对象独有的。都指向该派生类的虚函数表内存空间。

奥妙在于这个虚函数表以及这种间接调用方式。虚函数表的内容是依据类中的虚函数声明次序,一一填入函数指针。派生类会继承基类的虚函数表(这里应该指表内数据)也继承其他可以继承的成员,当我们在派生类中改写虚函数时,虚函数表就受了影响:表中元素所指的函数地址将不再是基类的函数地址,而是派生类的函数地址。

看下面例子
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
通过以上例子,明显可以看出vptr继承自基类并且vptr位于基类第一个成员变量,所以每个对象的开始地址才能放的都是vptr,同时也是基类开始地址。所以基类指针指向派生类对象时(将派生类对象地址赋给基类指针变量),虽然该指针只能操作派生类对象地址空间中基类内存空间部分,但是因为派生类改变了继承自基类的vptr指向,使其指向了派生类的虚函数表内存空间。所以现在基类指针指向的内存空间中的vptr指向的是派生类的虚函数表地址。最后当通过该基类指针调用虚函数时就调用成了派生类定义的函数实现了。

最后记录一个知识点,C++类的成员函数,你可以想象就是C语言中的函数。它只是被编译器改过名称,并增加了一个参数(this指针),因而可以处理调用者(C++对象)中的成员变量,也可以被C++对象调用(估计跟Csharp语法中一样,成为了类的扩展方法,只能先这样理解,后面看看是不是??、?因为对象内存块确实没记录成员函数的地址啊??那对象怎么能直接调用这个成员函数的?????)。所以,我们并不能在类对象的内存区块中看到与成员函数有关的任何东西。
在这里插入图片描述
sizeof(Class1)的话获取的值将是12.也就是两个成员变量加一个vptr的大小。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值