【C++】多态(一)--重载,重写,重定义

【本节内容】
1.多态的概念
2.多态的定义及实现(虚函数重写)
3.抽象类
4.C++11 override和final

1. 多态的概念

简单说 多态就是同一个函数表现出的多种形态。具体点 就是当不同的对象去完成某个相同行为时所产生不同的状态

举例:就生活中买票这个行为,当成人去完成时买到全价票,当学生去完成时买到半价票,当儿童去完成时是免费票

2.多态的定义及实现

2.1 多态构成的条件

1.调用函数的对象必须是指针或引用
2.被调用的函数必须是虚函数,且已完成了虚函数的重写
【C++】多态(一)--重载,重写,重定义_第1张图片
这里引入一个问题:什么是虚函数?

虚函数:就是在类的成员函数前面加virtual关键字

例如下面的BuyTicket()函数就是虚函数:

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

那么什么是虚函数的重写?

虚函数的重写:也叫虚函数的覆盖。指子类中有一个和父类完全相同的虚函数,就称子类将父类的虚函数重写。

这里的完全相同是指:函数名,参数,返回值都相同。
例如:下面的Student 类就将Person 类的BuyTicket()函数重写了:

class Person
{
public:
	virtual void BuyTicket() 	{ cout << "全价票" << endl;	}
};
class Student :public Person
{
public:
	virtual void BuyTicket()  { cout << "半价票" << endl;	}
};
虚函数重写的例外:协变

虚函数重写还有一个例外:重写的虚函数返回值可以不同,但是必须同为指针或引用。
这个例外我们了解一下就好了,毕竟用到的地方不多。

class A{};
class B :public A{};
class Person
{
public:
	virtual A* f()  { return new A; }     //返回值A*
};
class Student :public Person
{
public:
	virtual B* f()  { return new B; }       //返回值B*
};
虚函数不规范的重写行为

有时我们可能看到的子类中重写的虚函数没有virtual关键字这是允许的,因为父类的虚函数被子类继承下来以后仍然保持虚函数的属性,我们只是重写了它。但这是非常不规范的,不建议大家这样写

析构函数的重写问题

我们建议将父类的析构函数写成虚函数,这样子类的析构函数就重写了父类的析构函数。这里子类与父类的函数名虽然不同,但仍然构成了重写,因为编译器会对析构函数的名字做特殊处理,编译后的析构函数名统一为destructor,所以可以将父类的析构函数写成虚函数。

class Person
{
public:
	virtual ~Person() { cout << "~Person" << endl; }
};
class Student :public Person
{
public:
	virtual ~Student() { cout << "~Studennt" << endl; } 
	 //可不加virtual关键字,但不建议这样做
};

只有子类的析构函数重写了父类的析构函数,下面的delete对象调用析构函数时
才能构成多态,才能保证p1和p2指向的对象正确调用析构函数

int main()
{
	Person *p1 = new Person;    //指向父类的指针
	Person *p2 = new Student;    //指向子类的指针
	delete p1;    
	delete p2;
	system("pause");
	return 0;
}

若子类未与父类构成多态,则 Person *p = new Student; delete p; 只会调用父类的析构函数,子类成员未清理,此时会造成内存泄漏!!

接口继承和实现继承

普通函数的继承是一种实现继承,因为子类继承的父类成员函数用于函数实现。虚函数的继承是一种接口继承,因为子类继承的父类虚函数用于重写达成多态。
定义虚函数的主要目的就是实现多态,不实现多态就不要定义虚函数。

2.2 重载,重写(覆盖),重定义(隐藏)的对比

【C++】多态(一)--重载,重写,重定义_第2张图片

那么现在,看下面两个代码,你知道哪个构成了多态吗?

class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "全价票" << endl;
	}
};
class Student :public Person
{
public:
	virtual void BuyTicket()
	{
		cout << "半价票" << endl;
	}
};
void func(Person &p)
{
	p.BuyTicket();
}

【C++】多态(一)--重载,重写,重定义_第3张图片
相信认真看了我前面内容的伙伴都知道的,A构成了多态,因为它是用不同继承关系的类对象去调用同一函数,最后达到不同的效果。
而在B中,虽然也是不同继承关系的类对象去调用函数,但它们调用的只是相同函数,并不是同一个函数,所以不构成多态。

3.抽象类

在虚函数的后面写上 =0,则这个函数就是纯虚函数
包含纯虚函数的类叫做抽象类(接口类),抽象类不能实例化出对象。 即使被子类继承后也不能实例化出对象,只有重写了纯虚函数的子类才能实例化出对象。所以纯虚函数更体现出了接口继承。

由此看出纯虚函数的作用

1)实现抽象类2)强制子类重写虚函数

class Car
{
public:
	virtual void Drive() = 0;      //纯虚函数
};
class Benz :public Car
{
public:
	virtual void Drive() { cout << "舒适" << endl; }
};
class BMW :public Car
{
public:
	virtual void Drive() { cout << "简单" << endl; }
};
void Test()
{
	//Car c;    //Car是抽象类,无法实例化对象
	Car *p1 = new Benz;
	p1->Drive();
	Car *p2 = new BMW;
	p2->Drive();
}

强制子类重写虚函数有两种方法

1)基类使用纯虚函数 2)用override修饰虚函数

4. C++11 override和final

override 修饰派生类虚函数强制完成重写,如果没有重写,编译会报错。

实际中建议大家多使用 纯虚函数+override 的方式来强制重写虚函数,因为虚函数的意义就是实现多态,如果没有重写那虚函数就没有了意义。

class Car
{
public:
	virtual void Drive() {}
};
class Benz :public Car
{
public:
	virtual void Drive() override { cout << "舒适" << endl; }
};

final 修饰基类的虚函数不能被派生类重写

class Car
{
public:
	virtual void Drive() final {}
};
class Benz :public Car
{
public:
	virtual void Drive() { cout << "舒适" << endl; } //编译无法通过!
};

final 关键字在我的博客 继承(二)里也提到了,并涉及了一道面试题:
实现一个不能被继承的类:https://blog.csdn.net/ly_6699/article/details/88805277
有兴趣的伙伴可以点链接去了解。

我在下篇博客多态(二):https://blog.csdn.net/ly_6699/article/details/88928006 中,将继续讲多态的知识点,包括多态的原理和虚函数表的内容,希望大家多多关注和留言交流哟~

你可能感兴趣的:(C++,/,数据结构)