C++|多态性与虚函数(1)功能绑定|向上转换类型|虚函数

发布于:2024-05-20 ⋅ 阅读:(132) ⋅ 点赞:(0)

目录

什么是多态性?

概念

分类

向上类型转换

功能的早绑定和晚绑定

绑定

绑定与多态的联系

编译时多态(功能的早绑定)

运行时多态(功能的晚绑定)

一般而言

实现功能晚绑定——虚函数

虚函数定义的说明


什么是多态性?

概念

书上的表示是——向不同的对象发送同一个消息,不同的对象在接受时会有不同的反应,产生不同的动作。

我们在上课时,老师是这么解释的:在一个学校里,每个人都有不同的工作,当校长发出号令“开工”,每个人就开始工作(属于自己的那份工作)。

具体一点:多态性是指用一个名字定义不同的函数,这些函数执行不同但又类似的操作,从而可以使用相同的口令来调用相应函数——一个接口,多个方法。

分类

多态性可以分为——参数多态,包含多态,重载多态,强制多态。

参数多态:函数模板,类模板

由函数模板实例化的各个函数具有相同的操作,而这些函数的参数类型却各不相同

由类模板实例化的各个类具有相同的操作,而操作对象的类型各不相同

包含多态定义于不同类之间的同名成员函数,主要通过虚函数来实现。

重载多态:函数重载,运算符重载

函数重载有就是在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数、类型、类型顺序)不同。

运算符重载是对已有的运算符赋予多重含义,使同一个运算符作用于不同类型时导致不同的行为。 

强制多态:是指将一个变元的类型加以变化,以符合一个函数(或者操作)的要求

如加法运算符在进行浮点数于整形数相加时,首先进行类型强制转换。把整型数变为浮点数再相加的情况。

向上类型转换

向上类型转换就是把一个派生类对象当成基类对象来用 

注意:

向上类型转换是安全的

向上类型转换可以自动完成

向上类型转换的过程会丢失子类信息

#include<iostream>
using namespace std;

class point
{
public:
	point(double a, double b):x(a), y(b){}
	void area(void)
	{
		cout << "point:" << 0 << endl;
	}


	double x;
	double y;

};

class rectangle:public point
{
public:
	rectangle(double a,double b,double c,double d):point(a,b),x1(c),y1(d){}
	void area(void)
	{
		cout << "rectangle:" << (x-x1)*(y-y1)<< endl;
	}

private:
	double x1;
	double y1;
};

class circle :public point
{
public:
	circle(double a,double b,double c):point(a,b),r(c){}
	void area(void)
	{
		cout << "circle:" <<r*r*3.14<< endl;
	}
private:
	double r;
};


void calcarea(point& p)
{
	p.area();
}


int main(void)
{
	point p(0, 0);
	rectangle r(0, 0, 1, 1);
	circle c(0, 0, 1);
	cout << "直接调用:" << endl;

	p.area();
	r.area();
	c.area();
	cout << "通过calearea函数调用:" << endl;
	calcarea(p);
	calcarea(r);
	calcarea(c);

	return 0;
}

再caleare函数中,接受point的对象,但也不拒绝point的派生类对象,无需类型转换就可以将rectangle和circle的对象传给calearea。这也就是向上类型转换,可以将派生类转换为基类,这也导致rectangle和circle类的接口变窄。

可以看到通过calearea函数调用时,输出的全是“point:0",显然不是我们希望可看到的,我们希望通过基类的引用直接调用到相应的派生类成员函数。 

也就是当calearea函数中的对象是rectangle时调用rectangle中的area函数;

当calearea函数中的对象时circle的对象时调用cricle中的area函数。

为了解决这个问题需要了解功能的早绑定和晚绑定

功能的早绑定和晚绑定

绑定

确定操作具体对象的过程称为绑定。

绑定是指计算机程序自身彼此关联的过程,把一个标识符和一个存储地址联系在一起(把一条消息和一个对象的方法相结合的过程)。

绑定与多态的联系

按照绑定进行的阶段不同分为:功能的早绑定和功能的晚绑定

多态从实现的角度可以分为:编译时多态和运行时多态

两种绑定方法分别对应多态的两种实现方式

编译时多态(功能的早绑定)

编译过程中确定了同名操作的具体操作对象,在程序执行前期,系统就可以确定同名标识要调用那一段程序代码。

有些多态类型(重载多态,强制多态,参数多态)可以通过早绑定确定同名操作的具体操作对象

运行时多态(功能的晚绑定)

程序运行过程中动态的确定操作所针对的具体对象,在编译过程中无法解决绑定问题,等程序开始运行之后再来确定,包含多态就是通过晚绑定来确定具体操作对象的。

一般而言

编译型语言(C,PASCAL)都采用功能的早绑定,

解释型语言(LISP,Prolog)都采用晚绑定

早绑定具有函数调用速度快,效率高,但缺乏灵活性,晚绑定恰恰相反。

C++由C语言发展而来,为了保持C语言的高效性,C++仍采用编译型也就是早绑定,为了解决某些特定情况,发明了虚函数,让C++可部分采用功能的晚绑定。
C++中,编译时的多态性主要通过函数重载和运算符重载实现。运行时的多态主要通过虚函数来实现。

实现功能晚绑定——虚函数

虚函数提供了一种更为灵活的多态性机制。虚函数允许函数调用与函数体之间的联系在运行时才建立,也就是在运行时才决定同名操作的具体操作对象

虚函数的定义和作用

虚函数定义在基类定义,在成员函数声明前加上virtual,定义语法如下:

virtual 函数类型 函数名(形参表)

{
        函数体;

}

在基类中定义虚函数,

在派生类中重写虚函数,重写时必须满足同名,同参数,同返回值。

使用时必须通过指针或者引用来调用

上面的例子基类时point,修改:我们只需要将point类中的area函数前面加上virtual就可以达到想要的效果。

class point
{
public:
	point(double a, double b):x(a), y(b){}
	virtual void area(void)
	{
		cout << "point:" << 0 << endl;
	}


	double x;
	double y;

};

上面的代码是通过引用调用的,下面我们用指针来调用 ,也就是形参为指针,实参传地址。具体代码如下:

void calcarea(point* p)
{
	p->area();
}


int main(void)
{
	point p(0, 0);
	rectangle r(0, 0, 1, 1);
	circle c(0, 0, 1);
	cout << "直接调用:" << endl;

	p.area();
	r.area();
	c.area();
	cout << "通过calearea函数调用:" << endl;
	calcarea(&p);
	calcarea(&r);
	calcarea(&c);

	return 0;
}

虚函数定义的说明

1.派生类应该从它的基类公有派生

2.必须先在基类中定义虚函数(并不一定是最高层的基类)

3.在派生类中重写虚函数是virtual可以写也可以不写

4.一个虚函数无论被继承多少次,都保持其虚函数特性

5.虚函数必须在其类的成员函数中,不能是友元函数,不能是静态成员函数

6.内联函数不能是虚函数

7.构造函数不能是虚函数

8.析构函数可以是虚函数


网站公告

今日签到

点亮在社区的每一天
去签到