多态性与虚函数(Part Ⅱ)

利用虚函数实现动态多态性

在什么情况下应当声明虚函数

使用虚函数时,有两点要注意:

  1. 只能用virtual声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数。因为虚函数的作用是允许在派生类中对基类的虚函数重新定义。显然,它只能用于类的继承层次结构中。
  2. 一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但与该虚函数具有相同的参数(包括个数和类型)和函数返回值类型的同名函数。

根据什么考虑是否把一个成员函数声明为虚函数呢?主要考虑以下几点:

  1. 首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。
  2. 如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。不要仅仅考虑到要作为基类而把类中的所有成员函数都声明为虚函数。
  3. 应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问,如果是通过基类指针或引用去访问的,则应当声明为虚函数
  4. 有时,在定义虚函数时,并不定义其函数体,即函数体是空的。它的作用只是定义了一个虚函数名,具体功能留给派生类去添加。

需要说明的是:使用虚函数,系统要有一定的空间开销。当一个类带有虚函数时,编译系统会为该类构造一个虚函数表(virtual function table,简称vtable),它是一个指针数组,存放每个虚函数的入口地址。系统在进行动态关联时的时间开销是很少的,因此,多态性是高效的。

虚析构函数

  • 析构函数的作用是在对象撤销之前做必要的"清理现场"的工作。当派生类的对象从内存中撤销时一般先调用派生类的析构函数,然后再调用基类的析构函数。但是,如果用new运算符建立了临时对象,若基类中有析构函数,并且定义了一个指向该基类的指针变量。在程序用带指针参数的delete运算符撤销对象时,会发生一个情况:系统会只执行基类的析构函数,而不执行派生类的析构函数。

例:基类中有非虚构函数时的执行情况

#include <iostream>
using namespace std;
class Point //定义基类Point类
{
public:
    Point() {} //Point类构造函数
    ~Point() { cout << "executing Point destructor" << endl; } //Point类析构函数
};

class Circle :public Point //定义派生类Circle类
{
public:
    Circle() {} //Circle类构造函数
    ~Circle() { cout << "executing Circle destructor" << endl; } //Circle类析构函数
private:
    int radus;
};

int main()
{
    Point* p = new Circle; //用new开辟Circle类对象的动态存储字间
    delete p; //用delete释放动态存储空间
    return 0;
}

运行结果:
executing Point destructor

程序分析:

  • p是指向基类Point的指针变量,指向new开辟的动态存储空间。希望用detele释放p所指向的空间。但运行结果为:executmg Point destmctor,表示只执行了基类Point的析构函数,而没有执行派生类Circle的析构函数。
  • 如果希望能执行派生类Circle的析构函数,可以将基类的析构函数声明为虚析构函数, 如:virtual ~Point() { cout << "executing Point destructor" << endl; }
  • 当基类的析构函数为虚函数时,无论指针指的是同一类族中的哪一个类对象,当对象撤销时,系统会采用动态关联,调用相应的析构函数,对该对象进行清理工作。
  • 如果将基类的析构函数声明为虚函数,由该基类所派生的所有派生类的析构函数也都自动成为虚函数,即使派生类的析构函数与基类的析构函数名字不相同。
  • 在程序中最好把基类的析构函数声明为虚函数。这将使所有派生类的析构函数自动成为虚函数。这样,如果程序中显式地用delete运算符准备删除一个对象,而delete运算符的操作对象用了指向派生类对象的基类指针,则系统会调用相应类的析构函数。

构造函数不能声明为虚函数。这是因为在执行构造函数时类对象还未完成建立过程,当然谈不上函数与类对象的关联。

纯虚函数与抽象类

纯虚函数

  • 纯虚函数是在声明虚函数时被"初始化"为0的函数
  • 声明虚函数的一般形式是:virtual 函数类型 类型名(参数表列) = 0;

注意:

  1. 纯虚函数没有函数体
  2. 最后面的"=0"并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”
  3. 这是一个声明语句,最后应有分号
  • 纯虚函数只有函数的名字而不具备函数的功能,不能被调用。它只是通知编译系统:"在这里声明一个虚函数,留待派生类中定义:。在派生类中对此函数提供定义后,它才能具备函数的功能,可被调用
  • 纯虚函数的作用是在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。如果在基类中没有保留函数名字,则无法实现多态性。
  • 如果在一个类中声明了纯虚函数,而在其派生类中没有对该函数定义,则该虚函数在派生类中仍然为纯虚函数。

