在派生类中可以重新定义从基类继承下来的虚函数,从而提供该函数的适用于派生类的专门版本。也可能并不需要重新定义,在这种情况下,继承下来的虚函数仍然保持其在基类中的定义,即派生类和基类使用同一函数版本。除少数特殊情况外,在派生类中重定义虚函数时,函数名、形参表和返回值类型必须保持不变。
虚函数在派生类被重定义后,重定义的函数仍然是一个虚函数,可以在其派生类中再次被重定义。注意,对于虚函数的重定义函数,无论是否用virtual修饰都是虚函数。当然,最好不要省略virtual修饰,以免削弱程序的可读性。
对虚函数的调用有两种方式:非多态调用和多态调用。非多态调用是指不借助于指针或引用的直接调用。非多态调用总是通过成员访问运算符\进行的。与通常的成员函数调用类似,非多态调用是建立在静态绑定机制的基础之上的,不具备多态性特征。多态调用是指借助于指向基类的指针或引用的调用。在C++中,一个基类指针(或引用)可以用于指向它的派生类对象,而且通过这样的指针(或引用)调用虚函数时,被调用的是该指针(或引用)实际所指向的对象类的那个重定义版本。
基类中的实函数也可以在派生类中重定义,但重定义的函数仍然是实函数。在实函数的情况下,通过基类指针(或引用)所调用的只能是基类的那个函数版本,无法调用到派生类中的重定义函数。也就是说,尽管调用的语法形式可能是相同的,但对实函数的任何形式的调用都是非多态的。注意,无论是虚函数还是实函数,在派生类中被重定义后,原来的函数版本即被隐藏,在通过成员访问运算符 .直接调用该函数时,所调用的是重定义版本。但原来的版本依然存在,仍然可以通过在函数名前加域修饰(即:<类名>::)来调用它们。
3.虚析构函数
析构函数也可以通过virtual修饰而声明为虚函数。虚析构函数与一般虚函数的不同之处在于:
(1)重定义函数就是派生类的析构函数,不要求同名。
(2)一个虚析构函数的版本被调用执行后,接着就要调用执行基类版本,依次类推,直到调用执行了派生序列的最开始的那个虚析构函数版本为止。
通常,只要派生类中包含有虚函数的重定义(从而有可能被多态调用),而且对析函数进行了专门的声明(而不是不做任何声明,从而采用默认的析构函数),其基类的析构函数就应当声明为虚函数,否则就可能出问题。
4.纯虚函数与抽象类
在某些情况下,基类无法确定(或无法完全确定)一个虚函数的具体操作方式或内容,只能靠派生类来提供各个具体的实现版本。基类中的这种必须靠派生类提供重定义版本的虚函数称为纯虚函数。为了将一个虚函数声明为纯虚函数,需要在虚函数原型的语句结束符 ;之前加上=0。
拥有纯虚函数的类称为抽象类,抽象类不能用来定义对象。如果一个抽象类的派生类没有重定义来自基类的某个纯虚函数,则该函数在派生类中仍然是纯虚函数,这就使得该派生类也成为抽象类。也就是说,一个派生类可以把重定义纯虚函数的任务进一步转交给它自己的派生类。
可以在将一个函数声明为纯虚函数的同时,为该函数提供实现版本。换句话说,一个函数是否为纯虚函数,取决于其原形的尾部是否为“=0”,与实现版本的有无没有什么关系。拥有实现版本的纯虚函数仍然有赖于派生类提供重定义版本。纯虚函数的实现版本通常是不完善的版本,但包含了一些共有操作,供各个派生类在重定义函数中调用。派生类在重定义一个纯虚函数时,可以继续将之声明为纯虚函数。另外,纯虚函数不得声明为内联函数。
第8章 运算符重载考点归纳 8.1 运算符函数据与运算符重载
运算符重载是计算机语言固有多态性的体现,是构成计算机语言的基础之一。 C++把重载的运算符视为特殊的函数,称为运算符函数。运算符重载就是函数重载的一种特殊情况。像对待一般重载函数一样,编译系统能够依据使用运算符的不同环境,即参数(操作数)的数量或类型的差异,区分同一运算符的不同含义。
“运算符重载”是针对C++中原有运算符进行的,不可能通过重载创造出新的运算符。除了.、.*、->*、::、?:这五个运算符外,其他运算符都可以重载。由于很多符号是一元运算符和二元运算符公用的,为了避免含混,不得为重载的运算符函数设置默认值,调用时也就不得省略实参。
除了new和delete这两个较为特殊运算符以外,任何运算符如果作为成员函数重载时不得重载为静态函数。=、[ ]、()、->以及所有的类型转换运算符只能作为成员函数重载,而且不能是针对枚举类型操作数的重载。
运算符函数的函数名是由运算符前加关键字operator构成的,在声明运算符或调用运算符时都可以用这个名称。
8.2 典范运算符的重载 1.关于分数类fraction
fraction的声明和定义包含在头文件fraction.h和程序文件fraction.cpp中。 一个标准的用fraction表示的分数须满足以下复印件: ①分母永远为正,分数和符号用分子表示; ②分子分母互质,即总表示为最简分数。
Fraction通过两个私有数据成员num和den分别保存分子和分母,并在必要时调用standardize函数进行标准化处理,以使num和den的值满足标准分数的条件。Gcd是求两个整数的最大公约数的函数,standardize在化简分数时要调用它。 2.重载取负运算符“-”
因为fraction用分子的符号代表整个分数的符号,因此所谓“取负”只需对分子num取负就可以了。由于取负运算符“-”是一元运算符,当作为成员函数重载时参数表中没有参数,那个唯一的操作数以this指针的形式隐藏在参数表中。为此,只需要在fraction.h的类声明中增加:
fraction poerator -()const { return fraction(-num,den);}
就可以了。由于在类声明中直接给出了完整定义,因此是一个inline函数。
“-”是一个典型的一元运算符,除++、--外的其他一元运算符的重载都可以参考这里描述的方法。
3.重载加法运算符“+”
“+”是一个二元运算符,因此作为成员函数重载时参数表中只有一个参数,对应于第二操作数,而第一操作数就是对象本身,仅以this指针的形式隐藏在参灵敏表中。 “+”是一个典型的二元运算符,除赋值类运算符外的其他二元运算符的重载都可以参考这里描述的方法。 4.重载增1运算符“++”
++既可以是前缀运算符(前增1),又可以是后缀运算符(后增1)。为了区分这两种情况,重载这两个运算符时必须在格式上有所区别:重载后缀++时必须多一个虚拟参数:int,因此从形式上看像是一个二元运算符重载。 5.重载类型转换符“long”
类型转换符必须作为成员函数重载。在重载类型转换符时,由于运算符本身已经表示出返回值类型,因此不需要返回值类型的声明。一个分数可以看成是由一个整数部分和一个纯分数部分组成的,为了取得一个分数的整数部分,可为fraction重载类型转换符long。为此可在fraction.h的类声明中增加: opertator long()const { return num/den;} 6.重载赋值运算符“=”
赋值运算符只能作为成员函数重载。
常见的真正需要重载赋值运算符的情况是:类中包含指向动态空间的指针 赋值运算符=的重载应注意以下几点:
①返回值声明为引用,而函数体中总是用语句return *this;返回;
②如果参数被声明为指向同类对象的引用或指针,应判别所指向对象的是否与被赋值对象为同一对象,如果是,立即返回,不做任何赋值处理;
③如果被赋值对象占用了动态空间或其他资源,应首先释放这些资源,以便接收新的资源;
④如果参数被声明为指针或引用,通常应加上const修饰; ⑤如果参数被声明为指针,应判别是否为空,以便做出特殊处理;