C# 类和继承(使用基类的引用)

使用基类的引用

派生类的实例由基类的实例和派生类新增的成员组成。派生类的引用指向整个类对象,包括
基类部分。

如果有一个派生类对象的引用,就可以获取该对象基类部分的引用(使用类型转换运算符把
该引用转换为基类类型)。类型转换运算符放置在对象引用的前面,由圆括号括起的要被转换成
的类名组成。类型转换将在第17章阐述。将派生类对象强制转换为基类对象的作用是产生的变
量只能访问基类的成员(在被覆写方法中除外,稍后会讨论)。

接下来的几节将阐述使用对象的基类部分的引用来访问对象。我们从观察下面两行代码开
始,它们声明了对象的引用。图8-6阐明了代码,并展示了不同变量所看到的对象部分。

  • 第一行声明并初始化了变量derived,它包含一个MyDerivedClass类型对象的引用。
  • 第二行声明了一个基类类型MyBaseClass的变量,并把derived中的引用转换为该类型,
    给出对象的基类部分的引用。
    • 基类部分的引用被存储在变量mybc中,在赋值运算符的左边。
    • 基类部分的引用“看不到"派生类对象的其余部分,因为它通过基类类型的引用“看”
      这个对象。
MyDerivedClass derived=new MyDerivedClass();      //创建一个对象
MyBaseClass mybc=(MyBaseClass) derived;           //转换引用

派生类的引用可以看到完整的MyDerivedClass对象,而mybc只能看到对象的
MyBaseClass部分

下面的代码展示了两个类的声明和使用。图8-7阐明了内存中的对象和引用。
Main创建了一个MyDerivedClass类型的对象,并把它的引用存储到变量derived中。Main
还创建了一个MyBaseClass类型的变量,并用它存储对象基类部分的引用。当对每个引用调用
print方法时,调用的是该引用所能看到的方法的实现,并产生不同的输出字符串。