抽象类

  • 如果声明了一个类,一般可以用它定义对象。但是在面向对象程序设计中,往往有一些类,它们不用来生成对象。定义这些类的唯一目的是用它作为基类去建立派生类。它们作为一种基本类型提供给用户,用户在这个基础上根据自己的需要定义出功能各异的派生类。用这些派生类去建立对象。
  • 这种不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类(abstract class),由于它常用作基类,通常称为抽象基类(abstract base class)。凡是包含纯虚函数的类都是抽象类。因为纯虚函数是不能被调用的,包含纯虚函数的类是无法建立对象的。抽象类的作用是作为一个类族的共同基类,或者说,为一个类族提供一个公共接口。
  • 如果在抽象类所派生出的新类中对基类的所有纯虚函数进行了定义,那么这些函数就被赋予了功能,可以被调用。这个派生类就不是抽象类,而是可以用来定义对象的具体类(concrete class)。如果在派生类中没有对所有纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象。
  • 虽然抽象类不能定义对象(或者说抽象类不能实例化),但是可以定义指向抽象类数据的指针变量。当派生类成为具体类之后,就可以用这种指针指向派生类对象,然后通过该指针调用虚函数,实现多态性的操作。

例:虚函数和抽象基类的应用

#include <iostream>
using namespace std;
//声明抽象基类Shape
class Shape 
{
public:
	virtual float area() const { return 0.0; } //虚函数
	virtual float volume() const { return 0.0; } //虚函数
	virtual void shapeName() const = 0; //纯虚函数 就是声明虚函数时被初始化为0的函数
};

//声明Point类
class Point :public Shape //Point是Shape的公用派生类
{
public:
	Point(float = 0, float = 0); //声明构造函数
	void setPoint(float, float); //设置坐标值
	float getX() const { return x; }  //读x坐标,getX函数为常成员函数
	float getY() const { return y; }  //读y坐标,getY函数为常成员函数
	virtual void shapeName() const { cout << "Point:"; } //对虚函数进行再定义
	friend ostream& operator<<(ostream&, const Point&); //友元重载运算符<<
protected: //受保护成员
	float x, y;
};
//定义Point类成员函数
Point::Point(float a, float b) { x = a;y = b;} //定义构造函数,对x,y初始化
void Point::setPoint(float a, float b) { x = a; y = b; } //设置x,y的坐标值,对x,y赋予新值
ostream& operator<<(ostream& output, const Point& p) //重载运算符"<<",使之能输出点的坐标
{
	output << "[" << p.x << "," << p.y << "]";
	return output;
}

class Circle :public Point //Circle是Point类的公用派生类
{
public:
	Circle(float x = 0, float y = 0, float r = 0);  //声明构造函数
	void setRadius(float); //设置半径值
	float getRadius() const;  //读取半径值
	virtual float area() const; //对虚函数进行再定义
	virtual void shapeName() const { cout << "Circle:"; } //对虚函数进行再定义
	friend ostream& operator<<(ostream&, const Circle&); //重载运算符"<<"
protected:
	float radius;
};

//声明Circle类成员函数
Circle::Circle(float a, float b, float r) :Point(a, b), radius(r) {}//定义构造函数
void Circle::setRadius(float r) { radius = r; }
float Circle::getRadius() const { return radius; }
float Circle::area() const { return 3.14159 * radius * radius; }

//重载运算符"<<",使之按规定的形式输出圆的信息
ostream& operator<<(ostream& output, const Circle& c) 
{
	output << "[" << c.x << "," << c.y << "],r=" << c.radius;
	return output;
}

class Cylinder :public Circle 
{
public:
	Cylinder(float x = 0, float y = 0, float r = 0, float h = 0);  //构造函数
	void setHeight(float); //设置圆柱高
	virtual float area() const; //重载虚函数
	virtual float volume() const; //重载虚函数
	virtual void shapeName() const { cout << "Cylinder:"; } //重载虚函数
	friend ostream& operator<<(ostream&, const Cylinder&);   //重载运算符"<<"
protected:
	float height; //圆柱高
};

Cylinder::Cylinder(float a, float b, float r, float h) :Circle(a, b, r), height(h) {} //定义构造函数
void Cylinder::setHeight(float h) { height = h; } //设置圆柱高的函数
float Cylinder::area() const { return 2 * Circle::area() + 2 * 3.14159 * radius * height; } //计算圆表面积
float Cylinder::volume() const { return Circle::area() * height; } //计算圆柱体积
ostream& operator<<(ostream& output, const Cylinder& cy) //重载运算符"<<"
{
	output << "[" << cy.x << "," << cy.y << "],r=" << cy.radius << ",h=" << cy.height;
	return output;
}

int main() {
	Point point(3.2, 4.5); //建立Point类对象point
	Circle circle(2.4, 1.2, 5.6); //建立Cirlce类对象circle
	Cylinder cylinder(3.5, 6.4, 5.2, 10.5); //建立Cylinder类对象cylinder
	point.shapeName();  //用对象名建立静态关联
	cout << point << endl; //输出点的数据
	circle.shapeName();  //静态关联
	cout << circle << endl; //输出圆柱的数据
	cylinder.shapeName(); //静态关联
	cout << cylinder << endl << endl; //输出圆柱的数据
	Shape* pt; //定义基类指针
	pt = &point; //使指针指向Point类对象
	pt->shapeName(); //用指针建立动态关联
	cout << "x=" << point.getX() << ",y=" << point.getY() << "\narea=" << pt->area()
		<< "\nvolume=" << pt->volume() << "\n\n"; //输出点的数据

	pt = &circle; //指针指向Circle类对象
	pt->shapeName(); //动态关联
	cout << "x=" << circle.getX() << ",y=" << circle.getY() << "\narea=" << pt->area()
		<< "\nvolume=" << pt->volume() << "\n\n"; //输出圆的数据

	pt = &cylinder; //指针指向Cylinder类对象
	pt->shapeName(); //动态关联
	cout << "x=" << cylinder.getX() << ",y=" << cylinder.getY() << "\narea=" << pt->area()
		<< "\nvolume=" << pt->volume() << "\n\n"; //输出圆柱的数据
	return 0;
}

