欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 艺术 > 多态性(一)

多态性(一)

2025/12/13 4:14:02 来源:https://blog.csdn.net/Y2663438690/article/details/144721967  浏览:    关键词:多态性(一)

知识图谱

 一.多态的概念

1.概念

  • 当不同的对象接受到相同的信息后,会表现出不同的行为
  • 条件:不同的对象必须处于同一继承层次结构

2.例子 

假设你在家里有一只宠物。宠物可以是 、狗 或 。每种宠物都会发出叫声,但每种宠物的叫声不同。我们可以定义一个基类 Pet,然后让 CatDog 和 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博客

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词