虚函数和多态

什么是多态?

对于同一行为(函数),不同的对象调用,会有不同的反响。比如售票系统的售票行为(函数)对于学生、残疾人、老人、成人(对象)有不同的反响(执行过程及结果)。

多态:使用基类的指针或引用调用重写的虚函数时,如果父类去调用这个函数,那么自动识别并去调用父类的虚函数;如果是子类去调用这个函数,那么自动识别并去调用子类的虚函数。

怎么实现多态?

实现多态的两个条件:1)调用函数的对象必须是指针或者引用。2)被调用的函数必须是虚函数,并且完成了函数重写。

虚函数

什么是虚函数?在函数前加virtual关键字类的成员函数叫作虚函数。

class Person 
{
public:
virtual void BuyTicket() { cout << "Full price ticket" << endl;}
};

子类有一个跟父类的函数名、参数、返回值都完全相同的虚函数。 这种现象称为子类重写了父类的虚函数。

class Person {
public:
	virtual void BuyTicket() { cout << "Full price ticket" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "Half price ticket" << endl; }
};

多态调用和对象调用

#include<iostream>
using namespace std;

class Person {
public:
	virtual void BuyTicket() { cout << "Full price ticket" << endl; }
};
class Student : public Person {
public:
	virtual void BuyTicket() { cout << "Half price ticket" << endl; }
};
void Func(Person& p)			
{
	p.BuyTicket();
}
int main()
{
	Person p;
	Student s;
	p.BuyTicket();			//对象调用
	s.BuyTicket();			//对象调用
	Func(p);				//多态调用
	Func(s);				//多态调用
	system("pause");
	return 0;
}

在这里插入图片描述
我们知道继承中的切片行为,这个Func函数的参数是父类的引用,当函数参数是父类对象时候,这个函数会去调用这个父类的虚函数,当函数参数是子类对象时候,自动发生切片行为,然后去调用子类的虚函数。这就实现了多态,达到了“一种代码,不同对象,不同形态”。

容易被忽视的虚函数

1)父类虚函数前有virtual关键字,子类虚函数前的virtual关键字可以省略。
2)子类的析构函数默认是对父类析构函数的重写。因为编译器会把析构函数都变成destructor函数去调用。

虚函数表和虚函数表指针

虚函数表是通过一块连续内存来存储虚函数的地址的指针数组,以nullptr结尾。 这张表解决了继承、虚函数(重写)的问题。在有虚函数的对象实例中都存在一张虚函数表,虚函数表就像一张地图,指明了实际应该调用的虚函数。

如果一个类有虚函数,就有对应的虚函数表,类中头四个字节就会存储下虚函数表的地址,这四个字节就是虚函数指针。 证明一下这个虚函数表指针的存在:

#include<iostream>
using namespace std;

class A{
public:
	virtual void func()
	{

	}
};
int main()
{
	A a;
	cout << sizeof(a) << endl;
	system("pause");
	return 0;
}

在这里插入图片描述
虚函数表在编译阶段初始化
在调用构造函数时,初始化虚函数指针,在调用了Father构造函数之后。我们明显看到虚函数指针指向了唯一的虚函数fun()。
在这里插入图片描述
多态就是通过虚函数来实现的,当通过父类指针调用多态函数时候,如果调用者是父类对象,会去父类虚函数表找对应的虚函数,如果是子类对象会去子类虚函数表找对应的虚函数。

虚函数总结

  • 最好把基类的析构函数定义声明为虚函数,因为子类和父类的析构函数被编译器处理成destructor函数,所以子类的析构对父类构成覆盖(即重写) ,定义成虚函数即可实现多态。

  • 只有类的成员函数才能定义为虚函数,在成员函数前面加virtual关键字即可实现将这个成员函数定义为虚函数。

  • 派生类重写基类的虚函数实现多态,要求函数名、参数列表、返回值(协变例外)完全相同。

  • 基类定义了虚函数,在派生类中该函数始终保持虚函数特性。(即使重写该虚函数没有virtual,也保持有虚函数特性,这个容易造成混淆,属于C++缺陷。)。

  • 虚函数表初始化在编译阶段,同一个类拥有相同的一个虚基表,存储在代码段。

  • 虚函数的继承不同于普通实现继承,它属于接口继承,我继承了父类的虚函数,就要重写虚函数,否则没有实现多态的意义。

不能定义为虚函数的成员原因
静态成员函数静态成员没有this指针,没有虚基表
构造函数对象不完成,构造函数初始化虚基表指针
内联函数内联函数没有函数地址
不要在构造/析构函数调用虚函数对象不完整,容易发生未定义的行为。

不把析构函数定义成虚函数,可能出现内存泄漏问题

#include<iostream>

using namespace std;

class Father{
public:
	~Father(){}
private:
	int a;
};


class Son :public Father
{
public:
	~Son(){}
private:
	int b;
};

int main()
{
	Son s;
	Father *f = &s;//父类指针可以指向子类对象,子类对象发生切片行为
	delete f;//此时对p调用析构函数,如果析构函数不是虚函数,则调用的是父类的析构,没有释放全部空间
	//倘若定义成虚函数,则调用的是子类的析构函数,空间全部释放
	system("pause");
	return 0;
}

在这里插入图片描述

纯虚函数和抽象类

虚函数之后=0,那么这个虚函数就是纯虚函数。纯虚函数不能实例化出对象,子类继承了纯虚函数,必须对纯虚函数重写之后,才能实例化出对象。含有纯虚函数的类称为抽象类,抽象类更能体现出接口继承的特点。

一般而言纯虚函数的函数体是缺省的,但是也可以给出纯虚函数的函数体(此时纯虚函数仍然为纯虚函数,对应的类仍然为抽象类,还是不能实例化对象)调用纯虚函数的方法为:抽象类类名::纯虚函数名(实参表)

因为对于纯虚函数的重写非常有必要,而实际使用中,虚函数重写又有很多限制,比如函数名和返回值、形参类型等必须完全相同。在使用中一不小心将原本需要重写的函数,忘记重写了。那么子类仍然使用父类的虚函数,不符合本应该实现多态的思路,为了防止万一忘记重写。C++11强制子类对父类进行重写,增加了override关键字,翻译成英文就是覆盖,就是标记了我这函数必须重写。override是用来标记子类需要重写的虚函数的。

class Car{
public:
virtual void Drive(){}
};
// 2.override 修饰派生类虚函数强制完成重写,如果没有重写会编译报错
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

1)final 修饰基类的虚函数不能被派生类重写
2 )协变允许重写的虚函数返回值不同的情况。

#include<iostream>
using namespace std;

class Person {
public:
	virtual Person& BuyTicket() { cout << "Full price ticket" << endl; return *this; }
};
class Student : public Person {
public:
	virtual Student& BuyTicket() { cout << "Half price ticket" << endl; return *this; }
};
void Func(Person& p)			//多态通过这个函数实现
{
	p.BuyTicket();
}
int main()
{
	Person p;
	Student s;
	Func(p);				//多态调用
	Func(s);				//多态调用
	system("pause");
	return 0;
}

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值