高质量C++编程_第8章_C++函数的高级特性(2)

类中成员函数的重载、覆盖与隐藏

函数重载

定义:函数名一样,参数不同

特征:一个类中 + 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) 派生类指针指向派生类对象时,派生类指针调用函数时,直接调用派生类自己的函数,此时基类函数被自己的函数隐藏。


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值