主页 > 人工智能  > 

【C++进阶02】多态

【C++进阶02】多态

一、多态的概念及定义 1.1 多态的概念

多态简单来说就是多种形态 同一个行为,不同对象去完成时 会产生出不同的状态 多态分为静态多态和动态多态 静态多态指的是编译时 在程序编译期间确定了程序的行为 比如:函数重载 动态多态指的是运行时 在程序运行期间,根据具体拿到的类型 确定程序的具体行为,调用具体的函数

1.2 在继承中要构成多态的两个条件

必须通过父类指针或引用调用虚函数

虚函数的重写 函数名、参数类型、返回值都要相同

被virtual修饰的类成员函数称为虚函数

class Person { public: virtual void BuyTicket() { cout << "买票-全价" << endl;} }; 1.3 虚函数的重写(覆盖)

派生类中有一个跟基类完全相同的虚函数 (即派生类虚函数与基类虚函数的返回值类 型、函数名字、参数列表完全相同) 称子类的虚函数重写了基类的虚函数

普通函数的继承是实现继承 派生类继承了基类函数,可以使用函数 继承的是函数的实现 虚函数的继承是接口继承 派生类继承的是基类虚函数的接口 目的是为了重写,达成多态,继承的是接口 如果不实现多态,不要把函数定义成虚函数

class Person { public: virtual void BuyTicket() { cout << "买票-全价" << endl; } // 只要父类析构加了virtual就构成多态,子类加不加都可以正确释放 virtual ~Person() { cout << "~Person" << endl; }; }; class Student : public Person { public: // 子类可以不写virtual,因为他继承父类的接口,重写实现 virtual void BuyTicket() { cout << "买票-半价" << endl; } ~Student() { cout << "~Student" << endl; } }; void Func(Person& p) { p.BuyTicket(); } int main() { Person ps; Student st; // 构成多态后 Func(ps); // 传父类调用父类的虚函数 Func(st); // 传子类调用子类的虚函数 return 0; } 1.4 协变

如果是父子关系的指针或引用 返回值可以不同也构成多态

class A{}; class B : public A {}; class Person { public: virtual A* f() {return new A;} }; class Student : public Person { public: virtual B* f() {return new B;} }; 1.5 final和override

final: 修饰虚函数 表示该虚函数不能再被重写 现实中不常用,不能实现多态的虚函数 意义不大

class Car { public: virtual void Drive() final {} }; class Benz :public Car { public: virtual void Drive() {cout << "Benz-舒适" << endl;} };

override: 检查派生类虚函数 是否重写了基类某个虚函数 如果没有重写编译报错

class Car{ public: virtual void Drive(){} }; class Benz :public Car { public: virtual void Drive() override {cout << "Benz-舒适" << endl;} }; 1.6 重载、覆盖(重写)、隐藏(重定义)的对比

面试题经常被问到

1.7 抽象类

在虚函数后面加上 =0 这个函数就叫纯虚函数 包含纯虚函数的类叫做抽象类 抽象类不能实例化出对象 派生类继承后也不能实例化出对象 只有重写纯虚函数,派生类才能实例化出对象

class Car { public: // 纯虚函数 --- 抽象类 virtual void Drive() = 0; }; int main() { Car car; // 无法实例化对象 return 0; } 二、多态的原理 2.1 虚函数表

这里常考一道笔试题:sizeof(Base)是多少?

class Base { public: virtual void Func1() { cout << "Func1()" << endl; } private: int _b = 1; }; int main() { cout << sizeof(Base) << endl; return 0; }

在32位操作系统下是8 bit 在64位操作系统下是16 bit 通过调试发现还有个指针_vfptr 这个指针叫做虚函数表指针 本质是指针数组 用来存放虚函数的地址

对象中存的是虚表指针 虚表存的是虚函数指针 虚函数和普通函数一样的 都是存在代码段的

2.2 多态的原理

通过下面代码观察父子类 的虚表之间的关系

class Base { public: virtual void Func() { cout << "Base::Func1()" << endl; } }; class Derive : public Base { public: virtual void Func() { cout << "Derive::Func1()" << endl; } }; void Test(Base* p) { p->Func(); } int main() { Base b; Derive d; Test(&b); return 0; }

监视窗口 通过监视窗口可以发现 派生类对象d中也有一个虚表指针 通过地址发现基类和派生类的虚表是不一样的

虚函数表本质是存虚函数指针的指针数组 一般情况这个数组最后面放了一个nullptr

结论: 观察下图红色箭头 当传过来的是父类对象的地址 p->Func在父类的虚表中找对应的虚函数Func地址 当传过来的是子类对象的地址 p->Func在子类的虚表中找对应的虚函数Func地址

这样就实现不同对象的同一行为 展现的不同状态 当父类有虚函数而子类没有虚函数 也没有重名函数 子类是不能继承父类的虚表指针 子类会生成一个虚表指针 存父类的虚函数地址 当子类有虚函数而父类没有 父类也就不会有虚表 因为子类有的东西父类不一定有

派生类的虚表生成: a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数 用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的 声明次序增加到派生类虚表的最后

2.3 在多态下建议把基类的析构函数定义成虚函数

如果基类析构函数不是虚函数 就调不到派生类的析构函数 (指针类型是父类,所以调用父类的析构函数) 从而造成内存泄漏

class Person { public: virtual ~Person() {cout << "~Person()" << endl;} }; class Student : public Person { public: virtual ~Student() { cout << "~Student()" << endl; } }; int main() { Person* ps = new Student; delete ps; return 0; }

形成多态的条件之一便是 只能通过父类去调用 所以子类对象只能强转成父类类型 如果父类的析构函数不是虚函数 那子类便调不到自己的析构函数 因为子类对象的类型是父类 所以只能调用父类的析构函数 子类成员无法释放从而造成内存泄漏 父类析构函数定义成虚函数便能解决问题

✨✨✨✨✨✨✨✨ 本篇博客完,感谢阅读🌹 如有错误之处可评论指出 博主会耐心听取每条意见 ✨✨✨✨✨✨✨✨

标签:

【C++进阶02】多态由讯客互联人工智能栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“【C++进阶02】多态

上一篇
PyTorch之线性回归

下一篇
Kioptrix-3