C++多态

多态的概念

  • 多态是面向对象设计中的一个概念,与继承点此进入、封装并称为面向对象设计的三大特性。
  • 多态是不同的对象完成相同的行为会产生不同的结果。
例:
  • 游乐园买票时,成人全价,儿童可半价。
  • 发工资时,上层领导工资较多,底层员工较少。

多态的实现

  1. ​​​​​​​多态在继承的基础上
  2. 调用对象必须是指针或引用
  3. 被调用的函数必须为虚函数且完成了虚函数的重写
虚函数
  • 在函数前加virtual关键字的非静态成员函数
  • 形式一般为:virtual 函数返回值类型 虚函数名(形参表){函数体}
特点
  • 定义了虚函数后,可在派生类中对虚函数重新定义。派生类重新定义的函数与虚函数有相同的形参个数与形参类型。实现了统一接口,但实现过程不同。
虚函数的重写
  • 派生类中有和基类完全相同(函数名,参数,返回值)的虚函数,称派生类的虚函数重写了基类的虚函数。
  • 虚函数的重写又称虚函数的覆盖。
class Employee
{
public:
	virtual void Salary()
	{
		cout << "salary——little" << endl;
	}
};

class Leader:public Employee
{
public:
	virtual void Salary()
	{
		cout << "salary——many" << endl;
	}
};

void GetSalary(Employee& e)
{
	e.Salary();
}

int main()
{
	Employee em;
	Leader le;
	
	GetSalary(em);
	GetSalary(le);

	return 0;
}

可见调用了同一函数GetSalary() 出现了不同的结果

虚函数重写的例外:协变
  • 重写的虚函数的返回值可以不同,但必须是基类指针(引用)和派生类指针(引用)
class A
{
};

class B : public A
{
};

class Base
{
public:
	virtual A* func() { return new A; }
};
class Derived : public Base
{
public:
	virtual B* func() { return new B; }
};
重写不规范写法
  • 在派生类重写中可以不加virtual关键字,因为基类虚函数被继承至派生类后,依然具有虚函数属性。但不建议使用。
class Base
{
public:
	virtual void Func(){}
};

class Derived : public Base
{
public:
	void Func(){}
};
  • 注意:析构函数比较特殊。
  • 如果基类的析构函数是虚函数,则派生类的析构函数就重写了基类的析构函数。看起来他们函数名不同而违反了重写的规则。
  • 其实编译器对析构函数的名称进行了特殊处理,统一成destructor。
  • 建议析构函数写成虚函数。

多态的原理

虚函数表

下面看个例子

class Base
{
public:
	virtual void Func1()
	{}

	virtual void Func2()
	{}
private:
	int _a = 1;
	int _b = 2;
};

查看监视窗口

  • 可以看出来除了成员_a,_b,还多出了一个_vfptr的指针。这个指针称为虚函数表指针。
  • 一个虚函数类中至少有一个虚函数表指针,用虚函数表来存放虚函数的地址。
  • 那么这个虚函数表(又称虚表)中存放着什么呢?
class Base
{
public:
	virtual void Func1()
	{}

	virtual void Func2()
	{}

	void Func3()
	{}
private:
	int _a = 1;
};

class Derived : public Base
{
public:
	virtual void Func2()
	{}
private:
	int _b = 2;
};

经过对以上代码的测试得到了下面内容:
在这里插入图片描述
可以得出以下结论:

  • 基类对象b中有一个虚表指针,存放着自己的虚函数Func1,Func2,派生类对象d中也有一个虚表指针,存放着自己继承来的Func1和自己重写的Func2
  • Fun3同样被继承了下来,但由于不是虚函数因此没有在虚表中
  • 虚函数表本质是一个从存放虚函数指针的指针数组,最后存了nullptr
  • 派生类虚函数表的生成:
  1. 先拷贝一份基类的虚表内容至自己的虚表中
  2. 用自己重写过的虚函数覆盖基类的虚函数
  3. 将自己增加的虚函数按声明次序增加至虚表后

多态实现过程

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

  • 可以看出e指向em对象时,e->GetSalary在em的虚函数表中找到虚函数Employee::Salary
  • 可以看出e指向le对象时,e->GetSalary在em的虚函数表中找到虚函数Leader::Salary
  • 这就证明了不同对象完成同一行为,产生了不同结果。

在这里插入图片描述

查看反汇编可以看出
  • mov eax,dword ptr [e]//e中存的是em对象的地址,将e赋给eax
  • mov edx,dword ptr [eax]//将eax中的值(即为em对象)的前四个字节(虚标指针)赋给edx
  • mov eax,dword ptr [edx]//将edx的值(即虚表中前四个字节)赋给eax
  • call eax//eax存着虚函数的指针
    可以看出多态的调用实在运行后通过对象来查找的。

关于派生类的虚函数表在此片中详谈C++多态——虚函数表详谈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值