《21天学通C++》(第十一章)多态

2024-04-30 08:36

本文主要是介绍《21天学通C++》(第十一章)多态,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

为什么需要多态?
为了最大限度地减少代码,提高可读性

1.虚函数

虚函数是C++中的一种特殊成员函数,它允许在派生类(也称为子类)中重写(覆盖)基类的实现,使用virtual进行声明

在C++中,如果基类中的成员函数不是虚函数,派生类中的同名函数并不会覆盖或重写基类中的函数,而是产生函数隐藏,意味着如果你通过基类类型的指针或引用调用该函数,实际上调用的是基类中的版本,而不是派生类中的版本。

不使用虚函数:

#include <iostream>
using namespace std;class Base {
public:// 普通函数,不是虚函数void func() {cout << "Base func" << endl;}
};class Derived : public Base {
public:// 看起来像是重写,实际上是函数隐藏void func() {cout << "Derived func" << endl;}
};int main() {Base* basePtr = new Derived();basePtr->func(); // 调用 Base::func,而不是 Derived::func//输出结果为Base funcdelete basePtr;system("pause");return 0;
}

使用虚函数

#include <iostream>
using namespace std;class Base {
public:// 声明为虚函数virtual void func() {cout << "Base func" << endl;}
};class Derived : public Base {
public://真正地重写void func() {cout << "Derived func" << endl;}
};int main() {Base* basePtr = new Derived();basePtr->func(); // 正确调用 Derived::func//输出结果为Derived funcdelete basePtr;system("pause");return 0;
}

2.使用虚函数实现多态行为

通过函数引用实现

#include <iostream>
using namespace std;// 基类 Fish,定义了鱼类的通用行为
class Fish {
public:// 虚函数 swim,允许派生类重写,实现多态virtual void swim() const {cout << "Fish is swimming" << endl;}
};// 派生类 Tuna,继承自 Fish
class Tuna : public Fish {
public:// 重写 Fish 类的 swim 函数,实现 Tuna 类特有的游泳行为void swim() const override {cout << "Tuna is swimming fast" << endl;}
};// 派生类 Carp,继承自 Fish
class Carp : public Fish {
public:// 重写 Fish 类的 swim 函数,实现 Carp 类特有的游泳行为void swim() const override {cout << "Carp is swimming slowly" << endl;}
};// 函数,使用 Fish 类的引用参数来实现多态
void makeFishSwim(const Fish& fish) {fish.swim(); // 根据传入对象的实际类型调用相应的 swim 方法
}int main() {Tuna tuna;Carp carp;// 通过引用传递给函数,实现多态makeFishSwim(tuna); // 输出 "Tuna is swimming fast"makeFishSwim(carp); // 输出 "Carp is swimming slowly"system("pause");return 0;
}

通过指针实现:

#include <iostream>
using namespace std;// 基类 Fish,定义了鱼类的通用行为
class Fish {
public:// 虚函数 swim,允许派生类重写,实现多态virtual void swim() {cout << "Fish is swimming" << endl;}// 虚析构函数virtual ~Fish() {cout << "Fish is deconstructed" << endl;}
};// 派生类 Tuna,继承自 Fish
class Tuna : public Fish {
public:// 重写 Fish 类的 swim 函数,实现 Tuna 类特有的游泳行为void swim() override {cout << "Tuna is swimming fast" << endl;}// Tuna 类的析构函数~Tuna() {cout << "Tuna is deconstructed" << endl;}
};// 派生类 Carp,继承自 Fish
class Carp : public Fish {
public:// 重写 Fish 类的 swim 函数,实现 Carp 类特有的游泳行为void swim() override {cout << "Carp is swimming slowly" << endl;}// Carp 类的析构函数~Carp() {cout << "Carp is deconstructed" << endl;}
};int main() {// 创建派生类对象Fish* fish = new Tuna();fish->swim(); // 调用 Tuna::swim,输出 "Tuna is swimming fast"Fish* carp = new Carp();carp->swim(); // 调用 Carp::swim,输出 "Carp is swimming slowly"// 删除对象,调用相应的析构函数delete fish;delete carp;system("pause");return 0;
}

3.虚函数的工作原理——虚函数表

