知识图谱

一.多态的概念
1.概念
- 当不同的对象接受到相同的信息后,会表现出不同的行为
- 条件:不同的对象必须处于同一继承层次结构
2.例子
假设你在家里有一只宠物。宠物可以是 猫、狗 或 鸟。每种宠物都会发出叫声,但每种宠物的叫声不同。我们可以定义一个基类
Pet,然后让Cat、Dog和Bird继承自这个基类。
class Pet {
public:virtual void makeSound() {cout << "Some generic pet sound" << endl;}
};class Cat : public Pet {
public:void makeSound() override {cout << "Meow" << endl;}
};class Dog : public Pet {
public:void makeSound() override {cout << "Woof" << endl;}
};class Bird : public Pet {
public:void makeSound() override {cout << "Tweet" << endl;}
};
注意:基类中的makesound是虚函数
现在假设你有一个
Pet类型的指针,它可以指向任何一种具体的宠物对象:Pet* myPet = new Dog(); myPet->makeSound(); // 输出 "Woof"尽管
myPet是Pet类型的指针,但它指向的是一个Dog对象。因此,当你调用makeSound()方法时,会调用Dog类的makeSound()方法,而不是Pet类的。(这就是多态)如果你把
myPet指向一个Cat对象:myPet = new Cat(); myPet->makeSound(); // 输出 "Meow"
3.总结
多态
1️⃣:允许你用 基类指针或引用来操作不同的派生类对象。2️⃣:当调用虚函数时,程序会 根据实际对象的类型来调用对应的方法,而不是根据指针的类型。
3️⃣:这使得程序更加灵活,易于扩展和维护。
4.提出疑惑(帮助理解)
1️⃣:构成多态需要那些条件 ?
2️⃣:什么是虚函数? 代码中关键字 virtual 是干什么的 ?
3️⃣:为什么 派生类中 可以出现 和 基类 同样的函数,这样会不会构成 继承中的 隐藏呢 ?
4️⃣:为什么 指向基类(Pet)的指针可以指向派生类(Dog 、Cat)的对象呢 ?
5️⃣:代码 派生类 中出现的关键字 override 是干什么呢 ?
二.多态的定义及实现
多态构成的条件
1️⃣:继承关系(Inheritance)
多态 依赖于类之间的 继承 关系。至少需要一个基类和一个或多个派生类。派生类从基类继承属性和方法,并可以覆盖基类的方法。
2️⃣:虚函数(Virtual Functions)
基类中必须有一个或多个虚函数。这些虚函数是用 virtual 关键字声明的。虚函数允许派生类重写它们,从而实现多态性。
// 基类
class Base {
public:virtual void display() // 虚函数{cout << "Display from Base" << endl;}
};// 派生类
class Derived : public Base {
public:void display() override {cout << "Display from Derived" << endl;}
};
3️⃣:必须通过基类的指针或引用来调用虚函数
使用 基类指针 或 引用 来 引用派生类对象。这是实现多态性的关键,因为通过基类指针或引用调用虚函数时,程序 会根据实际指向的对象类型调用对应的函数。
Base* b = new Derived();
b->display(); // 输出 "Display from Derived"

4️⃣:虚函数表(Virtual Table, VTable)
虚函数表是编译器在处理虚函数时创建的一个数据结构。它包含类的虚函数的指针。当通过基类指针调用虚函数时,程序会查找虚函数表并调用实际的函数实现。(了解)
5️⃣:基类中
virtual修饰后的函数形成虚函数,与派生类中的 override 修饰函数形成 重写(三同:返回值、函数名、参数均相同)
例子
#include <iostream>
using namespace std;// 基类
class Animal {
public:// 虚函数virtual void makeSound() {cout << "Some generic animal sound" << endl;}
};// 派生类
class Dog : public Animal {
public:// 函数重写void makeSound() override { cout << "Woof" << endl;}
};class Cat : public Animal {
public:// 函数重写void makeSound() override {cout << "Meow" << endl;}
};int main() {// 基类指针指向派生类对象Animal* animal1 = new Dog();Animal* animal2 = new Cat();// 调用虚函数,根据实际对象类型调用对应的方法animal1->makeSound(); // 输出 "Woof"animal2->makeSound(); // 输出 "Meow"delete animal1;delete animal2;return 0;
}

详细解释:
- 继承关系:Animal 是基类,Dog 和 Cat 是从 Animal 继承的派生类。
- 虚函数:基类 Animal 中的 makeSound 函数被声明为虚函数。
- 基类指针:在 main 函数中,Animal* animal1 和 Animal* animal2 分别指向 Dog 和 Cat 对象。
- 虚函数表:编译器为 Animal 类创建一个虚函数表,其中包含 makeSound 函数的指针。Dog 和 Cat 类覆盖了 makeSound 函数,因此它们各自的虚函数表会包含指向相应重写函数的指针。
- 虚函数重写:Dog 和 Cat 中的 makeSound 函数均被 override 修饰 ,并且 返回值、函数名、参数均相同,形成了虚函数重写
🔥 虚函数的重写🔥
虚函数重写(覆盖)是指派生类重新定义基类中的虚函数(返回值、函数名、参数列表,均相同)。当通过基类指针或引用调用虚函数时,程序会根据实际对象类型调用对应的重写函数,而不是基类中的函数。
具体实现步骤
- 定义基类和虚函数
- 定义派生类并重写虚函数
- 通过基类指针或引用调用虚函数
详细示例:
基类和虚函数
首先,我们定义一个基类 Base,其中包含一个虚函数 display:
#include <iostream>
using namespace std;class Base {
public:virtual void display() {cout << "Display from Base" << endl;}
};
- virtual 关键字用于声明 display 是一个虚函数。
- 这样,派生类可以选择性地重写这个函数。
派生类和函数重写
然后,我们定义一个派生类 Derived,它继承自 Base 并重写 display 函数:
class Derived : public Base {
public:void display() override {cout << "Display from Derived" << endl;}
};
override关键字表明这个函数是 重写基类中的虚函数。- 虽然
override是可选的,但推荐使用它来确保函数签名正确匹配基类中的虚函数。
使用基类指针调用重写的虚函数
在 main 函数中,我们使用基类指针来调用虚函数:
int main() {Base* b = new Derived();b->display(); // 输出 "Display from Derived"delete b;return 0;
}
Base* b = new Derived();创建一个基类指针b,指向一个Derived对象。b->display();调用display函数,由于b实际上指向的是Derived对象,因此会调用Derived类中的display函数。


虚析构
#include <iostream>
using namespace std;class Base {
public:virtual ~Base() {cout << "Base destructor called" << endl;}
};class Derived : public Base {
public:~Derived() {cout << "Derived destructor called" << endl;}
};int main() {Base* b = new Derived();delete b; // 会调用 Derived 和 Base 的析构函数return 0;
}
输出
Derived destructor called
Base destructor called

部分参考了这位大佬的文章
sunny-ll-CSDN博客

