函数重载只会发生在同一个类中,函数名相同,只能通过参数类型,参数个数或者有无const来区分。不能通过返回值类型区分,而且virtual也是可有可无的,即虚函数和普通函数在同一类中也可以构成函数重载。
基类和派生类中只能是隐藏或者覆盖。
1)隐藏是指派生类中有函数与基类中函数同名,但是没有构成虚函数覆盖,就是隐藏。
隐藏的表现:若基类中函数func()被派生类中函数func()隐藏,那么无法通过派生类对象访问基类中的func() 函数,派生类对象只能访问到派生类中的func()函数。不过基类中的func()确实继承到了派生类中。
2)虚函数也只是在基类和派生类中发挥多态的作用,而在同一类中虚函数也可以重载。
虚函数实现多态的条件:
a 基类中将这些成员声明为virtual。
b 基类和派生类中的这些函数必须同名且参数类型,参数个数,返回值类型必须相同。
c 将派生类的对象赋给基类指针或者引用,实现多态。
缺少任何一条,只会是基类和派生类之间的隐藏,而不是覆盖
3) 如何判断基类和派生类中函数是否是隐藏?
当基类和派生类存在同名函数,不论参数类型,参数个数是否相同,派生类中的同名函数都会将基类中的同名函数隐藏掉。
a 基类和派生类都是虚函数,并且同名,但是形参类型或者形参个数不同,多态不满足,但是构成了隐藏,只是没有虚特性。
b 基类中不是虚函数,派生类中定义为虚函数,不构成多态,只是隐藏关系。
c 基类和派生类的两个函数同名,都是虚函数,形参的个数和类型也都相同,但是返回值类型不同,这时编译会报错,因为两个虚函数在隐藏时,返回值类型发生了冲突,因此隐藏发生错误。注意,如果这两个函数不是虚函数,这不会报错,隐藏会成功;同时,如果派生类中是虚函数,基类中不是虚函数,也不过报错,隐藏也是成功的。但是如果基类中为虚函数,派生类中不是,也会报错。这些说明,虚化并隐藏时,返回值类型一定要保持相同。
虚函数要求返回值类型也一样,但是有一种情况允许虚函数返回值时本类对象的引用或者指针,也可以构成覆盖。这个是“协变”规则,具体协变看例子:
class A: { public: virtual A* func() { cout<<"A"<<endl; return this; } }; class B:public A { public: virtual B* func() { cout<<"B"<<endl; return this; } }; int main() { A *pa=new B; B* pb=pa->func();//编译无法通过,因为pa是A*类型指针,编译时,对于pa->func()翻译成调用的是A类的函数,返回值为 A*类型。而A*类型无法赋值给派生类指针 B* pb=(B*)pa->func();//正确 B* pb=(B*)(pa->func());//正确 }
A *pa=new B;对于虚函数将基类指针指向派生类对象,调用派生类的虚函数。该基类指针能解引用的内存空间是继承到派生类中的基类的内存空间。基类指针调用派生类的虚函数,在虚函数中,this指针指向的是派生类本身,也就是在虚函数中将基类指针强制转换成了派生类指针。其实基类指针pa和派生类中的this指针值相同,都是派生类对象的地址。
协变的存在是为了解决返回值的强制类型转换,真正用途是,通过派生类对象调用虚函数,直接返回派生类指针。若无协变,则会返回基类指针,需要再将基类指针强制转换成派生类指针。具体的意思看例子:
若没有协变,那么上述的代码中派生类中虚函数需要改成以下形式: class B :public A { public: virtual A* func() { cout<<"B"<<endl; return this;//返回值this为B*类型指针,但是因为没有协变,返回的时候将B*类型赋值给了A*类型,然后以A*类型返回到main函数中 } }; int main() { B b; A *pa=b.func(); B *pb=dynamic<B*> (pa);//将返回的A*类型强制转换成B*类型 }