虚函数表(通常称为vtable)是C++中实现运行时多态的一种机制。当一个类包含至少一个虚函数时,编译器会为这个类创建一个虚函数表,这张表包含了类中所有虚函数的地址。

工作流程如下:

1.虚函数表的创建: 当一个类中包含至少一个虚函数时,编译器会为这个类创建一个虚函数表。这个表包含了该类所有虚函数的地址。

2.虚函数表指针: 编译器为每个对象添加一个指针,指向其类的虚函数表。这个指针通常存储在对象的内存布局的最前面。

3.调用虚函数: 当你通过一个基类指针或引用调用一个虚函数时,编译器生成的代码首先会访问对象的虚函数表指针,然后查找并调用表中对应的函数。

4.动态绑定: 由于虚函数表的存在,函数调用的解析是在运行时进行的,这称为动态绑定或晚期绑定。这意味着即使基类指针指向的是派生类对象,调用的也是派生类中重写的函数版本。

class Base {
public:virtual void show() {std::cout << "Base show" << std::endl;}virtual ~Base() {}  // 虚析构函数
};class Derived : public Base {
public:void show() override {  // 重写基类中的虚函数std::cout << "Derived show" << std::endl;}
};int main() {Base* basePtr = new Derived();  // 创建Derived对象的指针,但声明为Base类型basePtr->show();  // 调用show(),虽然basePtr是Base类型,但实际调用的是Derived的show()delete basePtr;return 0;
}

4.抽象基类和纯虚函数

抽象基类: 至少包含一个纯虚函数,而且无法被实例化,只能用于派生其他类,简称为ABC

纯虚函数: 它在基类中声明但故意不提供实现,其声明的函数体部分使用 = 0 来标识

virtual ReturnType FunctionName() = 0;

抽象基类使用方法如下:

#include <iostream>
using namespace std;// 抽象基类
class Shape {
public:// 纯虚函数,用于定义绘制形状的接口virtual void draw() const = 0;// 虚析构函数,确保派生类的析构函数被正确调用virtual ~Shape() {}
};// 派生类 Circle,表示圆形
class Circle : public Shape {
public:// 实现 Circle 的 draw 方法void draw() const override {std::cout << "Drawing a circle." << std::endl;}
};// 派生类 Rectangle,表示矩形
class Rectangle : public Shape {
public:// 实现 Rectangle 的 draw 方法void draw() const override {std::cout << "Drawing a rectangle." << std::endl;}
};int main() {// 创建一个指向 Shape 的指针数组,用于存储不同形状的指针Shape* shapes[] = { new Circle(), new Rectangle() };// 使用基类指针调用 draw 方法,实现多态for (Shape* shape : shapes) {shape->draw(); // 根据对象的实际类型调用相应的派生类的 draw 方法}// 释放动态分配的内存for (Shape* shape : shapes) {delete shape;}system("pause");return 0;
}

5.使用虚继承解决菱形问题

菱形问题: 即一个派生类继承自两个中间基类,而这两个中间基类又都继承自同一个基类时。这种继承结构在类图上看起来像一个菱形,因此得名。
在这里插入图片描述
田园犬类同时继承狗类和哺乳类,而哺乳类和狗类又同时继承动物类,呈现一个菱形结构。

在这个例子中田园犬类会分别从狗类和哺乳类中各自继承一个动物类,导致内存浪费和潜在的一致性问题,所以为了解决这个问题,可以使用虚函数继承来解决

#include <iostream>
using namespace std;// 定义基类 Animal
class Animal {
public:// 动物的呼吸方法virtual void breathe() { cout << "Animal breathes" << endl; }// 虚析构函数,确保派生类可以正确释放资源virtual ~Animal() {}
};// 定义中间基类 Mammal,使用虚继承自 Animal
class Mammal : virtual public Animal {
public:// 哺乳动物特有的哺育行为void nurse() { cout << "Mammal nurses its young" << endl; }// 虚析构函数virtual ~Mammal() {}
};// 定义中间基类 Dog,使用虚继承自 Animal
class Dog : virtual public Animal {
public:// 狗的吠叫行为void bark() { cout << "Dog barks" << endl; }// 虚析构函数virtual ~Dog() {}
};// 定义派生类 Poodle,同时继承自 Dog 和 Mammal
class Poodle : public Dog, public Mammal {
public:// 贵宾犬特有的行为void prance() { cout << "Poodle prances" << endl; }// 虚析构函数virtual ~Poodle() {}
};// 主函数
int main() {// 创建 Poodle 对象Poodle myPoodle;// 调用从各个基类继承来的方法myPoodle.bark();    // Dog 类的 bark 函数myPoodle.nurse();   // Mammal 类的 nurse 函数myPoodle.breathe();  // Animal 类的 breathe 函数myPoodle.prance();   // Poodle 类的 prance 函数system("pause"); // 用于在控制台程序结束前暂停,以便查看输出return 0;
}

6.表明覆盖意图的限定符override

使用override关键字有助于编译器检查函数签名是否与基类中的虚函数相匹配,从而提高代码的可读性和安全性。

使用方法如下:

class Base {
public:virtual void function() {// 基类}
};class Derived : public Base {
public:void function() override { // 使用 override 明确指出重写// 派生类}
};

7.使用final禁止覆盖函数

final关键字用于阻止派生类进一步重写(覆盖)基类中的虚函数。当你希望某个虚函数在派生类中保持最终实现,不允许任何进一步的重写时,可以使用final关键字。

class Base {
public:virtual void function() final {//使用final禁止覆盖// 基类实现}
};class Derived : public Base {
public:void function() override { // 这里会编译错误,因为 Base::function() 被声明为 final// 派生类实现}
};

这篇关于《21天学通C++》(第十一章)多态的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/948382

相关文章

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

06 C++Lambda表达式

lambda表达式的定义 没有显式模版形参的lambda表达式 [捕获] 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 有显式模版形参的lambda表达式 [捕获] <模版形参> 模版约束 前属性 (形参列表) 说明符 异常 后属性 尾随类型 约束 {函数体} 含义 捕获:包含零个或者多个捕获符的逗号分隔列表 模板形参:用于泛型lambda提供个模板形参的名

6.1.数据结构-c/c++堆详解下篇(堆排序,TopK问题)

上篇:6.1.数据结构-c/c++模拟实现堆上篇(向下,上调整算法,建堆,增删数据)-CSDN博客 本章重点 1.使用堆来完成堆排序 2.使用堆解决TopK问题 目录 一.堆排序 1.1 思路 1.2 代码 1.3 简单测试 二.TopK问题 2.1 思路(求最小): 2.2 C语言代码(手写堆) 2.3 C++代码(使用优先级队列 priority_queue)

【C++高阶】C++类型转换全攻略:深入理解并高效应用

📝个人主页🌹:Eternity._ ⏩收录专栏⏪:C++ “ 登神长阶 ” 🤡往期回顾🤡:C++ 智能指针 🌹🌹期待您的关注 🌹🌹 ❀C++的类型转换 📒1. C语言中的类型转换📚2. C++强制类型转换⛰️static_cast🌞reinterpret_cast⭐const_cast🍁dynamic_cast 📜3. C++强制类型转换的原因📝

C++——stack、queue的实现及deque的介绍

目录 1.stack与queue的实现 1.1stack的实现  1.2 queue的实现 2.重温vector、list、stack、queue的介绍 2.1 STL标准库中stack和queue的底层结构  3.deque的简单介绍 3.1为什么选择deque作为stack和queue的底层默认容器  3.2 STL中对stack与queue的模拟实现 ①stack模拟实现

c++的初始化列表与const成员

初始化列表与const成员 const成员 使用const修饰的类、结构、联合的成员变量,在类对象创建完成前一定要初始化。 不能在构造函数中初始化const成员,因为执行构造函数时,类对象已经创建完成,只有类对象创建完成才能调用成员函数,构造函数虽然特殊但也是成员函数。 在定义const成员时进行初始化,该语法只有在C11语法标准下才支持。 初始化列表 在构造函数小括号后面,主要用于给

2024/9/8 c++ smart

1.通过自己编写的class来实现unique_ptr指针的功能 #include <iostream> using namespace std; template<class T> class unique_ptr { public:         //无参构造函数         unique_ptr();         //有参构造函数         unique_ptr(