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

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.lryc.cn/news/2394927.html

相关文章:

  • 进程间通信(消息队列)
  • Linux gron 命令使用详解
  • Nginx--手写脚本压缩和切分日志(也适用于docker)
  • OpenCv高阶(十八)——dlib人脸检测与识别
  • 中山大学无人机具身导航新突破!FlightGPT:迈向通用性和可解释性的无人机视觉语言导航
  • WIN11+CUDA11.8+VS2019配置BundleFusion
  • WPF prism
  • 实时同步缓存,与阶段性同步缓存——补充理解《补充》
  • [Redis] Redis:高性能内存数据库与分布式架构设计
  • Mobaxterm解锁Docker
  • React 第四十九节 Router中useNavigation的具体使用详解及注意事项
  • 【JavaEE】Spring事务
  • Flink 状态管理深度解析:类型与后端的全面探索
  • Android15 userdebug版本不能remount
  • R包安装报错解决案例系列|R包使用及ARM架构解决data.table安装错误问题
  • k8s Headless Service
  • Linux上安装MongoDB
  • Redis最佳实践——安全与稳定性保障之访问控制详解
  • 【华为开发者空间 x DeepSeek】服务器运行Ollama并在本地调用
  • Halcon
  • STM32之IIC(重点)和OLED屏
  • 学习海康VisionMaster之表面缺陷滤波
  • 游戏引擎学习第314天:将精灵拆分成多个层
  • 【学习笔记】深度学习-梯度概念
  • 【数据结构】图的存储(邻接矩阵与邻接表)
  • tomcat yum安装
  • 【Elasticsearch】suggest_mode
  • 数据库只更新特定字段的两种方式(先读后写 vs. 动态组织 SQL)-golang SQLx 实现代码(动态组织 SQL)
  • 从翻译后修饰角度解析人工合成途径与底盘细胞的适配性-文献精读136
  • Cesium快速入门到精通系列教程一