C++多态和抽象类


Reference:

  1. 明日科技 《零基础学 C++》

1. 多态

多态性(polumorphism) 是面向对象程序设计的一个重要特征,利用多态性可以设计和实现一个易于扩展的系统。在 C++ 语言中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。当发出同样的消息被不同类型的对象接收时,导致完全不同的行为。这里所说的消息主要指类的成员函数的调用,而不同的行为是指不同的实现。

多态性通过联编实现。联编是指一个计算机程序自身彼此关联的过程。按照联编所进行的阶段不同,可分为两种不同的联编方法:静态联编动态联编。在 C++ 中,根据联编的时刻不同,存在两种类型多态性,即函数重载虚函数

1.1 虚函数概述

在类的继承层次结构中,在不同的层次中可以出现名字、参数个数和类型都相同而功能不同的函数。编译器按照先自己后父类的顺序进行查找覆盖,如果子类有父类相同原型的成员函数时,要想调用父类的成员函数,需要对父类重新引用调用。虚函数则可以解决子类和父类相同原型成员函数调用问题:
虚函数允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数

在基类中用 virtual 声明成员函数为虚函数,在派生类中重新定义此函数,改变该函数的功能。在 C++ 语言中虚函数可以继承,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数,但如果派生类没有覆盖基类的虚函数,则调用时调用基类的函数定义。

覆盖和重载的区别是,重载是同一层次函数名相同,覆盖是在继承层次中成员函数的函数原型完全相同。

1.1.1 利用虚函数实现动态绑定

多态主要体现在虚函数上,只要有虚函数存在,对象类型就会在程序运行时进行动态绑定。

class CEmployee
{
public:
	int m_ID;
	char m_Name[128];
	char m_Depart[128];
	CEmployee()
	{
		memset(m_Name,0,128);						//初始化数据成员
		memset(m_Depart,0,128);						//初始化数据成员
	}
	virtual void OutputName()						//定义一个虚成员函数
	{
		cout << "员工姓名: " << m_Name << endl;		//输出信息
	}
};
class COperator :public CEmployee
{
public:
	char m_Password[128];
	void OutputName()								//定义OutputName()虚函数
	{
		cout << "操作员姓名: " << m_Name << endl;	//输出信息
	}
};
int main(int argc, int* argv[])
{
	//定义CEmployee类型指针,调用COperator类构造函数
	CEmployee *pWorker = new COperator();
	strcpy(pWorker->m_Name,"MR");					//设置m_Name数据成员信息
	pWorker->OutputName();							//调用COperator类的OutputName()成员函数
	delete pWorker;									//释放对象
	return 0;
}
// 输出:
// 操作员姓名:MR

上述代码中,在 CEmployee 类中定义了一个虚函数 OutputName(),在子类 COperator 中改写了 OutputName() 成员函数,其中 COperator 类中的 OutputName() 成员函数即使没有使用 virtual 关键字仍为虚函数。下面定义一个 CEmployee 类型的指针,调用 COperator 类的构造函数构造对象

“pWorker->OutputName();” 语句调用的是 COperator 类的 OutputName() 成员函数。虚函数有以下几方面的限制:

  1. 只有类的成员函数才能为虚函数;
  2. 静态成员函数不能是虚函数,只有静态成员函数不受限于某个对象;
  3. 内联函数不能是虚函数,因为内联函数是不能在运行中动态确定其位置的;
  4. 构造函数不能是虚函数,析构函数通常是虚函数。

1.1.2 虚继承

从 CBird 类和 CFish 类派生子类 CWaterBird 时,在 CWaterBird 类中将存在两个CAnimal 类的复制。C++ 提供的虚继承机制能够使派生 CWaterBird 类时只存在一个 CAnimal 基类。

