C++ Virtual详解

Virtual是C++ OO机制中很重要的一个关键字。只要是学过C++的人都知道在类Base中加了Virtual关键字的函数就是虚拟函数(例如函数print),于是在Base的派生类Derived中就可以通过重写虚拟函数来实现对基类虚拟函数的覆盖。当基类Base的指针point指向派生类Derived的对象时,对point的print函数的调用实际上是调用了Derived的print函数而不是Base的print函数。这是面向对象中的多态性的体现。(关于虚拟机制是如何实现的,参见Inside the C++ Object Model ,Addison Wesley 1996)

class Base
{
public:Base(){}
public:
       virtual void print(){cout<<"Base";}
};
 
class Derived:public Base
{
public:Derived(){}
public:
       void print(){cout<<"Derived";}
};
 
int main()
{
       Base *point=new Derived();
       point->print();
}

Output:
Derived

为了帮助您更好地入门并深入掌握C++,我们精心准备了一系列丰富的学习资源包,包括但不限于基础语法教程、实战项目案例、核心概念解析以及进阶技巧指导等。

您只扫码上方二维码,即可免费获取这份专属的学习礼包。我们的教程覆盖了C++语言的各个方面,旨在让您在理论学习与实践操作中不断进步,提升编程技能。

同时,我们也鼓励您在学习过程中遇到任何问题时积极提问,我们会尽全力提供解答和帮助。期待您在C++编程的道路上越走越远,早日成为一位优秀的C++开发

 

这也许会使人联想到函数的重载,但稍加对比就会发现两者是完全不同的:
(1)       重载的几个函数必须在同一个类中;
覆盖的函数必须在有继承关系的不同的类中
(2)       覆盖的几个函数必须函数名、参数、返回值都相同;
重载的函数必须函数名相同,参数不同。参数不同的目的就是为了在函数调用的时候编译器能够通过参数来判断程序是在调用的哪个函数。这也就很自然地解释了为什么函数不能通过返回值不同来重载,因为程序在调用函数时很有可能不关心返回值,编译器就无法从代码中看出程序在调用的是哪个函数了。
(3)       覆盖的函数前必须加关键字Virtual;
重载和Virtual没有任何瓜葛,加不加都不影响重载的运作。
 
关于C++的隐藏规则:
我曾经听说过C++的隐藏规则:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

#include <iostream.h>
class Base
{
public:
virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
void g(float x){ cout << "Base::g(float) " << x << endl; }
void h(float x){ cout << "Base::h(float) " << x << endl; }
};
 
class Derived : public Base
{
public:
virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }
void g(int x){ cout << "Derived::g(int) " << x << endl; }
void h(float x){ cout << "Derived::h(float) " << x << endl; }
};
 
void main(void)
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
// Good : behavior depends solely on type of the object
pb->f(3.14f); // Derived::f(float) 3.14
pd->f(3.14f); // Derived::f(float) 3.14
// Bad : behavior depends on type of the pointer
pb->g(3.14f); // Base::g(float) 3.14
pd->g(3.14f); // Derived::g(int) 3 (surprise!)
// Bad : behavior depends on type of the pointer
pb->h(3.14f); // Base::h(float) 3.14 (surprise!)
pd->h(3.14f); // Derived::h(float) 3.14
}

bp 和dp 指向同一地址,按理说运行结果应该是相同的,而事实上运行结果不同,所以他把原因归结为C++的隐藏规则,其实这一观点是错的。决定bp和dp调用函数运行结果的不是他们指向的地址,而是他们的指针类型。“只有在通过基类指针或引用间接指向派生类子类型时多态性才会起作用”(C++ Primer 3rd Edition)。pb是基类指针,pd是派生类指针,pd的所有函数调用都只是调用自己的函数,和多态性无关,所以pd的所有函数调用的结果都输出Derived::是完全正常的;pb的函数调用如果有virtual则根据多态性调用派生类的,如果没有virtual则是正常的静态函数调用,还是调用基类的,所以有virtual的f函数调用输出Derived::,其它两个没有virtual则还是输出Base::很正常啊,nothing surprise!
所以并没有所谓的隐藏规则,虽然《高质量C++/C 编程指南》是本很不错的书,可大家不要迷信哦。记住“只有在通过基类指针或引用间接指向派生类子类型时多态性才会起作用”。
 
纯虚函数:
C++语言为我们提供了一种语法结构,通过它可以指明,一个虚拟函数只是提供了一个可被子类型改写的接口。但是,它本身并不能通过虚拟机制被调用。这就是纯虚拟函数(pure
virtual function)。 纯虚拟函数的声明如下所示:

class Query {
public:
// 声明纯虚拟函数
virtual ostream& print( ostream&=cout ) const = 0;
// ...
};

这里函数声明后面紧跟赋值0。
包含(或继承)一个或多个纯虚拟函数的类被编译器识别为抽象基类。试图创建一个抽象基类的独立类对象会导致编译时刻错误。(类似地通过虚拟机制调用纯虚拟函数也是错误的例如)

