C++学习笔记:虚函数和多态

虚函数

定义:在类的定义中,函数声明前有virtual关键字的成员函数就是虚函数
如下所示,需要注意的是,关键字virtual只需要在类定义的函数声明中使用,而在描述函数体时不加virtual

class base{
	virtual int get();
};
int base::get() { }

注意:构造
虚函数的存在使C++语言中实现多态的前提条件。

虚函数的存在使得C++从基于对象的程序设计语言升级为面向对象的程序设计语言

多态的表现形式

将派生类的指针赋值给基类指针(指针的类型一定为基类指针,但其取值,是可以为派生类同名虚函数地址的)

关于多态:
通过基类指针调用基类和派生类中的同名虚函数时,
如果指针指向基类对象,则调用基类中的虚函数
如果指针指向派生类对象A,则调用派生类A中的虚函数
如果指针指向派生类对象B,则调用派生类B中的虚函数
即编译器将会调用指针指向的对象中的虚函数,这种机制叫做多态
e.g

class CBase{
	public:
		virtual void SomeVirtualFunction(){ }
};

class CDerived: public CBase{
	public:
		virtual void SomeVirtualFunction(){ }
};

int main(){
	CDerved ODerived;
	CBase * p = & ODerived;
	p -> SomeVirtualFunction();// 调用哪个虚函数,取决于p指向哪种类型的对象
	return 0;
}

将派生类的对象赋给基类引用

通过基类引用调用基类和派生类中的同名虚函数时,
若该引用 引用的是一个基类的对象,那么被调用的是基类的虚函数
若该引用 引用的是一个派生类的对象,那么被调用的是派生类的虚函数
这也是多态的一种
e.g.

class CBase{
	public:
		virtual void SomeVirtualFunction(){}
};
class CDerived :public CBase{
	public:
		virtual void SomeVirtualFunction(){ }
};

int main()
{
	CDerived ODerived;
	CBase & r = ODerived;
	r.SomeVirtualFunction();
	//调用哪个虚函数取决于r引用哪种类型的对象
	//在这里,显然是使用r指向的CDervied类中的虚函数
	return 0;
}

关于多态的使用,其作用体现在:
在面向对象的程序设计中使用多态,能够增强程序的可扩充性,即程序需要修改或增加功能的时候,需要改动和增加的代码较少。

多态程序实例

1 几何形体处理程序

输入若干个几何形体的参数,要求按照面积排序输出,输出时需要指明形状
关于要求:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

//几何形体处理程序
//用于计算若干矩形、圆形、梯形等几何图形的面积并排序
# include <iostream>
# include <stdlib.h>
# include <math.h>
using namespace std;

class CShape
{//基类
	public:
		virtual double Area() = 0;//虚函数等于0而没有函数体,是为纯虚函数
		//因为基类CShape没有特定的几何形状与面积,函数无实际意义,故而使用纯虚函数
		virtual void PrintInfo() = 0;
};
class CRectangle: public CShape
{//矩形派生类
	public:
		int w, h;
		virtual double Area(){
			return w*h;
		}
		virtual void PrintInfo(){
		cout<< "Rectangle:"<< Area()<<endl;
		}
};
class CCircle: public CShape
{//圆形派生类
	public:
		int r;
		virtual double Area(){
			return 3.14*r*r;
		}
		virtual void PrintInfo(){
			cout<< "Cirle:"<< Area()<<endl;
		}
};
class CTriangle:public CShape
{//三角形派生类
	public:
		int a,b,c;
		virtual double Area(){
			double p = (a+b+c)/2/0;
			return sqrt(p*(p-a)*(p-b)*(p-c));
		}
		virtual void PrintInfo(){
			cout<< "Triangle:"<< Area()<<endl;
		}
};


CShape * pShapes[100];//将new出来的派生类对象指针存入基类对象数组
int MyCompare(const void * s1, const void * s2);
//以两个void常指针作为索引,对面积进行比较

