多态与重载

多态的基本概念

多态性是一个接口多种实现,分为类的多态性和函数多态性。
函数的多态性(重载)是指一个函数被定义成多个不同参数的函数。

类的多态性用一句话概括就是:

在基类的函数前加上virtual关键字(即虚函数),在派生类中重写该函数,
运行时将会根据对象的实际类型来调用相应的函数。如果对象类型
是派生类,就调用派生类的函数;如果对象类型是基类,就调用基类的函数。

以下是针对函数的多态的一个简单的实现:

#include<iostream>
using namespace std;
float Sum(float a, float b)
{
	return a + b;
}
int Sum(int a, int b)
{
	return a + b;
}
int main()
{
	int var1, var2;
	float var3, var4;
	cin >> var1 >> var2;
	cin >> var3 >> var4;
	cout << Sum(var1, var2) << endl << Sum(var3, var4) << endl;
	system("pause");
	return 0;
}

下面是程序的运行结果:

从程序代码中可以看出,定义了两个同名的函数Sum,他们的不同之处在于函数参数类型以及返回值类型的不同。
在对程序进行编译时,编译器并没有报错,并且程序的运行结果也是我们所期待的。因为在此程序中使用了函数
多态(重载)这一特性,这样编译器在对代码进行编译的过程中就会通过函数参数的不同来进行同名函数的选择调用,
选择出最合适的函数类型。这就是函数多态的方便之处。

我们来看一下下面这段代码:

#include<iostream>
using namespace std;
class Mammal {
public:
	void speak() {
		cout << " Mammal::speak " << endl;
	}
};
class Dog :public Mammal {
public:
	void speak()
	{
		cout << " Dog::speak " << endl;
	}
};
int main()
{
	Dog a;
	a.speak();
	Mammal *b = &a;
	b->speak();
	system("pause");
	return 0;
}

此程序运行结果:

以上代码中,定义了一个基类Mammal,在由其派生出了类Dog,二者都有函数speak(),我们
在main函数里面首先调用了Dog类的speak函数,然后通过将Dog类的a的地址赋给Mammal类b,
我们并想以这种方式来通过基类来调用派生类的函数,但是从程序运行的结果来看,这种方式
并不可取。

那若想得到我们所期待的结果,此时就需要用到类的多态性。我们只需将
基类中的speak函数声明为虚函数,即加上

virtual

关键字:

class Mammal {
public:
        virtual void speak() {
		cout << " Mammal::speak " << endl;
	}
};

此时再将程序运行:

这样就得到了我们所想要的结果。

在类的多态性里面还有纯虚函数,含有纯虚函数的类也称之为抽象类:

纯虚函数的使用:

1,当想在基类中抽象出一个方法,且该基类只做能被继承,而不能被实例化;
2,这个方法必须在派生类(derived class)中被实现;

构造纯虚函数只需要在虚函数定义是在右边加上

“= 0”

的格式就可以。

下面是利用类的继承来计算矩形和圆的面积与周长的程序(纯虚函数):

#include<iostream>
using namespace std;
#define PI 3.1415926
class Shape {
public:
	virtual double getArea() = 0;
	virtual double getPerim() = 0;
};
class Rectangle :public Shape {
public:
	virtual double getArea();
	virtual double getPerim();
	Rectangle(double a = 1, double b = 1) :r1(a), r2(b) {

	}
private:
	double r1;
	double r2;
};
double Rectangle::getArea() {
	return r1 * r2;
}
double Rectangle::getPerim() {
	return 2 * (r1 + r2);
}
class Circle : public Shape {
public:
	virtual double getArea();
	virtual double getPerim();
	Circle(double a) :r(a) {

	}
private:
	double r;
};
double Circle::getArea()
{
	return PI * r*r;
}
double Circle::getPerim()
{
	return 2 * PI*r;
}
int main()
{
	Rectangle a(1.2, 3.4);
	Circle b(3);
	cout << "Rectangle的面积:" << a.getArea() << endl;
	cout << "Rectangle的周长:" << a.getPerim() << endl;
	cout << "Circle的面积:" << b.getArea() << endl;
	cout << "Circle的周长:" << b.getPerim() << endl;
	system("pause");
	return 0;
}

因为矩形与圆的周长与面积的计算方式不同,所以在这里可以将基类定义为抽象类。在此
程序中定义了一个抽象类Shape,在此基础上派生出类Retangle和Circle。这两个派生类
都继承了抽象类里面的纯虚函数getArea(),getPerim()二者分别为计算面积以及周长的函数。
因为在派生类中纯虚函数不能被直接继承,所以在两个类中,纯虚函数都被重新定义。

下面是程序的运行结果:

在这个程序中纯虚函数的优越性就直接体现出来了。

函数运算符的重载:

运算符的重载可以通过两种方式来实现:

1、将函数定义为类的成员函数

2、将函数定义为非成员函数(类的友元函数)

运算符重载规则:

1、如果一个运算符函数是成员函数,则它的第一个(左侧)运算对象绑定到隐式的this指针上,
因此,成员运算符函数的(显式)参数数量比运算符的运算对象总少一个。

2、当运算符函数是非成员函数时,函数的参数与该运算符作用的运算对象数量一样多。

为了区分运算符到底是前置还是后置运算符,后置版本接受一个额外的(不被使用)int类型的形参。 使用后置运算符时,编译器为这个形参提供一个值为0的实参。这个形参的唯一作用就是区分前置 版本和后置版本的函数。

“++”运算符通过友元函数来实现:

#include<iostream>
using namespace std;
class Point {
public:
	friend Point operator ++(Point &a);//前++
	friend Point operator ++(Point &a, int);//后++
	Point(int a = 0) :x(a) {

	}
	void DisPlay()
	{
		cout << "私有成员 x = " << x << endl;
	}
private:
	int x;
};
Point operator++(Point &a)
{
	a.x = a.x + 1;
	return a;
}
Point operator ++(Point &a, int)
{
	Point b = a;
	a.x = a.x + 1;
	return b;
}
int main()
{
	Point a(10);
	a.DisPlay();
	Point b = a++;
	b.DisPlay();
	a.DisPlay();
	++a;
	a.DisPlay();
	system("pause");
	return 0;
}

下面是程序的运行结果:

从程序运行结果来看,根据函数参数的不同,编译器将重载函数“++”分为了前加加和后加加两个不同的函数。

再看下面这段代码(虚析构函数):

#include<iostream>
using namespace std;
class BaseClass {
public:
	BaseClass()
	{
		cout << "--BaseClass的构造函数被调用--" << endl;
	}
	virtual ~BaseClass()
	{
		cout << "--BaseClass的虚析构函数被调用--" << endl;
	}
};
class DerivedClass:public BaseClass {
public:
	DerivedClass()
	{
		cout << "--DerivedClass的构造函数被调用--" << endl;
	}
	~DerivedClass()
	{
		cout << "--DerivedClass的析构函数被调用--" << endl;
	}
};
void Delete(BaseClass *a)
{
	delete a;
}
int main()
{
	BaseClass *a = new DerivedClass;
	Delete(a);
	system("pause");
	return 0;
}

在此程序中定义了一个基类BaseClass,从它派生出了类DerivedClass。基类中的析构函数被声明为了
虚析构函数,在主函数中将一个动态分配的DerivedClass的对象地址赋给一个BaseClass的指针,然后
通过指针释放对象空间。

下面是代码的运行结果:

构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数

析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象

posted @ 2019-10-27 14:10  浪……浪  阅读(1533)  评论(0编辑  收藏  举报