学习之路:C++多态

1.什么是多态

多态按照字面意思就是多种状态,在面向对象语言就是接口的不同的实现方式。
举例子例如同样是买票,你作为大学生买的学生票和作为成年人买的票的价格不同,这就是多态的一种形式。

2.多态的定义及其实现

1多态定义的构成条件

  • 调用函数的对象必须为指针或者引用
  • 被调用的函数必须为虚函数
    例如
class man//父类
{
	public:
	virtual void test()//虚函数
	{
		cout<<"is a man"<<endl;// 有一张虚表 
	}	
 } ;
 class student:public  man//子类
 {
 	public:
 	virtual void test()
	 {
	 	cout<<"is a student"<<endl;
	 }	
};

void need(man &a)//调用的函数
{
	a.test();
}

虚函数的例外:协变
虚函数重写有一个例外:一般的重写覆盖需要返回值相同,但是重写的虚函数的返回值可以不同,但必须分别是基类指针和派生类指针或者引用。
不规范的重写行为
在派生类中重写的成员函数可以不加virtual也能重写,这是因为基类的虚函数被继承下来了,所以派生类中依然是虚函数,所以可以,但是这种写法并不规范。
析构函数的重写问题
基类的析构函数如果是虚函数,则派生类的析构函数就重新了基类的析构函数,但是我们知道重写行为的函数,必须函数名相同,不考虑函数协变的情况下,返回类型也要相同。但是析构函数的名字并不同,这里可以理解为编译器对析构函数的函数名称统一处理为destructor。
接口继承和实现继承
普通函数的继承是一种实现继承。子类继承父类的函数,就可以使用父类的函数。虚函数的继承是接口继承,目的是为了重写,从而达到实现多态的目的,继承的是接口。

  • 重载,覆盖(重写),隐藏(重定义)
  • 重载:两个函数的作用域在同一类内,形参列表不同
  • 重写:两个函数分别在基类和派生类的作用域,函数名/参数/返回值相同(协变除外),函数必须是虚函数。
  • 重定义:两个函数分别在基类和派生类的作用域,函数名必须相同,且不构成重写。

3.抽象类
在虚函数后面写=0,则这个函数为纯虚函数,有纯虚函数的类叫做抽象类也叫接口类,抽象类不能实例化对象,派生类继承纯虚函数后,必须重写纯虚函数才能实例化对象,纯虚函数的作用就是规定了,继承类必须重写,提醒了接口继承

class A
{
	public:
	A(){
	}
	virtual void test()=0;//纯虚函数
	
};
class B: public A//继承函数
{
	public:
	 B(){
	}
	virtual void test()
	{
		cout<<"B";
	}
};

需要注意纯虚函数的写法为 virtual +返回值 +函数名 +形参列表 + =0 ,没有函数体。
小知识点:关键词final和override
override在派生类的虚函数后面写要求其必须基类的同名函数,否则程序会报错

class
{
public:
	A() {
	}
	virtual void test()
	{} ;

};
class B : public  A 
{
public:
	B() {
	}
	virtual void test() override
	{
		cout << "B";
	}
};

作用和纯虚函数有些相似,出现函数是在基类的虚函数后面加上=0,verride是在派生类的虚函数后面加上的。
final关键词的作用想法,有时候我们不想希望函数被重写覆盖,在后面加上final就可以实现。
4.多态的原理

class a
{
public:
a()
{};
virtual void test()=0;
}

通过sizeof可以得到类的大小为4,同时我们知道一个空类的大小为1,通过vs’2016的调试功能,可以看到有一个_vfptr的指针,对象中的这个指针我们称为虚函数表指针,有时也叫做虚表,注意与虚基表区别。
虚表的具体操作如下

class A
{
public:
	A() {
	}
	virtual void test1() {};
	virtual void test2() {};
	void test3() {};

};
class B : public A
{
public:
	B() {
	}
	virtual void test1()
	{
		cout << "B";
	}
private:
	int _b;
};
int main()
{
	A a;
	B b;
	cout << sizeof(b) << endl;
}

从vs2016的监视窗口可以看到如下结果
在这里插入图片描述
首先基类三个函数test1,test2,test3,其中test1和test2为虚函数,test3为普通函数,派生类重写了test1函数,从监视窗口可以看到

  • 派生类对象b也有一个虚表指针,d对象由两部分组成一部分是父类继承的成员,另一部分是自己的成员
  • 基类对象和派生类对象的虚表不完全相同,我们可以看到test1被重写所以b的虚表指针里存的是B::test1函数,所以虚函数的重写也叫覆盖,覆盖就是指虚表中虚函数的覆盖,重写是代码的叫法,覆盖是原理层的叫法。
  • 可以看到,test2在两个虚表的指针相同,因为在B中并没有重写test2的函数,而是继承下来,还有注意虽然test3也被B继承下来了,但是因为test3为普通函数所以不会放进虚表里面。
  • 虚函数表本质是一个存虚函数指针的指针数组,最后一个成语放nullptr。
  • 总结一下派生类的虚表生成:首先将基类的虚表内容复制拷贝一份到派生类虚表中,如果派生类重写了某个虚函数,用派生类自己重新的虚函数覆盖虚表中的基类的虚函数,按其声明次序增加到派生类虚表的最后
  • 注意虚表存的是虚函数指针,而不是虚函数,虚函数和普通的函数一样存在代码段中,对象存的不是虚表而是虚表指针在vs中经过测试虚表存在代码段中
    小知识点:
  • 满足多态的函数调用,不是在编译中确定的,是运行起来在对象里找的,而不满足多态的函数调用时编译时确认好的。
  • 在函数编译期间确定了程序的行为,称为静态多态,函数的重载。
  • 在程序运行期间,根据具体的类型确定程序的具体行为调用具体函数,称为动态多态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值