int main()
{
	int i,n;
	CRectangle * pr;
	CCircle * pc;
	CTriangle * pt;//准备好三类几何形体的指针备用
	cin>> n;
	for(i=0; i<n; i++){
		char c;
		cin>> c;
		switch(c){
			case 'R':
				pr = new CRectangle();
				cin >> pr->w >> pr->h;
				pShapes[i] = pr;//将第i个基类数组指针位,指向矩形派生类对象
				break;
			case 'C'://同理于矩形
				pc = new CCircle();
				cin >> pr->r;
				pShapes[i] = pc;
				break;
			case 'T'://同理于矩形和圆形
				pt = new CTriangle();
				cin >> pt->a >>pt->b >>pt->c;
				pShapes[i] = pt;
				break;
		}
	}
	qsort(pShapes,n,sizeof(CShape *),MyCompare);
	//对基类指针数组pShapes进行排序,共n个元素
	//每个元素的大小为sizeof(CShape *),排序方法为MyCompare
	for(i= 0; i <n; i++)
		pShapes[i] ->PrintInfo();//基类指针pShapes[i],调用基类和派生类均存在的虚函数,是为多态语句
	return 0;
}

int MyCompare(const void *s1, const void * s2)
{//对于基类指针数组pShapes元素的排序方法
	double a1, a2;//面积
	//s1, s2为void *类型,不可以通过*s1方法来得到其指向的内容,
	//故而在下文中使用CShape ** 的格式来对s进行格式转换
	//关于void *类型的指针,这样编写可以避免计算其指向元素的字节大小
	//至于s1所指向的元素,是CShapes数组中的数组元素,数组元素则为CShape的指针
	CShape ** p1;
	CShape ** p2;
	p1 = (CShape **)s1;//强制类型转换
	p2 = (CShape **)s2;
	a1 = (*p1)->Area();//*p1为基类指针,指向派生类,此语句为多态
	a2 = (*p2)->Area();
	if (a1<a2)
		return -1;
	else if(a2<a1)
		return 1;
	else
		return 0;
}

上述程序中涉及到的一个常见处理手法:
用基类指针数组存放指向各种派生类对象的指针,然后遍历该数组,对各个派生类对象进行各种操作。


2 函数中调用虚函数实例

class Base{
	public:
		void fun1(){this -> fun2();}//"this->"可省略,意义相同
		virtual void fun2(){
			cout << "Base::fun2()" <<endl;
		}
};
class Derived: public Base{
	public:
		virtual void fun2(){
			cout<< "Derived:fun2()" <<endl;
		}
};

int main()
{
	Derived d;
	Base * pBase = &d;//基类指针指向派生类对象
	pBase -> fun1();//调用fun1,在其中调用fun2,在fun1中,this指向的指针是派生类对象d,则调用派生类的fun2()
	return 0;
}

在非构造函数、非析构函数外的成员函数中调用到了虚函数,则同样是多态

如果在构造函数和析构函数中调用虚函数,则不是多态,多为自己类中对应的虚函数,若该类中未定义,则调用直接基类中的虚函数。
在编译时即可确定

虚析构函数

我们通过基类指针指向派生类对象,在使用指针删除对象时,通常情况下由于指针的类别,我们仅仅能够调用基类的析构函数
但是,在我们的设想中,要删除一个派生类对象时,应该先调用派生类的析构函数,然后再调用基类的析构函数。

关于上述问题,我们给出解决办法:将基类的析构函数声明为虚函数


使用情况:
一般来说,一个类如果定义了虚函数,则应该将析构函数也定义为虚函数;或者一个类打算作为基类使用,也应该将析构函数定义成虚函数。
总之,就是涉及到多态问题的类,应该将析构函数设置为虚函数

值得注意的是,析构函数可以是虚函数,但不能以虚函数作为构造函数。

class son{
	public:
		virtual ~son() {cout << "bye from son"<<endl;}
};
class grandson:public son{
	public:
		~grandson(){cout << "bye from grandson"<<endl;}//当基类中析构函数声明为虚函数时,派生类虚构函数可再不声明为虚函数
};
int main(){
	son * pson;
	pson = new grandson();
	delete pson;//多态,先调用派生类析构函数,再调用基类析构函数
	return 0;
}
/*输出
bye from grandson
bye from son
*/

抽象类

定义:包含纯虚函数的类叫做抽象类
抽象类只能作为基类排派生新类,不能创建独立的抽象类的对象
但抽象类的指针和引用可以存在,也可以指向由抽象类派生出的新类的对象

在抽象类的成员函数中可以调用纯虚函数,但在其析构函数和构造函数中不能调用纯虚函数

如果一个类从抽象类派生而来,当且仅当它实现了基类中所有纯虚函数(给出具体函数体)时,它才能成为抽象类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

胖虎干嘛了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值