【C++】多态

一.多态的概念

  1. 多态分为编译时多态(静态多态),和运行时多态(动态多态),两者的区别在静态绑定和动态绑定章节讲,这里主要讲运行时多态
  2. 多态就是继承关系下的类对象,调用同一函数,产生不同的行为

二.多态的定义及实现

(1).虚函数

类成员函数前加virtual修饰,

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

(2).虚函数的重写(覆盖)

派生类中有一个和基类完全相同的虚函数(函数名,返回值,参数列表),
注意:在重写虚函数时,派生类也可以不加virtual关键字,同样构成多态,因为virtual在继承时已经被继承下来了

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

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

(3).实现多态的条件

  1. 必须是基类的指针或者引用调用虚函数,因为只有基类才能既能指向基类又能指向派生类
  2. 被调用的函数必须是虚函数,并且完成了虚函数重写(覆盖)。完成重写才能使基类和派生类才能有不同的函数,才能实现多态的效果
    在这里插入图片描述

(4).多态场景题

class A 
{ 
public:
	virtual void func(int val = 1) 
	{
		std::cout << "A->" << val << std::endl;
	} 
	virtual void test()
	{ 
		func(); 
	}
}; 
class B : public A
{
public: 
	void func(int val = 0)
	{ 
		std::cout << "B->" << val << std::endl;
	}
};
int main()
{
	B* p = new B;
	p->test();
	return 0;
}

程序运行结果是 B->1
解释:函数重写本质上只重写了函数体部分,参数及缺省值没有改变;

(5).析构函数的重写

  1. 基类的析构函数为虚函数时,无论派生类的析构函数加不加virtual,都构成析构函数的重写,因为在编译器会对析构函数做特殊处理,所有的析构函数都变成destructor()
  2. 基类的析构函数一般会设计成虚函数防止有些情况会出现内存泄漏,比如下面这种情况,当派生类成员赋给基类时,而且析构函数没有构成重写,就会只调用基类的析构函数,而派生类中的_p没有释放,造成内存泄漏,这里可以和析构函数的调用顺序对比理解一下---------->>>派生类的默认成员函数函数
class Person
{
public:
	~Person()
	{
		cout << "析构基类" << endl;
	}
};
class Student : public Person
{
public:
	~Student()
	{
		cout << "析构派生类" << endl;
		delete _p;
	}
	int* _p = new int[10];
};
int main()
{
	Person* per=new Person ;
	Person *stu=new Student;
	//delete per;
	delete stu;
	return 0;
}

在这里插入图片描述

(6).override和final关键字

  1. 在重写虚函数时,有时会由于疏忽犯写错函数名之类的错误,这些错误在编译期间是不会报错的,override可以检测是否重写
class Person
{
public:
	virtual void BuyTicket()
	{
		cout << "买票全价" << endl;
	}
};
class Student : public Person
{
public:
	virtual void BuyTicket_NO() override   //函数名写错
	{
		cout << "买票半价" << endl;
	}
};

在这里插入图片描述

  1. 不想让派生类重写这个虚函数,时可以让final修饰
class Person
{
public:
	virtual void BuyTicket() final
	{
		cout << "买票全价" << endl;
	}
};
class Student : public Person
{
public:
	virtual void BuyTicket() 
	{
		cout << "买票半价" << endl;
	}
};

在这里插入图片描述

三.纯虚函数和抽象类

  1. 在虚函数后面写上 =0,这个函数称为纯虚函数(这个函数不需要实现,没有意义),这个类称为抽象类,不能实例化出对象,如果派生类继承了之后不重写这个纯虚函数,派生类也是抽象类,不能实例化出对象(强制重写)。
class Person
{
public:
	virtual void BuyTicket() =0    //纯虚函数,抽象类
	{
		cout << "买票全价" << endl;
	}
};
class Student : public Person
{
public:
	virtual void BuyTicket() 
	{
		cout << "买票半价" << endl;
	}
};
int main()
{
	Person* per = new Person;  //错误,不能用抽象类实例化对象
	Person* stu = new Student;
	delete stu;
	return 0;
}

在这里插入图片描述

四.多态的原理

(1.)虚函数表指针的存在

class Person
{
public:
	virtual void BuyTicket() 
	{
		cout << "买票全价" << endl;
	}
	int _id;
	int _adress;
};
int main(){
	Person per;
	sizeof(per);
	return 0;
}

在这里插入图片描述
从这里可以看出,Person中不仅有_id和_adders成员变量,还有一个指针变量,这个时虚函数表指针,用来存放虚函数的地址,一个类所有虚函数的地址都会被放到这个个表里,一个有虚函数的类一定会有这个指针变量

(2.)虚函数表

  1. 虚函数表本质上是一个指针数组,用来存放类中所有虚函数的地址,这个数组的指针存在类中
  2. 基类对象的虚函数表存放基类所有的虚函数地址,不同类型的对象有各自不同的虚表,所以基类和派生类有各自不同的虚表
  3. 派生类由继承下来的基类和自己的成员组成,如果基类中有虚表,就不会在生成新的虚表。但是这里的虚表是独立的,像【基类对象】和【派生类对象中的基类成员】一样
  4. 派生类中重写的基类虚函数,虚表中的地址就会被覆盖成重写的函数地址
  5. 派生的虚表由 <1>基类的虚函数地址 <2> 重写的虚函数覆盖原来的地址 <3> 派生类自己的虚函数地址 这三部分构成
  6. 虚函数和普通函数一样,都放在代码段(提醒自己::看到这要去学内存分布)
class Person
{
public:
	virtual void func_Person() { cout << "Person :: func_Person()"; }
	virtual void func0() { cout << "Person :: func0()"; }
	virtual void func1() { cout << "Person :: func1()"; }
	virtual void func2() { cout << "Person :: func2()"; }
	void func_normal() { cout << "Person :: func_normal()"; }
	
public:
	int _id;
	int _adress;
};
class Student : public Person
{
public:
	virtual void func_Student() { cout << "Student :: func_Student()"; }
	virtual void func1() { cout << "Student :: func1()"; }
	virtual void func2() { cout << "Student :: func2()"; }
	void func_normal() { cout << "Student :: func_normal()"; }
public:
	int _num;
};

在这里插入图片描述
这里没有列出派生类自己的虚函数是由于编译器的优化,并不是不存在;

(3.) 多态的实现

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

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

在这里插入图片描述
在这里插入图片描述

(4.)动态绑定与静态绑定

  1. 对不满足多态条件(指针或者引用+调用虚函数)的函数调用是在编译时绑定,也就是编译时确定调用函数的地址,叫做静态绑定。(编译阶段是指将编译语言转换为机器语言的阶段)
  2. 满足多态条件的函数调用是在运行时绑定,也就是在运行时到指向对象的虚函数表中找到调用函数的地址,也就做动态绑定。 (运行阶段指程序的可执行文件被加载到计算机内存中,并由操作系统调度执行的过程。这个阶段是程序实际发挥作用的阶段。)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值