class CAnimal									//定义一个动物类
{
public:
	CAnimal()									//定义构造函数
	{
		cout << "动物类被构造" << endl;			//输出信息
	}
	void Move()									//定义成员函数
	{
		cout << "动物能够移动" << endl;			//输出信息
	}
};
class CBird : virtual public CAnimal			//从Animal类虚继承CBird
{
public:
	CBird()										//定义构造函数
	{
		cout << "鸟类被构造" << endl;			//输出信息
	}
	void FlyInSky()								//定义成员函数
	{
		cout << "鸟能在天空飞翔" << endl;		//输出信息
	}
	void Breath()								//定义成员函数
	{
		cout << "鸟能够呼吸" << endl;			//输出信息
	}
};
class CFish : virtual public CAnimal			//从Animal类虚继承CFish
{
public:
	CBird()										//定义构造函数
	{
		cout << "鱼类被构造" << endl;			//输出信息
	}
	void SwimInWater()							//定义成员函数
	{
		cout << "鱼能在水里游" << endl;			//输出信息
	}
	void Breath()								//定义成员函数
	{
		cout << "鱼能够呼吸" << endl;			//输出信息
	}
};
class CWaterBird: public CBird, public CFish 	//从CBird和CFish类派生子类CWaterBird
{
public:
	CWaterBird()//定义构造函数
	{
		cout << "水鸟类被构造" << endl;			//输出信息
	}
	void Action()//定义成员函数
	{
		cout << "水鸟既能飞又能游" << endl;		//输出信息
	}
};
int main(int argc, char* argv[])
{
	CWaterBird waterbird;//定义水鸟对象
	return 0;
}
// 输出:
// 动物类被构造
// 鸟类被构造
// 鱼类被构造
// 水鸟类被构造

上述代码在定义 CBird 类和 CFish 类时使用了关键字 virtual,从基类 CAnimal 派生而来。实际上,虚继承对于 CBird 类和 CFish 类没有多少影响,却对 CWaterBird 类产生了很大的影响。CWaterBird 类中不再有两个 CAnimal 类的复制,而只存在一个 CAnimal 的复制(”动物类被构造“只输出了一次)。

通常,在定义一个对象时,先依次调用基类的构造函数,最后才调用自身的构造函数。但是对于虚继承来说情况有些不同。在定义 CWaterBird 类对象时,先调用基类 CAnimal 的构造函数,然后调用 CBird 类的构造函数,这里 CBird 类虽为 CAnimal 类的子类,但在调用 CBird 类的构造函数时将不再调用 CAnimal 类的构造函数。对于 CFish 类也是同样的道理。

2. 抽象类

包含有纯虚函数的类称为抽象类,一个抽象类至少具有一个纯虚函数。抽象类只能作为基类派生出的新的子类,而不能在程序中被实例化(即不能说明抽象类的对象),但是可以使用指向抽象类的指针。在开发程序过程中并不是所有代码都是由软件构造师自己编写的,有时候需要调用库函数,有时候分给别人写。一名软件构造师可以通过纯虚函数建立接口,然后让程序员填写代码实现接口,而自己主要负责建立抽象类。

纯虚函数(pure virtual function) 是指被标明为不具体实现的虚成员函数,它不具备函数的功能。许多情况下,在基类中不能给虚函数一个有意义的定义,这时可以在基类中将它说明为纯虚函数,而其实现留给派生类去做。纯虚函数不能被直接调用,仅起到提供一个与派生类相一致的接口的作用。声明纯虚函数的形式为:

virtual 类型 函数名(参数列表)=0;

纯虚函数不可以被继承。当基类是抽象类时,在派生类中必须给出基类中纯虚函数的定义,或在该类中再声明其为纯虚函数。只有在派生类中给出了基类中所有纯虚函数的实现时,该派生类才不再成为抽象类。

class Cfigure
{
public:	
	virtual double getArea() =0;				//定义一个纯虚函数
};
const double PI=3.14;
class CCircle : public CFigure					//继承
{
private:
	double m_dRadius;
public:
	CCircle(double dR){m_dRadius=dR;}
	double getArea()							//实现父类中的纯虚函数
	{
		return m_dRadius*m_dRadius*PI;
	}
};
class CRectangle : public CFigure
{
private:
	double m_dHeight,m_dWidth;
public:
	CRectangle(double dHeight, double dWidth)
	{
		m_dHeight = dHeight;
		m_dWidth = dWidth;
	}
	double getArea()							//实现父类中的纯虚函数
	{
		return m_dHeight*m_dWidth;
	}
};
void main()
{
	CFigure *fg1;
	fg1 = new CRectangle(4.0, 5.0);				//使用子类对象实例化父类
	cout << fg1->getArea() << endl;				//父类对象调用子类重写的函数
	delete fg1;
	CFigure *fg2;
	fg2 = new CCircle(4.0);
	cout << fg2->getArea() << endl;
	delete fg2;
}

程序定义了矩形类 CRectangle 和圆形类 CCircle,两个类都派生于图形类 CFigure。图像类是一个在现实生活中不存在的对象,抽象类面积的计算方法不确定,所以,将图形类 CFigure 的面积计算方法设置为纯虚函数,这样圆形有圆形面积的计算方法,矩形有矩形面积的计算方法,每个继承自 CFigure 的对象都有自己的面积,通过 getArea 成员函数就可以获取面积值。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值