我眼中的虚函数
同事问我有关虚函数的问题,我张了张嘴,不自觉的冒出继承,多态,后绑定,这些词,脑子里反复的问自己是这样吗,这些名称能解释清楚什么是虚函
数吗。这不是一个简单的问题,显然短短几个专业术语是解释不清楚的。问了问google,看了好多篇关于虚函数的帖子,都不是我眼中的虚函数解释,不是说他们说得不对,而是觉得如果向一个不怎么了解虚函数的人解释,总觉少了些什么,特别是向c程序员解释,我觉得会让他越发的糊涂,到最后只能找个比较全的例子,照着代码敲上一遍,知道虚函数怎么实现,怎么访问。但是我想说的是什么是虚函数,至少我没看到让我觉得满意的解释。
1.1函数签名
──────
在C++中调用C的函数需要用extern"C"来修饰函数名,这样保证C++编译器在连接的时候能正确的找到对应的C函数。为什么?C与C++编译器在编译函数签名的办法上有些不同,比如函数:voidfunc(){…},我们叫这个函数就叫func,但是C编译器不这样叫,他有自己的一套比如叫_func(下划线函数名),而C++也有自己的一套比如叫?func@4。可以这样理解:一个苹果,程序员叫它苹果,C就叫树上的苹果,C++叫2两重的苹果,extern的作用就是让C++使用C的叫法,为什么要这样做呢,因为函数func已经被C用C的叫法编译过了,名字
已经被改成C叫法写进编译文件里了,也就是说在编译后的文件里已经没有一个叫func名称的函数了。基于以上解释,可以理解为什么C没有函数重载,因为苹果目前只有树上才长,而C++可以叫3两重4两重…的苹果,虽然我们都叫苹果,但是C只有一个叫法,而C++有很多中叫法所以它有函数重载,具体点的解释就是函数func(int),func(float)在c里面只有_func一种叫法,如果超出一个就会重名了。而c++可以这样来区别?func@int,?func@float,所以他可以重载,其实所有的函数都是唯一的,只是我们只看函数的名字,而忽略了函数的参数而已,完整的函数命名包含了所有的这些信息。
1.2多态是什么
───────
书上的解释很清楚了,简单点来说就是调用同一个函数,随着调用对象的不同,而导致最终调用的函数不同。复杂点来说,我相信进化论,我们的手是最完美的进化产物,如果用继承的观点来解释就是,我们的手可以有鱼鳍的功能划水,可以有猴子手的功能攀爬,可以有人手的功能扒饭,根据调用的对象不同而使用不同的功能。继承树是这样,鱼>猴子>人,到我们人这层,手的功能继承了前两个的函数,划水和攀爬,进化出新的功能扒饭。如果用代码解释可以参考下面
classFish{
virtualvoidhand(Waterw){…}
};
classMonkey:publicFish{
virtualvoidhand(Waterw){…}
voidhand(Treet){…}
};
classHuman:publicMonkey{
voidhand(Ricer){…}
};
鱼和猴子划水的姿势显然是不同的,所以划水的"手"需要被重新定义,我用virtual来修饰鱼的hand,然后在猴子类中再重新定义让它有自己的一套。而人和猴子的划水姿势估计差不多,所以在不需要重新实现。想象一下,同样是函数"hand"他会根据调用的东西不同(水,树,米饭)而使用不同的实现是不是很神奇,但是具体神奇在那里呢或者说编译器是怎么做到的,来我接着说。
1.3为什么会有接口这个东西
─────────────
可以这样理解接口,它是通往外面的通道,手段,方法…具体点来说就像银行的柜员,不管你是存款,贷款,取现等等直接找他就搞定多方便。其实函数就是接口,你可能听过什么接口类,不过就是一个专门提供接口函数的类。你可能注意到银行会有很多个柜员,但是怎么快速的找到自己想要的柜员呢?这里有个小瑕疵不过可以忽略,我们可以假设一下,假设每个柜员只负责办理一种业务你怎么能快速找到你想要的柜员。这个时候柜员机出现了,想想你去银行是不是需要那身份证去挂个号,什么个人业务,综合业务,企业业等等,出来一张字条,是不是告诉你需要在哪个窗口等待,柜员机出来之前,好像可以问问大堂经理,或者保安对吧。我觉得柜员机才是银行真正的对外接口,柜员是各个业务的子接口,接着再抽象一下银行是个类,柜员机是个对外接口(函数)而柜员就是虚函数,为什么是虚函数,而不是正常函数,或者是重载函数。解释一下,其实银行这个类也是可以继承的,比如现在有什么类似私人银行,基金银行,或者其他什么银行,里面的柜员所负责的业务肯定又些许不同,而在调用者(客户)又不能看出他们有啥不一样,
正常函数不能覆盖(覆盖了,继承过来的业务会有影响),只能重载(会让人看出不同)所以虚函数是最佳叫法。好了,所需要的元素都出現了,银行类,柜员机(接口),柜员(虚函数),具体过程就是,不管客户是什么人,到银行,找到柜员机,告诉办理什么业务,然后柜员机告诉具体柜员,最后到指定的柜员(虚函数)前办理业务。如果用代码表示可以这样
classBank{
public:intguiyuanji(){返回柜员号};
virtualvoidguiyuan1(){};
virtualvoidguiyuan2(){};
virtualvoidguiyuan3(){};
};
试想把柜员机想象成一张表,里面放着业务和对应的柜员编号,你是编译器,然后有客户来办理业务,你会怎么处理回到上面一节,编译器是怎么实现的。虚函数会被放经一张表,然后会有一个柜员机(虚标指针)指向这个表,当客户端代码调用业务的时候,编译器会根据业务给客户一个编号代具体的表虚函数
vptr={存款:guiyuan1,取钱:guiyuan2,贷款:guiyuan3};
如这个代码
pBank->guiyuanji(存款,数量);
编译器会这样来处理,
pBank->vptr[存款](数量);
现在来解决多态,既然是多态,肯定涉及到继承,想象一下有个私人银行,他既办理普通一样的业务,比如天热吹个空调,天冷吹个空调,脚痛歇个脚之类,还负责办理私人的存款业务就是很特殊的那种(具体我也不清楚)反正跟普通银行不一样就对了。
如果您觉得本文的内容对您的学习有所帮助:
关键字:
html