程序分析:

Shape类:

  • Shape类有3个成员函数,没有数据成员。3个成员函数都声明为虚函数,其中shapeName声明为纯虚函数,因此Shape是一个抽象基类。shapeName函数的作用是输出具体的形状(如点、圆、圆柱体)的名字,这个信息是与相应的派生类密切相关的,显然这不应当在基类中定义,而应在派生类中定义。所以把它声明为纯虚函数。Shape虽然是抽象基类,但是也可以包括某些成员的定义部分。类中两个函数area(面积)和volume(体积)包括函数体,使其返回值为0(因为可以认为点的面积和体积都为0)。由于考虑到在Point类中不再对area和volume函数重新定义,因此没有把area和volume函数也声明为纯虚函数。在Point类中继承了Shape类的area和volume函数。这3个函数在各派生类中都要用到。

Point类:

  • Point从Shape继承了3个成员函数,由于点是没有面积和体积的,因此不必重新定义area和volume。虽然在Point类中用不到这两个函数,但是Point类仍然从Shape类继承了这两个函数,以便其派生类继承它们。shapeName函数在Shape类中是纯虚函数, 在Point类中要进行定义。Point类还有自己的成员函数(setPoint,getX, getY)和数据成员(x和y)。

Circle类:

  • 在Circle类中要重新定义area函数,因为需要指定求圆面积的公式。由于圆没有体积,因此不必重新定义volume函数,而是从Point类继承volume函数。shapeName函数是虚函数,需要重新定义,赋予新的内容(如果不重新定义,就会继承Point类中的shapeName函数)。此外,Circle类还有自己新增加的成员函数(setRadius,getRadius)和数据成员(radius)。

Cylinder类:

  • Cylinder类是从Circle类派生的。由于圆柱体有表面积和体积,所以要对area和 volume函数重新定义。虚函数shapeName也需要重新定义。此外,Cylinder类还有自已 的成员函数setHeight和数据成员radius。

主函数:

  • 主函数中先后用静态关联和动态关联的方法输出结果。通过对象名point,circle和cylinder调用shapeName函数,属于静态关联。通过定义一个指向基类Shape对象的指针变量pt,使它先后指向3个派生类对象,然后通过指针调用各函数,这是动态关联。

运行结果:

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
4S店客户管理小程序-毕业设计,基于微信小程序+SSM+MySql开发,源码+数据库+论文答辩+毕业论文+视频演示 社会的发展和科学技术的进步,互联网技术越来越受欢迎。手机也逐渐受到广大人民群众的喜爱,也逐渐进入了每个用户的使用。手机具有便利性,速度快,效率高,成本低等优点。 因此,构建符合自己要的操作系统是非常有意义的。 本文从管理员、用户的功能要出发,4S店客户管理系统的功能模块主要是实现管理员服务端;首页、个人心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理,用户客户端:首页、车展、新闻头条、我的。门店客户端:首页、车展、新闻头条、我的经过认真细致的研究,精心准备和规划,最后测试成功,系统可以正常使用。分析功能调整与4S店客户管理系统实现的实际需相结合,讨论了微信开发者技术与后台结合java语言和MySQL数据库开发4S店客户管理系统的使用。 关键字:4S店客户管理系统小程序 微信开发者 Java技术 MySQL数据库 软件的功能: 1、开发实现4S店客户管理系统的整个系统程序; 2、管理员服务端;首页、个人心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理等。 3、用户客户端:首页、车展、新闻头条、我的 4、门店客户端:首页、车展、新闻头条、我的等相应操作; 5、基础数据管理:实现系统基本信息的添加、修改及删除等操作,并且根据需进行交流信息的查看及回复相应操作。
现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集处理数据信息的管理方式。本微信小程序医院挂号预约系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务处理效率,达到事半功倍的效果。此微信小程序医院挂号预约系统利用当下成熟完善的SSM框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的MySQL数据库进行程序开发。微信小程序医院挂号预约系统有管理员,用户两个角色。管理员功能有个人心,用户管理,医生信息管理,医院信息管理,科室信息管理,预约信息管理,预约取消管理,留言板,系统管理。微信小程序用户可以注册登录,查看医院信息,查看医生信息,查看公告资讯,在科室信息里面进行预约,也可以取消预约。微信小程序医院挂号预约系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要功能时,也为数据信息面临的安全问题提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现数据信息的整体化,规范化与自动化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值