一篇文章看懂C++三大特性——多态的定义和使用

目录

前文

一,什么是多态?

1.1 多态的概念

二, 多态的定义及实现

2.1 多态的构成条件

2.2 虚函数

2.3 虚函数的重写

2.3.1 虚函数重写的两个例外

2.4 C++ override 和 final

2.5 重载,重写(覆盖),隐藏(重定义)的区别

 三,抽象类

3.1 概念

3.2 接口継承和实现継承

总结


前文

C++三大特性——封装,継承,多态,前两个我们都已经学习过了,现在让我们一起来学习最后一个特性——多态

一,什么是多态?

1.1 多态的概念

 多态其实就是多种形态,实际上就是当完成某个行动时,不同的对象完成会产生不同的状况。如上图某景点门票,大学生,普通人,儿童所需要的票价都是不一样的,这就算是多态在实际生活的一种应用。

二, 多态的定义及实现

2.1 多态的构成条件

多态是在不同継承关系的类对象,去调用同一的函数,产生了不同的行为。如上面的Student继承了Person,Person对象买全价票,Student买半价票

但是我们需要主要构成多态要满足下面两个条件(后面会讲到两个特例)

1. 被调用的函数必须是虚函数,而且子类要对父类的虚函数进行重写

2. 必须通过父类的指针或者引用调用虚函数

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Person
{
public:
	virtual void Buyticket()
	{
		cout << "全价票" << endl;
	}
};

class Student :public Person
{
public:
	virtual void Buyticket()
	{
		cout << "半价票" << endl;
	}
};

void Func1(Person& p)
{
	p.Buyticket();
}

int main()
{
	Person p;
	Student s;

	Func1(p);
	Func1(s);
	return 0;
}

以上面为例,我们先简单使用一下多态,上述代码具体实现如下。

 看到这里可能有的老铁很好奇,这怎么这么神奇,加一个virtual就可以实现,底层是怎么实现的?我们不要急,饭要一口一口吃,我们先来了解一下多态两个必要条件都离不开的虚函数

2.2 虚函数

虚函数:被virtual修饰的类成员函数称为虚函数。

class Person
{
public:
	virtual void Buyticket()
	{
		cout << "全价票" << endl;
	}
};

2.3 虚函数的重写

虚函数的重写是多态必不可少的条件之一,接下来我们先了解一下到底什么是虚函数的重写。

虚函数的重写(覆盖):子类中有一个和父类完全相同的虚函数(三同,即子类虚函数与父类虚函数的函数名,参数,返回值完全相同。),这样的称作子类的虚函数重写了父类的虚函数。

举个例子:

class Person
{
public:
	virtual void Buyticket()
	{
		cout << "全价票" << endl;
	}
};

class Student :public Person
{
public:
	virtual void Buyticket()
	{
		cout << "半价票" << endl;
	}
};

注意:这里还有另一种写法,就是在重写父类虚函数时,子类的虚函数前面可以不加virtual关键字,仍然构成重写,这是因为子类继承了父类的虚函数接口,即使不加virtual也保持虚函数属性,但是这种写法可读性差,还是建议用上面的写法

class Person
{
public:
	virtual void Buyticket()
	{
		cout << "全价票" << endl;
	}
};

class Student :public Person
{
public:
	//virtual void Buyticket()
	void Buyticket()
	{
		cout << "半价票" << endl;
	}
};

2.3.1 虚函数重写的两个例外

1. 协变(父类与子类虚函数的返回值不同)

派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。这个实际用到的不多,我们了解一下即可

class Person
{
public:
	virtual Person* Buyticket()
	{
		cout << "全价票" << endl;
		return this;
	}
};

class Student :public Person
{
public:
	virtual Student* Buyticket()
	{
		cout << "半价票" << endl;
		return this;
	}
};

2. 析构函数的重写(父类与子类析构函数名不同)

如果父类的析构函数是虚函数,那么子类的析构函数只要定义,无论加不加virtual关键字,都与父类的析构函数形成重写,这里父类和子类的析构函数名字不同,老铁们可能会觉得不满足上面多态的两个必要条件,实际上不是,在编译后,编译器会将类的析构函数统一命名为~destructor()

析构函数的应用场景也是有的,如下面的情况。

 在上面例子中,只有Student的析构函数重写了Person的析构函数,delete对象调用析构函数,才能构成多态,才能保证p1,p2正确释放。

2.4 C++ override 和 final

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。

1.final:修饰虚函数,表示该虚函数不能再被重写

class Person
{
public:
	virtual void Buyticket final()
	{
		cout << "全价票" << endl;
	}
};

class Student :public Person
{
public:
	virtual void Buyticket()
	{
		cout << "半价票" << endl;
	}
};

2. override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。

class Person
{
public:
	virtual void Buyticket()
	{
		cout << "全价票" << endl;
	}
};

class Student :public Person
{
public:
	virtual void Buyticket override()
	{
		cout << "半价票" << endl;
	}
};

2.5 重载,重写(覆盖),隐藏(重定义)的区别

 三,抽象类

3.1 概念

在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承。

 上文我们提到了接口継承,那么什么是接口継承和实现継承

3.2 接口継承和实现継承

普通函数继承是一种实现继承派生类继承了基类函数,可以使用函数,继承的是函数的实现虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。

总结

多态的基本内容到这里就告一段落了,下一篇文章讲解多态的底层原理,希望铁子们能有所收获

猜你喜欢

转载自blog.csdn.net/zcxmjw/article/details/129961627