// Query 声明了纯虚拟函数
// 所以, 程序员不能创建独立的 Query 类对象
// ok: NameQuery 中的 Query 子对象
Query *pq = new NameQuery( "Nostromo" );
// 错误: new 表达式分配 Query 对象
Query *pq2 = new Query;

抽象基类只能作为子对象出现在后续的派生类中。
 
如果只知道virtual加在函数前,那对virtual只了解了一半,virtual还有一个重要用法是virtual public,就是虚拟继承。虚拟继承在C++ Primer中有详细的描述,下面稍作修改的阐释一下:
在缺省情况下C++中的继承是“按值组合”的一种特殊情况。当我们写
class Bear : public ZooAnimal { ... };
每个Bear 类对象都含有其ZooAnimal 基类子对象的所有非静态数据成员以及在Bear中声明的非静态数据成员类似地当派生类自己也作为一个基类对象时如:
class PolarBear : public Bear { ... };
则PolarBear 类对象含有在PolarBear 中声明的所有非静态数据成员以及其Bear 子对象的所有非静态数据成员和ZooAnimal 子对象的所有非静态数据成员。在单继承下这种由继承支持的特殊形式的按值组合提供了最有效的最紧凑的对象表示。在多继承下当一个基类在派生层次中出现多次时就会有问题最主要的实际例子是iostream 类层次结构。ostream 和istream 类都从抽象ios 基类派生而来,而iostream 类又是从ostream 和istream 派生
class iostream :public istream, public ostream { ... };
缺省情况下,每个iostream 类对象含有两个ios 子对象:在istream 子对象中的实例以及在ostream 子对象中的实例。这为什么不好?从效率上而言,存储ios 子对象的两个复本,浪费了存储区,因为iostream 只需要一个实例。而且,ios 构造函数被调用了两次每个子对象一次。更严重的问题是由于两个实例引起的二义性。例如,任何未限定修饰地访问ios 的成员都将导致编译时刻错误:到底访问哪个实例?如果ostream 和istream 对其ios 子对象的初始化稍稍不同,会怎样呢?怎样通过iostream 类保证这一对ios 值的一致性?在缺省的按值组合机制下,真的没有好办法可以保证这一点。
C++语言的解决方案是,提供另一种可替代按“引用组合”的继承机制虚拟继承(virtual inheritance )在虚拟继承下只有一个共享的基类子对象被继承而无论该基类在派生层次
中出现多少次共享的基类子对象被称为虚拟基类。
       通过用关键字virtual 修政一个基类的声明可以将它指定为被虚拟派生。例如,下列声明使得ZooAnimal 成为Bear 和Raccoon 的虚拟基类:

// 关键字 public 和 virtual
// 的顺序不重要
class Bear : public virtual ZooAnimal { ... };
class Raccoon : virtual public ZooAnimal { ... };

虚拟派生不是基类本身的一个显式特性,而是它与派生类的关系如前面所说明的,虚拟继承提供了“按引用组合”。也就是说,对于子对象及其非静态成员的访问是间接进行的。这使得在多继承情况下,把多个虚拟基类子对象组合成派生类中的一个共享实例,从而提供了必要的灵活性。同时,即使一个基类是虚拟的,我们仍然可以通过该基类类型的指针或引用,来操纵派生类的对象。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/530091.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

LRU算法的实现

目录 一&#xff0c;LRU算法 二&#xff0c;使用场景 三&#xff0c;LRU算法实现 一&#xff0c;LRU算法 LRU-least recently used-最近最少使用算法&#xff0c;是一种内存数据淘汰策略&#xff0c;使用常见是当内存不足时&#xff0c;需要淘汰最近最少使用的数据。LRU常用…

Mac 安装 brew brew cask 遇到的问题以及解决办法

安装Homebrew和Homebrew Cask是在Mac上管理软件包的常用方法。虽然大多数情况下安装这两个工具是比较简单的&#xff0c;但有时候也可能遇到一些问题。下面是一些常见的问题以及解决办法&#xff1a; 问题1&#xff1a;无法安装Homebrew 解决办法&#xff1a; 1.确保你的Mac已连…

4月9日学习记录

[GXYCTF 2019]禁止套娃 涉及知识点&#xff1a;git泄露&#xff0c;无参数RCE 打开环境&#xff0c;源码什么的都没有&#xff0c;扫描后台看看 扫描发现存在git泄露 用githack下载查看得到一串源码 <?php include "flag.php"; echo "flag在哪里呢&#…

REST API实战演练之JavaScript使用Rest API

咱们前面讲了一下如何创建REST API 假期别闲着&#xff1a;REST API实战演练之创建Rest API-CSDN博客 又讲了java客户端如何使用REST API 假期别闲着&#xff1a;REST API实战演练之客户端使用Rest API-CSDN博客 接下来咱们看看JavaScript怎么使用REST API。 一、新建一个…

swiftui macOS实现加载本地html文件

