虚函数 多态 函数重载[转载]
函数重载
如何讲函数重载:
What——函数重载是什么?
why——为什么要用函数重载,没有函数重载会怎样?
how——举例说明怎么使用函数重载
*******************************************************************************
能使名字方便使用,是任何程序设计语言的一个重要特征。
当我们创建一个对象(即变量)时,要为这个存储区取一个名字。一个函数就是一个操作的名字。正是靠系统描述各种各样的名字,我们才能写出易于人们理解和修改的程序。象写文章一样,目的在于如何同读者交流。这里就产生了这样一个问题:如何把人类自然语言的有细微差别的概念映射到编程语言中。
通常,自然语言中同一个词可以代表许多种不同的含义,这要依赖上下文来确定。这就是所谓的一词多义,从编程的角度来说就是该词被重载了。所谓重载,从词义上说就是重新载入,用句俗话说就是“换汤不换药”。要学会试着理解这一点,这点非常有用,特别是对于细微的差别。具个例子:
我们可以说“喝可乐,喝酒”。如果非得说成“喝(喝可乐的喝)可乐,喝酒(喝酒的喝)酒”,那将是很愚蠢的,就好像听话的人对指定的动作毫无辨别能力一样。管他和的么事,总不是让液体进入你的体内,至于么样喝,喝了以后起么样的反应,那就是后话了。
然而大多数编程语言要求我们为每个函数设定一个唯一的标识符。如果我们想打印三种不同类型的数据:整型、字符型和实型,我们通常不得不用三个不同的函数名,如p r i n t _ i n t ( )、p r i n t _ c h a r ( )和p r i n t _ f l o a t ( ) ,这些既增加了我们的编程工作量,也给读者理解程序增加了困难。举个吃饭的例子,你桌子上面有三盘菜,你吃第一盘菜要用金筷子,第二盘,要用银筷子,第三盘要用象牙筷子一样,几麻烦,用个方便筷子不是一样的吃了。
说白了,那双方便筷子就是一个函数重载的实例。
在C + +中,还有另外一个原因需要对函数名重载:构造函数。因为构造函数的名字预先由类的名字确定,所以只能有一个构造函数名。但如果我们想用几种方法来创建一个对象时该怎么办呢?例如创建一个类,它可以用标准的方法初始化,也可以从文件中读取信息来初始化,我们就需要两个构造函数,一个不带参数(缺省构造函数),另一个带一个字符串作为参数,以表示用于初始化对象的文件的名字。(例子:两双方便筷子,一双是新的刚那出来的,另一个是高温消毒好了的!但都是方便筷子。)所以函数重载的本质就是允许函数同名。在这种情况下,构造函数是以不同的参数类型被调用的。
重载不仅对构造函数来说是必须的,对其他函数也提供了很大的方便,包括非成员函数。
另外,函数重载意味着,我们有两个库,它们都有一个同名的函数,只要它们的参数不同就不会发生冲突。
我这里讲的主题不是函数重载,而是让大家如何方便是使用“筷子”(函数名)去取用餐桌上的“美味食物”(调用函数)!
函数重载允许多个函数同名(象多种形式的筷子一样),但还有另一种方法使函数调用更方便。如果我们想以不同的方法(象很多不同的手法那筷子一样,左手拿,右手拿)调用同一函数,该怎么办呢?当函数有一个长长的参数列表,而大多数参数每次调用都一样时,书写这样的函数调用会使人厌烦,程序可读性也差。C + +中有一个很通用的作法叫缺省参数。缺省参数就是在用户调用一个函数时没有指定参数值而由编译器插入参数值的参数。(象吃了饭,筷子碗一丢,不想洗碗筷,让你父母来洗一样。)
这样f (“h e l l o”) , f (“h i”, 1 )和f (“h o w d y”, 2 ,‘c’)可以用来调用同一函数。它们也可能是调用三个已重载的函数,但当参数列表相同时,我们通常希望调用同一函数来完成相同的操作。
关于函数重载更深层的说明(编译器)
void print(char);
void print(float);
无论这两个函数是某个类的成员函数还是全局函数都无关紧要。如果编译器只使用函数名字的范围,编译器并不能产生单一的内部标识符,这两种情况下都得用_ p r i n t结尾。(就象一双方便筷子,光看握筷子的手,我们谁都不晓得他夹了什么菜。)
重载函数虽然可以让我们有同名的函数,但这些函数的参数列表应该不一样。所以,为了让重载函数正确工作,编译器要用函数名来区分参数类型名。上面的两个在全局范围定义的函数,可能会产生类似于_ p r i n t _ c h a r和_ p r i n t _ f l o a t的内部名。(看到他夹了么菜了)
因为,为这样的名字分解规定一个统一的标准毫无意义,所以不同的编译器可能会产生不同的内部名。
重载的函数与具有多态性的函数(即虚函数)不同处在于:调用正确的被重载函数实体是在编译期间就被决定了的;而对于具有多态性的函数来说,是通过运行期间的动态绑定来调用我们想调用的那个函数实体。多态性是通过重定义(或重写)这种方式达成的。请不要被重载(overloading)和重写(overriding)所迷惑。重载是发生在两个或者是更多的函数具有相同的名字的情况下。区分它们的办法是通过检测它们的参数个数或者类型来实现的。
*********************************************************
虚函数
什么是虚函数???
虚函数是指一个类中你希望重载的成员函数,当你用一个基类指针或引用指向一个继承类对象的时候,你调用一个虚函数,实际调用的是继承类的版本。
——摘自MSDN
什么是多态???
多态是面向对象程序设计和面向过程程序设计的主要区别之一,何谓多态?一名俗话说:“龙生九子,子子不同”多态就是同一个处理手段可以用来处理多种不同的情况。
这里我们主要讨论虚函数的格式、条件(什么样的函数才叫虚函数)、调用虚函数时的注意事项。
虚函数是成员函数,而且是非static的成员函数。说明虚函数的方法如下:
virtual <类型说明符><函数名>(<参数表>)
其中,被关键字virtual说明的函数称为虚函数。
如果某类中的一个成员函数被说明为虚函数,这就意味着该成员函数在派生类中可能有不同的实现。(例:一句俗话就是“聋子的耳朵——摆设”,就象一个残疾人士,可能他听不见,但不代表他儿子听不见,我们一般把基类叫做“父类”,派生类叫做“子类”。但我们不能说“龙生龙,凤生风,老鼠生儿会打洞”啊!他老爸的耳朵是摆设,但在儿子这里就是接受信息的器官了。)
(1) 与基类的虚函数有相同的参数个数;
(2) 其参数的类型与基类的虚函数的对应参数类型相同;
(3) 其返回值或者与基类虚函数的相同,或者都返回指针或引用,并且派生类虚函数所返回的指针或引用的基类型是基类中被替换的虚函数所返回的指针或引用的基类型的子类型。(就像他老爹不识字,但是他可以让他读大学的儿子来带他看信,读报一样)
一般要求基类中说明了虚函数后,派生类说明的虚函数应该与基类中虚函数的参数个数相等,对应参数的类型相同,如果不相同,则将派生类虚函数的参数的类型强制转换为基类中虚函数的参数类型。
抽象类
带有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。
抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根,相关的子类是从这个根派生出来的。(例:就是一个手机模具)
抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。
抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。
一个实例:
class 人()
{public :
//......
void 吃()
{人吃饭;
}
//......
char *Name;
int age;
};
class 狗()
{public :
//......
void 吃()
{狗吃屎;
}
//......
char *Name;
int age;
};
人类、狗类有一些相同的东东,如名字,年纪,吃的动作等,有人想到了为了代码的重用,让人类继承狗类,可是数据的冗余让这个想法完蛋了,所以有人又想出可不可以定义一个这样的类:
这个类界于人类狗类之间,有人类和狗类共有的一些东东,比如年纪,名字,体重什么的,但它是不存在实例的,它的存在意义也是只是为了派生其它类,如人类、狗类,这样可以使系统清淅、。。。。。、、反正好处多多。
在这个世界上根本不存在界于人狗之间的东东,所以这个“人狗之间类”中的“吃”也是没什么意义,你也很难为它的内容下个定义,况且也没必要,所以定义它为纯虚函数,形式为:virtual void 吃()=0; 用来告诉系统:
1、这个函数可以没有函数定义;
2、拥有本函数的类是抽象类;
你可能会说,即然纯虚函数没什么意义,为什么还要它,它的作用是什么呢?
为实现多态作准备!!!
虚函数的作用:
之一: 虚实同型
之二: 相伴永远
之三: 有则用之,无则虚之
关于二义性的单独说明:
什么是二义性?从字面上来说就是产生的歧义,为什么会产生歧义?这里我举个例子:象你到商店去买烟,都是黄鹤楼,有150,24,18三个价位的,都是烟,也就是说烟是他们的基类,而你要买哪包抽呢?跟跟老板将:“拿包黄鹤楼!”他哪知道你买多少钱的烟?这里就产生了二义性!同时也引出了二义性的解决方法!老板就会问你:“买多少钱一包的!”这里你就可以做选择了!但是编译器不会开口问你调用哪个父类的成员函数!所以你要事先说明!(y::gety())
总结:
用个例子字来总结,函数重载,是静态的多态(静态联编),你可以想象成为,你去餐馆吃饭,吃着不同的菜(不同数量,不同类型的参数),用的是同一双筷子(函数名),自己动手吃,不能指使别人帮你实现功能(自己动手,风记足食)。而虚函数,是动态的多态(动态联编),你则可以想象为,你要么是儿孙满堂的老人或者是有钱的款爷,到饭店吃大餐,自己不动手,而且吃得很讲究,不同的食物要用不同的餐具(不同的对象),而且是使唤别人(子类成员函数实现功能)喂你吃。你只要指使一下(基类指针或引用指向)就OK了。在别人看来,你也是在吃饭(具有相应的功能),但是你根本没有动手,都是要手下的小弟搞定(子类成员函数实现功能)。