class MyBaseClass
{public void Print(){Console.WriteLine("This is the base class.");}
}class MyDerivedClass:MyBaseClass
{public int var1;new public void Print(){Console.WriteLine("This is the derived class.");}
}class Program
{static void Main(){MyDerivedClass derived=new MyDerivedClass();MyBaseClass mybc=(MyBaseClass)derived;       //转换为基类derived.Print();                             //从派生类部分调用Printmybc.Print();                                //从基类部分调用Print//mybc.var1=5;                               //错误:基类引用无法访问派生类成员}
}

对派生类和基类的引用

虚方法和覆写方法

在上一节我们看到,当使用基类引用访问派生类对象时,得到的是基类的成员。虚方法可以
使基类的引用访问“升至“派生类内。
可以使用基类引用调用派生类的方法,只需满足下面的条件。

  • 派生类的方法和基类的方法有相同的签名和返回类型。
  • 基类的方法使用virtual标注。
  • 派生类的方法使用override标注。

例如,下面的代码展示了基类方法和派生类方法的virtual及override修饰符。

class MyBaseClass             //基类
{virtual public void Print()....
}class MyDerivedClass:MyBaseClass //派生类
{override void Print()}

图8-8阐明了这组virtual和override方法。注意和上一种情况(用new隐藏基类成员)相
比在行为上的区别。

  • 当使用基类引用(mybc)调用Print方法时,方法调用被传递到派生类并执行,因为:
    • 基类的方法被标记为virtual;
    • 在派生类中有匹配的override方法。
  • 图8-8阐明了这一点,显示了一个从virtual Print方法后面开始,并指向overridePrint
    方法的箭头。

虚方法和覆写方法

下面的代码和上一节中的相同,但这一次,方法上标注了virtual和override。产生的结果
和前一个示例有很大不同。在这个版本中,对基类方法的调用实际调用了子类中的方法。

class MyBaseClass
{virtual public void Print(){Console.WiteLine("This is the base class.");}
}class MyDerivedClass:MyBaseClass
{public override void Print(){Console.WriteLine("This is the derived class.");}
}class Program
{static void Main(){MyDerivedClass derived=new MyDerivedClass();MyBaseClass mybc=(MyBaseClass)derived; //强制转换成基类derived.Print();mybc.Print();}
}

其他关于virtual和override修饰符的重要信息如下。

  • 覆写和被覆写的方法必须有相同的可访问性。例如,这种情况是不可以的:被覆写的方
    法是private的,而覆写方法是public的。
  • 不能覆写static方法或非虚方法。
  • 方法、属性和索引器(前一章阐述过),以及另一种成员类型一一事件(将在后面阐述),
    都可以被声明为virtual和override。

覆写标记为override的方法

覆写方法可以在继承的任何层次出现。

  • 当使用对象基类部分的引用调用一个被覆写的方法时,方法的调用被沿派生层次上溯执
    行,一直到标记为override的方法的最高派生(most-derived)版本。
  • 如果在更高的派生级别有该方法的其他声明,但没有被标记为override,那么它们不会
    被调用。

例如,下面的代码展示了3个类,它们形成了一个继承层次:MyBaseClass、MyDerivedClass
和SecondDerived。所有这3个类都包含名为Print的方法,并带有相同的签名。在MyBaseClass
中,Print被标记为virtual。在MyDerivedClass中,它被标记为override。在类SecondDerived
中,可以使用override或new声明方法Print。让我们看一看在每种情况下将发生什么。

class MyBaseClass  //基类
{virtual public void Print(){COnsole.WriteLine("This is the base class.");}
}class MyDerivedClass:MyBaseClass   //派生类
{override void Print(){Console.WriteLine("This is the derived class.");}}class SecondDerived:MyDerivedClass   //最高派生类
{...//在后面给出
}

情况1:使用override声明Print

如果把SecondDerived的Print方法声明为override,那么它会覆写方法的两个低派生级别的
版本,如图8-9所示。如果一个基类的引用被用于调用Print,它会向上传递,一直到类secondDerived
中的实现。

执行被传递到多层覆写链的顶端
下面的代码实现了这种情况。注意方法Main的最后两行代码。

  • 两条语句中的第一条使用最高派生类SecondDerived的引用调用Print方法。这不是通过
    基类部分的引用的调用,所以它将会调用SecondDerived中实现的方法。
  • 而第二条语句使用基类MyBaseClass的引用调用Print方法。
class SecondDerived:MyDerivedClass
{override public void Print(){Console.WriteLine("This is the second derived class.");}
}class Program
{static void Main(){SecondDerived derived=new SecondDerived();//使用SecondDerivedMyBaseClass mybc=(MybaseClass)derived;    //使用MyBaseClassderived.Print();mybc.Print();}
}

结果是:无论Print是通过派生类调用还是通过基类调用的,都会调用最高派生类中的方法。
当通过基类调用时,调用沿着继承层次向上传递。这段代码产生以下输出:

This is the second derived class.
This is the second derived class.

2.情况2:使用new声明Print

相反,如果将SecondDerived中的Print方法声明为new,则结果如图8-10所示。Main和上
一种情况相同。

class SecondDerived:MyDerivedClass
{new public void Print(){Console.WriteLine("This is the second derived class.");}
}class Program
{static void Main(){SecondDerived derived=new SecondDerived();//使用SecondDerivedMyBaseClass mybc=(MyBaseClass)derived;    //使用MyBaseClassderived.Print();mybc.Print();}
}

结果是:当通过SecondDerived的引用调用方法Print时,SecondDenved中的方法被执行,
正如所期待的那样。然而,当通过MyBaseClass的引用调用Print方法时,方法调用只向上传递
了一级,到达类MyDerived,在那里它被执行。两种情况的唯一不同是SecondDerived中的方法
使用修饰符override还是修饰符new声明。
这段代码产生以下输出:

This is the second derived class.
This is the derived class.

隐藏覆写的方法

覆盖其他成员类型

在之前的几节中,我们已经学习了如何在方法上使用virtual/override。在属性、事件以及
索引器上的用法也是一样的。例如,下面的代码演示了名为MyProperty的只读属性,其中使用
了virtual/override。

class MyBaseClass
{private int _myInt=5;virtual public int MyProperty{get{return _myInt;}}
}class MyDerivedClass:MyBaseClass
{private int _myInt=10;override public int  MyProperty {get{return _myInt;}}
}class Program
{static void Main(){MyDerivedClass derived=new MyDerivedClass();MyBaseClass mybc=(MyBaseClass)derived;Console.WriteLine(derived.MyProperty);Console.WriteLine(mybc.MyProperty);}
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在腾讯云 OpenCloudOS 上安装 Docker 和 Docker Compose

从你提供的 /etc/os-release 文件内容来看,你的服务器运行的是 OpenCloudOS 9.2。这是一个基于 CentOS 和 RHEL 的开源操作系统,因此它属于 CentOS/RHEL 系列。 关键信息总结 操作系统名称:OpenCloudOS版本:9.2ID:op…

趋势直线指标

趋势直线副图和主图指标,旨在通过技术分析工具帮助交易者识别市场趋势和潜在的买卖点。 副图指标:基于KDJ指标的交易策略 1. RSV值计算: - RSV(未成熟随机值)反映了当前收盘价在过去一段时间内的相对位置。通过计算当前…

FEMFAT许可分析的数据可视化方法

随着企业对FEMFAT软件使用的增加,如何有效地管理和分析许可数据成为了关键。数据可视化作为一种强大的工具,能够帮助企业直观地理解FEMFAT许可的使用情况,从而做出更明智的决策。本文将介绍FEMFAT许可分析的数据可视化方法,并探讨…

AMBER软件介绍

AMBER软件介绍 AMBER(Assisted Model Building with Energy Refinement)是一套广泛应用于分子动力学(MD)模拟和生物分子结构分析的软件工具集,尤其在蛋白质、核酸、多糖等生物大分子的模拟中表现突出。以下是关于AMBE…

GoogLeNet网络模型

GoogLeNet网络模型 诞生背景 在2014年的ImageNet图像识别挑战赛中,一个GoogLeNet的网络架构大放异彩,与VGG不同的是,VGG用的是3*3的卷积,而GoogLeNet从1*1到7*7的卷积核都用,也就是使用不同大小的卷积核组合。 网络…

Free2AI:企业智能化转型的加速器

随着数字化与智能化的深度交融,企业的竞争舞台已悄然转变为数据处理能力和智能服务水平的竞技场。Free2AI以其三大核心功能——智能数据采集、多格式文档解析、智能FAQ构建,为企业铺设了一条从数据洞察到智能服务的全链路升级之路,成为推动企…

Vue 核心技术与实战day07

1. vuex概述 2. 构建 vuex [多组件数据共享] 环境 <template><div id"app"><h1>根组件- {{ title }}- {{ count }}</h1><input :value"count" input"handleInput" type"text"><Son1></Son1>…

【原神 × 插入排序】刷圣遗物也讲算法:圣遗物评分系统背后的排序逻辑你真的懂吗?

📘 改编自:王争《数据结构与算法之美》 🎮 游戏演绎:米哈游《原神》 🧠 核心关键词:插入排序、排序算法、评分系统、属性评价、强化圣遗物、冒泡排序对比 🧭 引言:原神刷本=刷排序? 玩《原神》的玩家每天日常是啥?体力用来刷圣遗物、精通头、暴击头、攻充沙………

quasar electron mode如何打包无边框桌面应用程序

预览 开源项目Tokei Kun 一款简洁的周年纪念app&#xff0c;现已发布APK&#xff08;安卓&#xff09;和 EXE&#xff08;Windows&#xff09; 项目仓库地址&#xff1a;Github Repo 应用下载链接&#xff1a;Github Releases Preparation for Electron quasar dev -m elect…

微信小程序真机调试时如何实现与本地开发环境服务器交互

最近在开发微信小程序项目,真机调试时需要在手机上运行小程序,为了实现本地开发服务器与手机小程序的交互,需要以下步骤 1.将手机连到和本地一样的局域网 2.Visual Studio中将IIS Express服务器的localhost端口地址修改为本机的IP自定义的端口: 1&#xff09;找到web api项目…

Scratch节日 | 拯救屈原 | 端午节

端午节快乐&#xff01; 这款特别为端午节打造的Scratch游戏 《拯救屈原》&#xff0c;将带你走进古代中国&#xff0c;感受历史与文化的魅力&#xff01; &#x1f3ee; 游戏介绍 扮演勇敢的探险者&#xff0c;穿越时空回到古代&#xff0c;解锁谜题&#xff0c;完成任务&…

PHP下实现RSA的加密,解密,加签和验签

前言&#xff1a; RSA下加密&#xff0c;解密&#xff0c;加签和验签是四种不同的操作&#xff0c;有时候会搞错&#xff0c;记录一下。 1.公钥加密&#xff0c;私钥解密 发送方通过公钥将原数据加密成一个sign参数&#xff0c;相当于就是信息的载体&#xff0c;接收方能通过si…

Win10秘笈:两种方式修改网卡物理地址(MAC)

Win10秘笈&#xff1a;两种方式修改网卡物理地址&#xff08;MAC&#xff09; 在修改之前&#xff0c;可以先确定一下要修改的网卡MAC地址&#xff0c;查询方法有很多种&#xff0c;比如&#xff1a; 1、在设置→网络和Internet→WLAN/以太网&#xff0c;如下图所示。 2、在控…

C++中IO文件输入输出知识详解和注意事项

以下内容将从文件流类体系、打开模式、文本与二进制 I/O、随机访问、错误处理、性能优化等方面&#xff0c;详解 C 中文件输入输出的使用要点&#xff0c;并配以示例。 一、文件流类体系 C 标准库提供三种文件流类型&#xff0c;均定义在 <fstream> 中&#xff1a; std…

Unity3D仿星露谷物语开发56之保存角色位置到文件

1、目标 游戏中通过Save Game保存角色位置&#xff0c;当重启游戏后&#xff0c;通过Load Game可以恢复角色的位置。 2、Player对象操作 &#xff08;1&#xff09;组件添加 给Hierarchy下的Player组件添加Generate GUID组件。 &#xff08;2&#xff09;修改SceneSave.cs脚…

TKernel模块--杂项

TKernel模块–杂项 1.DEFINE_HARRAY1 #define DEFINE_HARRAY1(HClassName, _Array1Type_) \ class HClassName : public _Array1Type_, public Standard_Transient { \public: …

c++ typeid运算符

typeid运算符能获取类型信息。获取到的是type_info对象。type_info类型如下&#xff1a; 可以看到&#xff0c;这个类删除了拷贝构造函数以及等号操作符。有一些成员函数&#xff1a;hash_code、before、name、raw_name, 还重载了和!运算符。 测试&#xff1a; void testTyp…

第304个Vulnhub靶场演练攻略:digital world.local:FALL

digital world.local&#xff1a;FALL Vulnhub 演练 FALL (digitalworld.local: FALL) 是 Donavan 为 Vulnhub 打造的一款中型机器。这款实验室非常适合经验丰富的 CTF 玩家&#xff0c;他们希望在这类环境中检验自己的技能。那么&#xff0c;让我们开始吧&#xff0c;看看如何…

【数据库】数据库恢复技术

数据库恢复技术 实现恢复的核心是使用冗余&#xff0c;也就是根据冗余数据重建不正确数据。 事务 事务是一个数据库操作序列&#xff0c;是一个不可分割的工作单位&#xff0c;是恢复和并发的基本单位。 在关系数据库中&#xff0c;一个事务是一条或多条SQL语句&#xff0c…

switch-case判断

switch-case判断 #include <stdio.h> int main() {int type;printf("请输入你的选择&#xff1a;\n");scanf("%d",&type);getchar();switch (type){case 1:printf("你好&#xff01;");break;case 2:printf("早上好&#xff01;…