import SwiftUI import WebKitstruct ContentView: View {var body: some View {VStack {Text("测试")HTMLView(htmlFileName: "localfile") // 假设你的本地 HTML 文件名为 index.html.frame(minWidth: 100, minHeight: 100) // 设置 HTMLView 的最小尺寸…

权威报道 | 百分点科技:《突发事件应急预案管理办法》解读

近日&#xff0c;百分点科技CTO刘译璟作为唯一企业界代表&#xff0c;接受应急领域权威期刊——《中国应急管理》杂志邀请&#xff0c;与中国安全生产科学研究院、中央党校、中国政法大学等单位的专家一起&#xff0c;就《突发事件应急预案管理办法》&#xff08;以下简称《办法…

CLI的使用与IOS基本命令

1、实验目的 通过本实验可以掌握&#xff1a; CLI的各种工作模式个CLI各种编辑命令“?” 和【Tab】键使用方法IOS基本命令网络设备访问限制查看设备的相关信息 2、实验拓扑 CLI的使用与IOS基本命令使用拓扑如下图所示。 3、实验步骤 &#xff08;1&#xff09;CLI模式的切…

Vue3 + Vite 构建组件库发布到 npm

你有构建完组件库后&#xff0c;因为不知道如何发布到 npm 的烦恼吗&#xff1f;本教程手把手教你用 Vite 构建组件库发布到 npm 搭建项目 这里我们使用 Vite 初始化项目&#xff0c;执行命令&#xff1a; pnpm create vite my-vue-app --template vue这里以我的项目 vue3-xm…

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之七 简单指定视频某片段快放效果

Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之七 简单指定视频某片段快放效果 目录 Python 基于 OpenCV 视觉图像处理实战 之 OpenCV 简单视频处理实战案例 之七 简单指定视频某片段快放效果 一、简单介绍 二、简单指定视频某片段快放效果实现原理…

Docker内更新Jenkins详细讲解

很多小伙伴在Docker中使用Jenkins时更新遇到困难&#xff0c;本次结合自己的实际经验&#xff0c;详细讲解。根据官网Jenkins了解以下内容&#xff1a; 一、Jenkins 是什么? Jenkins是一款开源 CI&CD 软件&#xff0c;用于自动化各种任务&#xff0c;包括构建、测…

python数据分析和可视化【4】星巴克数据分析

有一组关于全球星巴克门店的统计数据directory.csv&#xff0c;分析了在不同国家和地区以及中国不同城市的星巴克门店的数量。 要求&#xff1a; &#xff08;1&#xff09;查看星巴克旗下有哪些品牌。如果我们只关心星巴克咖啡门店&#xff0c;则只需获取星巴克中Brand的数据集…

多层磁介质让HDD容量翻倍,可超过120TB

近些年&#xff0c;尽管HDD市场收到SSD重创&#xff0c;市场占比遭遇滑坡&#xff0c;但SSD在企业存储市场的侵蚀并未显著增加&#xff0c;SSD容量出货占企业存储&#xff08;HDDSSD&#xff09;总量的比例保持在31-32%左右。然而&#xff0c;考虑到AI优化加速计算对全闪存架构…

XUbuntu22.04之Typora添加水印并输出pdf文件(二百二十七)

简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀 优质视频课程:AAOS车载系统+AOSP…

科技动态人工智能应用太空探索生物科技

根据最新的科技资讯&#xff0c;以下是一些值得关注的科技动态&#xff1a; 人工智能领域 智能体热潮 &#xff1a;随着大模型的研发热潮&#xff0c;AI智能体的发展迅速&#xff0c;它们被用作认知核心&#xff0c;具备强大的学习和迁移能力。智能体的架构和交互方式也在不断进…

【保姆级讲解SQL Server的详细使用教程】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

前端入门:极简登录网页的制作(未使用JavaScript制作互动逻辑)

必备工具&#xff1a;vscode Visual Studio Code - Code Editing. Redefined 目录 前言 准备 HTML源文件的编写&#xff08;构建&#xff09; head部分 body部分 网页背景设置 网页主体构建 CSS源文件的编写&#xff08;设计&#xff09; 结果展示 前言 博主稍稍自…

【C++ 学习】 priority_queue 优先队列的学习!!

1 queue****的介绍** 队列是一种容器适配器&#xff0c;专门用于在FIFO上下文(先进先出)中操作&#xff0c;其中从容器一端插入元素&#xff0c;另一端提取元素。 队列作为容器适配器实现&#xff0c;容器适配器即将特定容器类封装作为其底层容器类&#xff0c;queue提供一组特…

Windows下编译boost库

官网&#xff1a;https://www.boost.org/ 使用git bash运行bootstrap.sh 运行b2.exe,会生成bin.v2文件夹 Cmake引入

jdk和Eclipse软件安装与配置(保姆级别教程)

目录 1、jdk的下载、安装、配置 1.1 jdk安装包的的下载地址&#xff1a;Java Archive | Oracle &#xff0c;点击进入&#xff0c;然后找到你想要的版本下载&#xff0c;如下图&#xff1a; 2.1 开始下载&#xff0c;如下图&#xff1a; 3.1 登入Oracle账号就可以立即下载了…

Java基于微信小程序的日语学习小程序,附源码

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…