类中成员函数的重载、覆盖与隐藏
函数重载
定义:函数名一样,参数不同
特征:一个类中 + virtual可有可无 + 同名函数 + 参数不同
(1) 相同的范围 ( 在同一个类中 )
(2) 函数名字相同
(3) 参数不同
(4) virtual 关键字可有可无
举例:
class Base
{
public:
void f(int x);
void f(float x);//函数重载
};
c++按下列三个步骤的先后顺序匹配并调用重载函数:
(1) 寻找一个严格匹配,如果找到了,就用那个函数。
(2) 通过相容类型的隐式转换寻求一个匹配,如果找到了,就用那个函数。
(3) 通过用户定义的转换寻求一个匹配,若能查出有唯一的一组转换,就用那个函数。
注意:
(1) 重载函数的调用,在编译期间就已经确定了(静态绑定),是静态的。
(2) 重载和多态无关!
函数覆盖 或 改写 或 重写:
定义:指派生类函数覆盖基类函数
产生的原因:为了实现多态。
前提:需要使用指向派生类的基类指针调用该函数
函数特征:两个类中 + virtual + 函数声明完全相同<函数名相同 + 参数相同>
(1) 不同的范围 ( 分别位于派生类与基类 )
(2) 函数名字相同
(3) 参数相同
(4) 基类函数必须有virtual 关键字
注意:
(1) 重写函数的调用是在运行时确定的 (动态链接)
过程:当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。
(2) 和多态真正相关
(3) 为什么要求派生类的函数外形和基类的函数外形完全相同?
这是因为:在构成多态时,是使用一个指向派生类对象的基类指针或引用调用相应函数的,这些函数完成的功能大体上都是一样的,但是它们由基类的函数提供一个函数外形,并在派生类中就按照这个外形查找,找到就覆盖虚函数表中的基类函数,使得在运行中调用该函数时,能直接调用派生类的函数。所以,为了能按照基类的这个虚函数的外形查找,就必须要求派生类的该函数的外形和基类的外形完全一样。
注意,实现函数重写(多态)的条件有三个,依次是 基类指针指向派生类 + 被调用函数在基类中是虚函数 + 基类和派生类中函数同名。
如果被调用的函数只满足后两个,即 被调用函数在基类中是虚函数 + 基类和派生类中函数同名,但不是使用指向派生类的基类指针调用,而是用派生类指针调用,此时仍会调用派生类函数,但不是函数重写,而是函数隐藏。
函数隐藏
定义:派生类的函数 屏蔽了 基类中的其同名函数
产生的原因:派生类和基类中具有同名函数,但是又不发生多态,此时派生类仅从函数名区分不了,而且区分时没看参数,这是没办法了,就想着仅仅调用自己的成员吧,把基类中的成员忽视吧,而产生了函数隐藏。
前提:需要使用派生类指针调用该函数
特征,派生类和基类中的函数,只要是 基类和派生类的函数名一样 而且 不是覆盖 就为隐藏。
由于函数覆盖的条件是:基类函数为虚函数 + 基类函数和派生类函数外形完全一样。
则反面就是包括两种
(1) 基类函数声明为虚函数,但是基类和派生类的函数外形不一样
(2) 基类函数没有声明为虚函数,而且基类和派生类的函数外形不一样
(2) 基类函数没有声明为虚函数,但是基类和派生类的函数外形一模一样。
说明:
具有继承关系的指针调用函数时的调用规则:
(1) 基类指针指向基类时,基类指针调用自己的成员
(2) 派生类指针指向派生类时(不发生多态,有可能发生隐藏),
函数不发生隐藏时 (该函数在基类和派生类中不同名),派生类指针既可以调用自己的成员,也可以调用基类成员
函数发生隐藏时 (该函数在基类和派生类中同名),派生类指针只能调用自己的成员,而看不到基类成员
(3) 基类指针指向派生类对象时,(有可能发生多态,不发生隐藏)
如果调用的函数满足多态要求(虚函数+ 函数外形一模一样),则基类指针调用派生类中的函数
如果调用的函数满足多态要求,则基类指针调用基类自己的函数
注意,基类指针调用的函数有两种:自己的函数 + 满足多态的函数
具体来说,基类指针调用自己的成员是默认的,而调用满足多态的函数是为了实现多态而添加的,一般基类指针是不能直接访问派生类自己独有的成员的(这里派生类自己的独有的成员不是指从基类中继承来的)。
举例
举例(一)
情况:在基类和派生类之间,不发生函数隐藏和函数覆盖的情况,这是很一般的情况
即,此时要求该函数在基类和派生类中函数名都不同。
此时,基类指针调用自己的成员,派生类指针指向自己成员(包含自己独有的成员和从基类中继承的成员)
#include <iostream>
using namespace std;
class Base
{
public:
void BaseA()
{cout<<"Base::BaseA"<<endl;}
};
class Derived : public Base
{
public:
void DerivedA()
{cout<<"Derived::DerivedA"<<endl;}
};
int main()
{
Base base;
Derived derived;
Base* pBase = &base;
Base* pBaseDerived = &derived;
Derived* pDerived = &derived;
//基类指针指向基类的情况
pBase->BaseA(); //基类指针调用自己的成员
//基类指针指向派生类且不发生多态的情况
pBaseDerived->BaseA();//基类指针调用基类的成员
//pBaseDerived->DerivedA();//错误,基类指针看不到派生类成员
//派生类指针指向派生类且不发生隐藏的情况
pDerived->DerivedA();//Derived::DerivedA,派生类指针调用派生类的成员
pDerived->BaseA(); //Base::BaseA,派生类指针调用基类的成员
system("pause");
return 1;
}
举例(二)
情况:函数在基类和派生类之间,发生函数隐藏和函数覆盖的情况
即,此时基类和派生类之间函数名相同。
#include <iostream>
using namespace std;
class Base
{
public:
void Basef()
{
cout<<"Base::Basef()"<<endl;
}
virtual void A(int i)
{
cout<<"Base::A(int i)"<<endl;
}
void B(double f)
{
cout<<"Base::B(double f)"<<endl;
}
void C()
{
cout<<"Base::C()"<<endl;
}
};
class Derived : public Base
{
public:
void Derivedf()
{
cout<<"Derived::Derivedf()"<<endl;
}
void A(int i) //覆盖了基类的A函数
{
cout<<"Drived::A(int i)"<<endl;
}
void B(int c) //隐藏了基类的B函数
{
cout<<"Drived::B(int c)"<<endl;
}
void C() //隐藏了基类的C函数
{
cout<<"Drived::C()"<<endl;
}
};
int main()
{
int nNum = 1;
double dNum = 2.2;
Base base;
Derived derived;
Base* pBase = &base;
Base* pBaseDerived = &derived;
Derived *pDerived = &derived;
//基类指针指向基类时,只调用基类自己的函数
pBase->A(nNum); //Base::A(int i)
pBase->B(dNum); //Base::B(double f)
pBase->C(); //Base::C()
cout<<endl;
//假如基类指针指向派生类且不发生多态以及隐藏的情况
//也不可能发生隐藏,隐藏是派生类隐藏基类成员,必须是派生类指针
//只调用派生类从基类继承的成员
pBaseDerived->Basef();//Base::Basef()
pBaseDerived->B(nNum);//Base::B(double f)
pBaseDerived->B(dNum);//Base::B(double f)
pBaseDerived->C();//Base::C()
//pBaseDerived->Derivedf();//错误,基类指针看不到派生类成员
cout<<endl;
//基类指针指向派生类且发生多态的情况
//调用派生类的成员(虚函数)
pBaseDerived->A(nNum);//Drived::A(int i)
pBaseDerived->A(dNum);//Drived::A(int i)
cout<<endl;
//派生类指针指向派生类时且不发生多态以及隐藏的情况,调用派生类和基类中自己的函数
//即派生类指针不调用基类和派生类中的同名函数
//注意:派生类指针指向派生类时也不可能发生多态
pDerived->Basef(); //Base::Basef()
pDerived->Derivedf(); //Derived::Derivedf()
cout<<endl;
//派生类指针指向派生类时发生隐藏的情况,调用派生类自己的函数
//注意,虽然函数A是虚函数,但是由于是派生类指针调用的,也不属于发生多态
//虽然派生类继承了基类中的函数B,而且参数也不同,虽然调用时仍是调用派生类的函数,这里要进行参数转换
pDerived->A(nNum);//Drived::A(int i)
pDerived->A(dNum);//Drived::A(int i)
pDerived->B(dNum);//Drived::B(int c)
pDerived->B(nNum);//Drived::B(int c)
pDerived->C();//Drived::C()
system("pause");
return 1;
}
总结说明:
1、基类指针指向基类,只调用基类的成员
2、基类指针指向派生类(有可能发生多态)
如果函数不发生多态,则调用基类成员(派生类从基类中继承的成员)
如果函数发生多态,则调用派生类中发生多态的虚函数
一般情况下,基类指针看不到派生类成员,只能使用自己的成员,除非是发生多态的虚函数
3、派生类指针指向派生类,(肯定不发生多态,有可能发生隐藏)
如果是不同名的函数(不发生隐藏),派生类可以调用基类函数和派生类函数
如果是同名函数(发生隐藏),派生类只调用派生类的函数(此时只要函数名一样,一定是隐藏)
具体来说:
1、在不发生多态时,调用哪一个函数是由指针类型或者对象类型确定的
对于基类和派生类中的不同名函数:(不发生隐藏和多态)
(1) 基类指针指向基类对象,基类指针调用函数时,直接调用基类函数
(2) 基类指针指向派生类对象时,基类指针调用函数时,直接调用派生类从基类中继承的函数 ( 其实还是基类函数 )
(3) 派生类指针指向派生类对象时,派生类指针调用函数时,既可以调用派生类的函数,也可以调用基类的函数
对于基类和派生类中的同名函数:(发生隐藏):
(1) 基类指针指向基类对象,基类指针调用函数时,直接调用基类函数
(2) 基类指针指向派生类对象时,基类指针调用函数时,直接调用派生类从基类中继承的函数 (其实还是基类函数)
(3) 派生类指针指向派生类对象时,派生类指针调用函数时,直接调用派生类自己的函数,此时基类函数被自己的函数隐藏。
2、在发生多态时,指针指向自己的指针时调用自己的函数,基类指向派生类的指针调用派生类的函数调用
(1) 基类指针指向基类对象,基类指针调用函数时,直接调用基类函数
(2) 基类指针指向派生类对象时,基类指针调用函数时,直接调用派生类自己的函数 (派生类函数对基类函数进行覆盖)
(3) 派生类指针指向派生类对象时,派生类指针调用函数时,直接调用派生类自己的函数,此时基类函数被自己的函数隐藏。