一、重载
如果顶层函数有不同的参数,它们的函数名可以相同
在一个类中,成员函数可以有相同的函数名,只要函数签名不同(函数名、参数个数
参数类型)即可,这种情况称为重载,重载与编译器绑定对应,不管是成员函数还是顶层函数。例
void f()
void f(int x)
则调用时f(); f(3)会自动识别调用的是哪一个函数
而
二、覆盖
1、在典型的多态情况下,派生类的虚函数覆盖了从基类继承来的虚函数,要形成覆盖,成员函数必须为函数签名相同的虚函数与运行期绑定相对应。例
class B{
public:
virtual void m1(){}
virtual void m2(){}
};
class D:public B(){
public:
virtual void m1(){}
};
B:m2()与D:m2()对应着相同的入口地址,以为派生类中没有定义m2(),直接由基类继承而来。但B:m1()与D:m1()对应着不同的入口地址,因为在基类和派生类中均有定义,派生类中m1()覆盖了基类中的m1()。因此p->m1()调用哪个m1()取决于p指针指向哪个谁的对象。这里需要被重提的是,基类类型的指针可以指向任何基类对象或派生类对象,对象属于哪个类并不取决于指针类型,而取决于赋给指针的是动态分配(new)的哪个对象的地址。例如B* p声明的是一个B类型指针,p=new D表明将动态分配的B的对象的地址赋给p。那么p->m1()则会调用D中的m1()。到这里就大概说完了多态下(虚成员函数)的覆盖问题。
2、继承中的非虚函数情况,对应着编译期绑定,取决于指针的数据类型而不是指向的对象。例
class B{
public:
void m1(){}
};
class D:public B(){
public:
void m1(){}
};
int main(){
B* p;
p=new D;
p->m1();
}
p->m1();调用的是B中函数,因为p是B类型的指针,编译期即绑定了入口地址,后面动态声明已无济于事。
三、遮蔽
如果基类B拥有一个非虚函数m,其派生类D也有一个成员函数m,我们就说D::m遮蔽了继承而来的函数B::m.如果派生类的同名成员函数与其基类的这个成员函数有不同的函数签名,那么这种遮蔽情况将会相当复杂。例
1、非虚函数的情况
class B{
public:
void m1(int x){}
};
class D:public B(){
public:
void m1(){}
};
int main(){
D* p;
p.m1();
p.m1(3);//ERROR!!!!!!!!
}
p.m1(3);会发生编译错误,因为D继承了基类中的m1(int x),但是D定义的本地同名函数m1()遮蔽了基类中的m1(int x)。只有通过p.B::m1(3);才可以调用。
2、虚函数的情况。
虚函数和非虚函数都有可能产生名字遮蔽,实际上一旦派生类的虚函数不能覆盖基类的虚函数,就会产生虚函数遮蔽。
class B{
public:
virtual void m1(int x){}
virtual void m2(){}
};
class D:public B(){
public:
virtual void m1(){}
再用D的对象去调用m1(23);是会发生编译错误的,因为发生了遮蔽
其实与覆盖最需要区分的一点就是虚函数的函数签名是否相
四、名字共享
名字共享指的是函数名相同但函数签名不同的函数。下面是使用名字共享的举例:
重载函数名的顶层函数。即使用一个函数名(print)就可以执行不同的函数体。
重载构造函数,一个类有几个构造函数,也需要重载。
非构造函数是同一个类中名字相同的成员函数。
继承层次中的同名函数(特别是虚函数)