当前位置: 首页 > news >正文

CD61.【C++ Dev】多态(1)

目录

1.形象地理解多态

2.使用virtual关键字实现多态

虚函数的定义

示例代码

3.虚函数重写

4.多态核心要点(考频高!)

满足多态的严格条件

多态调用和普通对象调用的区别

虚函数重写的例外1:协变

虚函数重写的例外2: 析构函数的重写

为什么析构函数需要重写?

析构函数需要重写的特殊场景

5.override和final关键字

override

final

练习题


1.形象地理解多态

多态指的是多种形态或状态

看一个形象的例子:不同人买票的票价不同,非VIP会员全价买票,而VIP会员是有优惠价的

2.使用virtual关键字实现多态

例如以下代码的VIP_member由non_VIP_member继承而来,它们有相同的成员函数,前面都用了virtual关键字修饰来实现多态

虚函数的定义

使用virtual修饰的成员函数为虚函数,具体应用参见多态

示例代码

注:菱形继承也使用virtual,但虚继承和虚函数没有任何关系

class non_VIP_member
{
public:virtual void price() const{cout << "Full price" << endl;}
};class VIP_member:public non_VIP_member
{
public:virtual void price() const{cout << "Discounted price" << endl;}
};

测试代码:

void test(non_VIP_member& member)
{member.price();
}int main()
{non_VIP_member mem1;VIP_member mem2;test(mem1);test(mem2);return 0;
}

运行结果:

3.虚函数重写

虚函数的重写(覆盖):

1.子类中有一个跟父类完全相同的虚函数(即子类虚函数与父类虚函数的
返回值类型、函数名字、参数列表完全相同,简称三同),称子类的虚函数重写或覆盖了父类的虚函数

2.虚函数重写的是实现,使用父类形参的定义,不使用子类形参的定义

注: 只有成员函数才能变成虚函数

上方两个price函数其实是虚函数的重写,VIP_member和non_VIP_member的price的返回类型都为void,参数列表都只有隐藏的this指针

4.多态核心要点(考频高!)

满足多态的严格条件

1.调用的函数必须是重写的虚函数

2.参数是父类的指针或引用

3.父类成员函数必须加virtual,子类相同的成员函数可以不写(
为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性,建议子类也写上virtual)

如果去掉virtual,就算是指针或引用,也不是多态

class non_VIP_member
{
public:void price() const{cout << "Full price" << endl;}
};class VIP_member:public non_VIP_member
{
public:void price() const{cout << "Discounted price" << endl;}
};void test(non_VIP_member& member)
{member.price();
}int main()
{non_VIP_member mem1;VIP_member mem2;test(mem1);//mem1是non_VIP_member类型,调用它的price()test(mem2);//mem1是VIP_member类型,调用它的price()return 0;
}

运行结果:

多态调用和普通对象调用的区别

因此可以推出:

1. 多态调用看的是“指针或引用”实际指向的对象类型(运行时决定,动态绑定)
2. 普通对象调用看的是"当前静态类型" (编译期决定,静态绑定)

多态调用:

使用引用:

void test(non_VIP_member& member)
{member.price();
}int main()
{non_VIP_member mem1;VIP_member mem2;test(mem1);//mem1是non_VIP_member类型,调用它的price()test(mem2);//mem1是VIP_member类型,调用它的price()return 0;
}

运行结果:

使用指针:

void test(non_VIP_member* member)
{member->price();
}int main()
{non_VIP_member mem1;VIP_member mem2;test(mem1);//mem1是non_VIP_member类型,调用它的price()test(mem2);//mem1是VIP_member类型,调用它的price()return 0;
}

运行结果:

普通对象调用:

#include <iostream>
using namespace std;
class non_VIP_member
{
public:void price() const{cout << "Full price" << endl;}
};class VIP_member:public non_VIP_member
{
public:void price() const{cout << "Discounted price" << endl;}
};void test(non_VIP_member member)//非引用也非指针
{member.price();//一律调用non_VIP_member的price(),静态绑定
}int main()
{non_VIP_member mem1;VIP_member mem2;test(mem1);test(mem2);return 0;
}

运行结果:

虚函数重写的例外1:协变

C++11引入了协变:指重写的虚函数的返回的值可以不同,但是返回值必须是父子关系指针或引用,而且返回值必须同时是指针或者同时是引用

