hello,各位小伙伴,本篇文章跟大家一起学习《C++:多态》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 !
文章目录
- :maple_leaf:多态的概念
- :maple_leaf:继承中的多态
- 1.:leaves:虚函数表
- :maple_leaf:多态原理
🍁多态的概念
在 C++ 中,多态性(Polymorphism)是面向对象编程中一个重要的概念,它允许使用统一的接口来操作不同的对象,从而提高代码的灵活性、可维护性和可扩展性。多态性的实现依赖于两种主要机制:编译时多态性(静态多态性)和运行时多态性(动态多态性)。
- 编译时多态性(静态多态性):
- 在 C++ 中,编译时多态性主要通过函数重载和运算符重载来实现。这种多态性是在编译期间根据函数或运算符的参数类型和数量来选择调用的函数版本,称为静态绑定或早期绑定。
- 例如,函数重载允许在同一个作用域内定义多个函数名相同但参数列表不同的函数,编译器会根据调用时的参数类型来选择正确的函数。如下实现不同类型进行交换代码:
void Swap(int& left, int& right)
{int temp = left;left = right;right = temp;
}void Swap(double& left, double& right)
{double temp = left;left = right;right = temp;
}void Swap(char& left, char& right)
{char temp = left;left = right;right = temp;
}
- 运行时多态性(动态多态性):
- 运行时多态性是通过虚函数和继承关系来实现的。这种多态性允许在程序运行时根据对象的实际类型来调用对应的函数,称为动态绑定或后期绑定。
- 在 C++ 中,通过在基类中将成员函数声明为虚函数(使用 virtual 关键字),允许派生类覆盖(override)这些虚函数。当通过基类指针或引用调用虚函数时,会根据实际指向的对象类型来决定调用哪个函数版本。
下面是一个简单的示例,展示了 C++ 中的运行时多态性:
#include <iostream>
using namespace std;// Base class
class Animal {
public:// Virtual functionvirtual void speak() {cout << "Animal speaks!" << endl;}
};// Derived class overriding the speak() function
class Dog : public Animal {
public:// Override the speak() functionvoid speak() override {cout << "Dog barks!" << endl;}
};// Derived class overriding the speak() function
class Cat : public Animal {
public:// Override the speak() functionvoid speak() override {cout << "Cat meows!" << endl;}
};int main() {Animal *animal;Dog myDog;Cat myCat;// Pointer to Dog objectanimal = &myDog;animal->speak(); // Output: Dog barks!// Pointer to Cat objectanimal = &myCat;animal->speak(); // Output: Cat meows!return 0;
}
在这个示例中:
- Animal 类中的 speak() 函数被声明为虚函数。
- Dog 和 Cat 类都重写了 speak() 函数,实现了不同的动作。
- 在 main() 函数中,通过 Animal 类指针 animal 分别指向 Dog 对象和 Cat 对象,并调用它们的 speak() 函数。虽然指针类型是基类 Animal,但实际上根据指向的对象类型,调用的是对应的虚函数版本,展示了运行时多态性的特性。
通过多态性,C++ 提供了一种灵活且强大的机制,使得程序能够根据对象的实际类型来决定调用哪个函数版本,从而实现代码的重用性和扩展性。
🍁继承中的多态
1.🍃虚函数表
先出一道题,32位机器下,sizeof(Base)
答案是多少呢?
class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};
答案是:8
可以看到,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。
class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};
class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};
int main()
{Base b;Derive d;return 0;
}
- 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
- 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
- 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。
- 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
- 总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
- 这里还有一个童鞋们很容易混淆的问题:虚函数存在哪的?虚表存在哪的? 答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。但是很多童鞋都是这样深以为然的。注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚表中。另外对象中存的不是虚表,存的是虚表指针。
🍁多态原理
在CS2中,不同枪械的机动性不同:
class Gun
{
public:virtual void Mobility(Gun& gun) = 0;
};class AK47 : public Gun
{
public:void Mobility(Gun& gun){cout << "Mobility = " << _Mobility << endl;}
private:int _Mobility = 215;
};class M4A4 : public Gun
{
public:void Mobility(Gun& gun){cout << "Mobility = " << _Mobility << endl;}
private:int _Mobility = 225;
};void Func(Gun& gun)
{gun.Mobility(gun);
}int main()
{AK47 ak47;M4A4 m4a4;Gun& ak = ak47;Gun& m4 = m4a4;Func(ak);Func(m4);return 0;
}
- 多态必须要满足两个条件:
1.必须通过基类的指针或者引用调用虚函数
2.被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
举个简单例子解释,如下代码:
class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};
class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};int main()
{Base b;Derive d;Base& s1 = b;Base& s2 = d;s1.Func1();s2.Func1();return 0;
}
解释:Derive
继承Base
对Func1进行重写,重写后的虚函数会产生新的地址,放入虚表中Base&
引用子类Derive
对象,会发生切割,切割部分其实就是继承Base
部分,由于Derive
对Func1进行重写,所以s2
虚表中的Func1是已经重写的。
图解:
你学会了吗?
好啦,本章对于《C++:多态(继承)》的学习就先到这里,如果有什么问题,还请指教指教,希望本篇文章能够对你有所帮助,我们下一篇见!!!
如你喜欢,点点赞就是对我的支持,感谢感谢!!!