【C++】多态(详解)
前言:今天学习的内容可能是近段时间最难的一个部分的内容了,C++的多态,这部分内容博主认为难度比较大,各位一起慢慢啃下来。
💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
目录标题
- 什么是多态
- 什么是虚函数
- 多态的定义及实现
- 1.多态构成的条件
- 2. 虚函数的重写
- 3.协变
- 4.析构函数的重写
- 5. override 和 final
- 6. 重载、覆盖(重写)、隐藏(重定义)的对比
- 抽象类
- 多态的原理
- 虚函数表
- 虚表指针的内容
- 引用和指针如何实现多态
- 动态绑定与静态绑定
- 虚函数表存放位置
- 单继承和多继承中的虚拟表
什么是多态
多态的概念:多态(polymorphism)是C++中面向对象编程的一个重要概念,它指的是同一种消息(方法调用)在不同的对象上产生不同的行为。这种特性使得程序设计更加灵活,提高了代码的可扩展性和可维护性。(通俗来说,就是多种形态, 具体点就是去完成某个行为,当不同的对象去完成时会产生出不同的状态)。
什么是虚函数
在C++中,虚函数是一种特殊的成员函数,用于实现多态性。通过将基类的成员函数声明为虚函数,可以在派生类中对该函数进行重写。当通过基类指针或引用调用虚函数时,实际调用的是相应派生类中的函数。
虚函数的声明和定义如下:
class Base//基类 { public: virtual void foo() { // 函数实现 } };
在基类的函数声明前加上virtual关键字,就将该函数声明为虚函数。派生类可以选择重写基类的虚函数,使用相同的函数签名来定义派生类中的函数:
class Derived : public Base//派生类 { public: void foo() override { // 函数实现 } };
注意,在派生类中重写虚函数时,可以使用override关键字显式标注,以增强代码可读性。
使用虚函数时,需要通过基类指针或引用来调用虚函数。根据指针或引用所指向的具体对象类型,调用的将是相应对象的虚函数实现。
例如:
Base* base = new Derived(); base->foo(); // 调用的是Derived的foo()函数
在上述示例中,通过基类指针base指向Derived对象,并调用虚函数foo(),实际调用的是Derived类中的foo()函数。
总结来说,虚函数实现了在基类中声明一个函数,使其可以在派生类中被重写,并能通过基类指针或引用调用派生类对象的对应实现,从而实现多态性。
多态的定义及实现
1.多态构成的条件
C++中的多态性是指在相同的函数签名下,通过基类指针或引用调用不同的派生类对象时,能够实现不同的行为。在C++中,实现多态性需要满足以下三个条件:
- 存在继承关系:多态性需要至少有一个基类和一个或多个派生类。
- 基类函数为虚函数:基类中的函数必须声明为虚函数,以便在派生类中进行重写。子类父类都有这个虚函数 + 子类的虚函数与父类虚函数的函数名/参数/返回值 都相同 。
- 使用基类指针或引用:通过基类指针或引用来调用派生类对象的函数,实现函数的动态绑定。
下面是一个示例代码,演示了多态性的实现:
class Animal //基类 { public: virtual void sound() { cout public: virtual void sound() { cout public: virtual void sound() { cout s.sound(); } void test1() { Animal s1; Cat s2; Dog s3; func(s1); func(s2); func(s3); } void test2() { Animal* animal1 = new Animal();//当基类的指针指向派生类的时候,只能操作派生类中从基类中继承过来的数因据和基类自身的数据 Animal* animal2 = new Cat(); Animal* animal3 = new Dog(); animal1-sound(); // Animal is making a sound. animal2-sound(); // Cat is meowing. animal3-sound(); // Dog is barking. delete animal1; delete animal2; delete animal3; } int main() { test1(); test2(); return 0; } public: virtual Animal* clone() { cout public: virtual Dog* clone() override { cout Dog s1; Animal* s2 = s1.clone();//基类接受派生类的虚函数的返回值构造对象 return 0; } public: virtual ~Animal() { cout public: virtual ~Dog() override { cout Animal* animal = new Dog();//先调用子类的析构,在调用父类的析构 delete animal; return 0; } public: virtual void foo() const; }; class Derived : public Base { public: void foo() const override; // override关键字表示覆盖基类的虚函数 }; public: virtual void foo() const final; // final关键字表示禁止派生类进一步覆盖该虚函数 }; class Derived : public Base { public: // 下面的代码会导致编译错误,因为foo()函数被标记为final,无法再被派生类覆盖 // void foo() const; }; public: virtual void foo(); }; class Derived : public Base { public: void foo() override; }; public: void foo(int x); }; class Derived : public Base { public: void foo(float x); }; public: virtual void pureVirtualFunction() = 0; // 纯虚函数 virtual void virtualFunction() { // 非纯虚函数 // 具体实现 } }; public: void pureVirtualFunction() override { // 实现纯虚函数 } }; public: virtual void func1() { cout cout } int main() { Base b1; Base b2; static int a = 0; int b = 0; int* p1 = new int; const char* p2 = "hello world"; printf("静态区:%p\n", &a); printf("栈:%p\n", &b); printf("堆:%p\n", p1); printf("代码段:%p\n", p2); printf("虚表:%p\n", *((int*)&b1));//虚表的地址是存放在类对象的头4个字节上因此我们对其进行强转,取地址就会得到虚表的位置了, printf("虚函数地址:%p\n", & Base::func1); printf("普通函数:%p\n", func); }