例如下面这样就不是协变:

这样就是协变:

class non_VIP_member
{
public:virtual non_VIP_member& price() {cout << "Full price" << endl;return *this;}
};class VIP_member:public non_VIP_member
{
public:virtual VIP_member&  price(){cout << "Discounted price" << endl;return *this;}
};

当然其他符合要求的写法也可以:

虚函数重写的例外2: 析构函数的重写

虽然父类与子类析构函数名字不同,看起来违反了虚函数重写的规则,但是如果条件合适也能成虚函数重写

如果父类的析构函数为虚函数,子类析构函数只要定义,无论子类析构函数是否加virtual关键字,都与基类的析构函数构成重写

这里可以理解为编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor

为什么析构函数需要重写?

class non_VIP_member
{
public:virtual ~non_VIP_member(){cout << "~non_VIP_member()" << endl;}
};class VIP_member:public non_VIP_member
{
public:virtual ~VIP_member(){cout << " ~VIP_member()" << endl;}
};

测试代码:

int main()
{non_VIP_member mem1;VIP_member mem2;return 0;
}

运行结果:

发现VIP_member析构了一次,non_VIP_member析构了两次

如果不加virtual,运行结果是一样的

上面的例子看不出问题,记下面这个特殊场景:

析构函数需要重写的特殊场景

例如这个场景没有重写:

#include <iostream>
using namespace std;
class non_VIP_member
{
public:~non_VIP_member(){cout << "~non_VIP_member()" << endl;}
};class VIP_member:public non_VIP_member
{
public:~VIP_member(){cout << " ~VIP_member()" << endl;delete[] area;}
protected:int* area = new int[100];
};int main()
{non_VIP_member* ptr=new non_VIP_member;//ptr是父类的指针delete ptr;ptr = new VIP_member;delete ptr;return 0;
}

VS上没有报内存泄漏,使用Linux下的valgrind内存泄漏检测工具,输入以下命令:

valgrind --tool=memcheck --leak-check=full ./a.out

(有关valgrind的介绍参见CD32.【C++ Dev】类和对象(22) 内存管理(下)文章)

运行结果:

==1212205== Memcheck, a memory error detector
==1212205== Copyright (C) 2002-2022, and GNU GPL'd, by Julian Seward et al.
==1212205== Using Valgrind-3.22.0 and LibVEX; rerun with -h for copyright info
==1212205== Command: ./a.out
==1212205== 
~non_VIP_member()
~non_VIP_member()
==1212205== Mismatched new/delete size value: 1
==1212205==    at 0x484A5B9: operator delete(void*, unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1212205==    by 0x109262: main (2.cpp:29)
==1212205==  Address 0x4e21510 is 0 bytes inside a block of size 8 alloc'd
==1212205==    at 0x4846FA3: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1212205==    by 0x10922F: main (2.cpp:28)
==1212205== 
==1212205== 
==1212205== HEAP SUMMARY:
==1212205==     in use at exit: 400 bytes in 1 blocks
==1212205==   total heap usage: 5 allocs, 4 frees, 75,161 bytes allocated
==1212205== 
==1212205== 400 bytes in 1 blocks are definitely lost in loss record 1 of 1
==1212205==    at 0x48485C3: operator new[](unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==1212205==    by 0x1092F2: VIP_member::VIP_member() (2.cpp:12)
==1212205==    by 0x109240: main (2.cpp:28)
==1212205== 
==1212205== LEAK SUMMARY:
==1212205==    definitely lost: 400 bytes in 1 blocks
==1212205==    indirectly lost: 0 bytes in 0 blocks
==1212205==      possibly lost: 0 bytes in 0 blocks
==1212205==    still reachable: 0 bytes in 0 blocks
==1212205==         suppressed: 0 bytes in 0 blocks
==1212205== 
==1212205== For lists of detected and suppressed errors, rerun with: -s
==1212205== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

内存泄漏了400字节(指int* ptr = new int[100];)

分析原因:

1.析构函数没有加virtual,因此不是多态

2.ptr=new VIP_member;是切片,ptr指向子对象中父类的那部分,不包含子类的area指针指向的内存区域,因此delete ptr时,是普通对象的调用,只析构子对象中属于父类的那部分,属于子类部分的400字节的空间没办法释放,导致内存泄漏

看看VS的反汇编:只调用了父类的析构函数,因为ptr的类型是non_VIP_member*

希望ptr指向谁就调用谁,那么就要加上virtual,将析构函数处理成destructor()

#include <iostream>
using namespace std;
class non_VIP_member
{
public:virtual  ~non_VIP_member(){cout << "~non_VIP_member()" << endl;}
};class VIP_member:public non_VIP_member
{
public:virtual ~VIP_member(){cout << " ~VIP_member()" << endl;delete area;}
protected:int* area = new int[100];
};int main()
{non_VIP_member* ptr=new non_VIP_member;//ptr是父类的指针delete ptr;ptr = new VIP_member;delete ptr;return 0;
}

使用valgrind再次检测:没有内存泄漏

修改后的代码满足多态的所有条件,多态调用看的是“指针或引用”实际指向的对象类型,那么ptr = new VIP_member;的ptr实际指向的对象类型是VIP_member,因此会调用VIP_member的析构函数,不看ptr原来定义的类型

可以发现是VIP_member的地址存放在eax寄存器中,是动态取得的

而eax指向的是跳转至VIP_member析构函数的指令

结论:析构函数需要重写,统一处理成destructor()

C++的库函数也是这样做的,例如exception:

5.override和final关键字

C++11引入了override和final关键字,用来帮助用户检测是否重写

override

作用:修饰子类虚函数,检查子类虚函数是否重写了父类某个虚函数,如果没有重写编译报错

final

作用:修饰父类的虚函数,表示该虚函数不能再被重写

练习题

1.下面代码编译会报错吗?

class non_VIP_member
{
public:virtual void func() final { }
};class VIP_member:public non_VIP_member
{
public:virtual void func(int) { }
};int main(){ }

解析:

final表示该虚函数不能再被重写,而且这两个func()不构成虚函数的重写(参数列表不一样)

因此不会报错,但func()构成隐藏,因为函数名相同

2.下面代码编译会报错吗?

class non_VIP_member
{
public:virtual void func(){ }
};class VIP_member:public non_VIP_member
{
public:virtual void func(int) override { } 
};int main(){ }

解析:

虽然func()不构成虚函数的重写,但是override是检查子类虚函数是否重写了父类某个虚函数,如果没有重写编译报错

显然父类non_VIP_member没有func(int),所以会报错

http://www.dtcms.com/a/319456.html

相关文章:

  • 腾讯云EdgeOne产品深度分析报告
  • Docker入门教程:在腾讯云轻量服务器上部署你的第一个容器化应用 (2025)
  • 基于Matlab图像处理的黄豆自动计数系统设计与实现
  • 【数据结构入门】双向链表
  • Windows中安装rustup-init.exe以及cargo build报错443
  • ENSP 中静态路由负载分担
  • linux开发之mmap内存映射
  • 算法解决爬楼梯问题
  • SQL注入攻击基础
  • 【LVGL自学笔记暂存】
  • 如何正确选择建站工具?
  • FPGA高端项目:图像采集+Aurora 8B10B+UDP图传架构,基于GTP高速收发器的光口转网口,提供4套工程源码和技术支持
  • 旧物回收小程序系统开发:连接你我,共筑环保梦想
  • Linux下动态库链接的详细过程
  • 【网络运维】Linux:NFS服务器原理及配置
  • Kafka数据生产和发送
  • RuoYi OpenAPI集成从单体到微服务改造全过程记录
  • 高速公路安装定向广播的优势
  • centos VMware ESXi 扩容
  • 为什么任务顺序会影响效率?如何实现自定义顺序?
  • Python 基础详解:数据类型(Data Types)—— 程序的“数据基石”
  • Fiddler 安装配置教程
  • 认识汇编:解码计算机思维的底层语言(第一章)
  • 【YOLO学习笔记】YOLOv8详解解读
  • WEB开发-第二十七天(PHP篇)
  • 【Unity Plugins】使用ULipSync插件实现人物唇形模拟
  • 基于Spring Cloud Stream与Kafka的事件驱动微服务架构设计与实战指南
  • 【Python】基于Python自动化邮件发送系统:从配置到实现的完整指南
  • 【YOLOv8改进 - C2f融合】C2f融合SFS-Conv(空间 - 频率选择卷积)提升特征多样性,同时减少参数和计算量
  • 如何在 VS Code 中进行 `cherry-pick`