【C#小知识】C#中一些易混淆概念总结(五)---------深入解析C#继承

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介:

目录:

【C#小知识】C#中一些易混淆概念总结--------数据类型存储位置,方法调用,out和ref参数的使用

【C#小知识】C#中一些易混淆概念总结(二)--------构造函数,this关键字,部分类,枚举

【C#小知识】C#中一些易混淆概念总结(三)--------结构,GC回收,静态成员,静态类

【C#小知识】C#中一些易混淆概念总结(四)---------解析Console.WriteLine()

----------------------------------分割线--------------------------------------

这次主要分享的内容是关于继承的知识。

首先,我们先来看看继承;

既然有继承,就要有父类和子类,来看下面的一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class  Person
     {
         private  int  nAge;
         protected  string  strName;
         double  douHeight;
         public  string  strEateType;
         public  void  Hello()
         {
             Console.WriteLine( "我可以说Hello!" );
         }
         public  void  Run()
         {
             Console.WriteLine( "我可以跑!" );
         }
     }
     class  Student : Person
     {
     }

然后我在Main()函数中实例化子类的对象,代码如下:

staticvoid Main(string[] args)        

{        

   Student stu1 new Student();    

  }

那么在这个过程中内存中发生了些什么呢?

我们先来看misl的中间代码,看看那能发现些什么


061458426044696.png

由此我们可以发现子类继承了父类的所有成员包括Private和Protect,并为这些成员开辟了空间来存储。

我们再来实例化我们的子类,然后访问父类的字段和方法,会发现,如下的现象

061505509659962.png

所以虽然子类为父类的所有成员在堆中都开辟了空间,但是父类的私有成员(Private)子类访问不到,

而受保护的成员(protected)可以通过实例化对象访问的到。


所以在内存中的情况如下图:

061525055993918.png

看下面的代码,我们来探究一下在子类中this关键字和base关键字所访问的类的成员有哪些,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class  Student : Person
     {
         private  string  strClass;
         private  string  strAddress;
         public  void  Address( string  cla,  string  adre)
         {
             //这里的this关键字调用了子类的成员和父类的非似有成员
             this .strClass =  "五" ;
             this .strAddress =  "北京" ;
             this .strName =  "子强" ;
             //这里base关键字调用了是父类的非似有成员
             base .strName =  "强子" ;
             Console.WriteLine( "我是{0}年纪,来自{1}" , cla, adre);
         }
         public  void  Sing()
         {
             this .strClass =  "" ;
             Console.WriteLine( "我可以唱歌!" );
         }
     }

所以在子类中this关键字和base关键字的访问范围的示意图如下:

061715362894801.png

二,关于子类对象的构造函数和父类构造函数的执行顺序

我们分别为父类和子类添加显式的构造函数,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class  Person
     {
         private  int  nAge;
         protected  string  strName;
         double  douHeight;
         public  string  strEateType;
         //父类的构造函数
         public  Person()
         {
             Console.WriteLine( "我是父类的构造函数" );
         }
         public  void  Hello()
         {
             Console.WriteLine( "我可以说Hello!" );
         }
         public  void  Run()
         {
             Console.WriteLine( "我可以跑!" );
         }
     }
     class  Student : Person
     {
         private  string  strClass;
         private  string  strAddress;
         //子类的构造函数
         public  Student ()
         {
             Console.WriteLine( "我是子类的构造函数" );
         }
     }

我们使用VS的单步调试,来看父类和子类显式构造函数的执行顺序,如下图(动态图片,可以看到过程):

061724588334412.gif

很容易的可以发现,当创建子类对象的时候

①先调用了子类的构造函数

②调用了父类的构造函数

③执行了父类的构造函数

④执行了子类的构造函数


那么为什么会这样呢?

我尝试通过反编译看源码来解释这个原因,但是反编译的结果如下,

061801203361346.png

没有发现有什么特别的地方可以解释这个原因。


最后还是查阅微软的MSDN官方文档找到了答案(原文地址点击这里

061801068235143.png

根据微软官方的代码示例,那么下面的代码的效果也是相同的

1
2
3
4
5
6
7
8
9
10
11
//子类的构造函数
         public  Student ()
         {
             Console.WriteLine( "我是子类的构造函数" );
         }
//这里的代码和上面的代码效果是相同的
         public  Student()
             : base ()
         {
             Console.WriteLine( "我是子类的构造函数" );
         }

也就是说只要在子类显式的声明了无参的构造函数,在实例化子类的对象是,子类的无参构造函数都会去调用父类无参的构造函数。

那么,如果父类没有这个无参的构造函数则会报错。

如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class  Person
     {
         private  int  nAge;
         protected  string  strName;
         double  douHeight;
         public  string  strEateType;
         //父类的构造函数
         //public Person()
         //{
         //    Console.WriteLine("我是父类的构造函数");
         //}
       //父类的有参数的构造函数,这里覆盖了无参的构造函数
         public  Person ( string  str)
         {
             Console.WriteLine( "我是父类的构造函数{0}" ,str);
         }
         public  void  Hello()
         {
             Console.WriteLine( "我可以说Hello!" );
         }
         public  void  Run()
         {
             Console.WriteLine( "我可以跑!" );
         }
     }
     class  Student : Person
     {
         private  string  strClass;
         private  string  strAddress;
         //子类的无参构造函数
         public  Student ()
         {
             Console.WriteLine( "我是子类的构造函数" );
         }
         public  Student( string  strName)
         {
             Console.WriteLine( "我的名字叫{0}" ,strName);
         }
     }


这时候编译会报错,

061816033476317.png

因为在父类中有参数的构造函数覆盖了无参数的构造函数,所以在子类的无参数的构造函数没办法回调父类的无参数的构造函数初始化父类的成员变量。所以报错。

那么在初始化子类的时候,为什么要调用父类的构造函数呢?

在初始化子类之前需要通过构造函数初始化父类的成员变量

父类的构造函数先于子类的构造函数执行的意义是什么呢?

当在父类的构造函数中和子类的构造函数中为父类的非私有成员变量赋不同默认值。当实例化子类,子类要调用构造函数初始化成员变量,如果先执行了子类的构造函数,再执行父类的构造函数,父类成员字段的值会覆盖子类成员字段的值。但是我们想得到的是子类的属性值。所以为了解决数据冲突,父类的构造函数要先于子类的构造函数执行。

如下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class  Person
     {
         private  int  nAge;
         private  string  strName;
         double  douHeight;
         public  string  strEateType;
        // 父类的构造函数
         public  Person()
         {
             //再父类中对strEateType赋初始值
             this .strEateType =  "吃饭" ;
             Console.WriteLine( "我是父类的构造函数{0}" , strEateType);
         }
     }
     class  Student : Person
     {
         private  string  strClass;
         private  string  strAddress;
         //子类的构造函数
         public  Student()
         {
             //在子类中对strEateType赋初始值
             this .strEateType =  "吃面条" ;
             Console.WriteLine( "我是子类的构造函数{0}" ,strEateType);
         }
     }

这时候我们通过,声明子类对象访问strEateType的值,如下:

1
2
3
4
5
Student stu1 =  new  Student();
             //stu1.
             string  str = stu1.strEateType.ToString();
             Console.WriteLine(str);
             Console.ReadKey();

这里肯定是要打印出子类的属性strEateType的值,如果先执行子类构造函数对strEateType赋值,然后父类的构造函数赋值覆盖strEateType的初始值。那么打印出的将是父类成员字段的值。所以,父类的构造函数先于子类的构造函数执行。

打印结果如下:

061910383266926.png


三,子类是否可以有和父类的同名方法

看下面的代码,我们声明一个父类Person:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class  Person
     {
         private  int  nAge;
         private  string  strName;
         double  douHeight;
         public  string  strEateType;
        public   readonly  string  strrrr;
         // 父类的构造函数
         public  Person()
         {
             this .strEateType =  "吃饭" ;
             Console.WriteLine( "我是父类的构造函数{0}" , strEateType);
         }
         public  Person( string  str)
         {
             this .strName = str;
             Console.WriteLine( "我是父类的构造函数{0}" , str);
         }
         public  void  Hello()
         {
             Console.WriteLine( "我可以说地球人的Hello!" );
         }
         public  void  Run()
         {
             Console.WriteLine( "我可以跑!" );
         }
     }


声明一个子类继承Person,代码如下:

1
2
3
4
5
6
7
8
9
10
11
class  Worker:Person
     {
         public  void   Hello()
         {
             Console.WriteLine( "我是工人会说Hello!" );
         }
         public  new  void   Run()
         {
             Console.WriteLine( "我是工人我会奔跑!" );
         }
     }


然后实例化Worker对象,打印Hello方法,结果如下图:

062124112658094.png


这是为什么呢?编译器已经告诉了我们,如下图:

062126425532539.png


看出来是子类的方法隐藏了父类的方法。

既然子类可以定义和父类同名的方法,那么是否可以定同名的字段呢?答案是肯定的,而且会像同名方法一样,子类同名字段会隐藏父类同名的字段










     本文转自yisuowushinian 51CTO博客,原文链接:http://blog.51cto.com/yisuowushinian/1356975,如需转载请自行联系原作者





相关文章
|
12天前
|
C# Windows
visual studio 2022 社区版 c# 环境搭建及安装使用【图文解析-小白版】
这篇文章提供了Visual Studio 2022社区版C#环境的搭建和安装使用指南,包括下载、安装步骤和创建C#窗体应用程序的详细图文解析。
visual studio 2022 社区版 c# 环境搭建及安装使用【图文解析-小白版】
|
19小时前
|
机器学习/深度学习 人工智能 自然语言处理
Transformer图解以及相关的概念解析
前言 transformer是目前NLP甚至是整个深度学习领域不能不提到的框架,同时大部分LLM也是使用其进行训练生成模型,所以transformer几乎是目前每一个机器人开发者或者人工智能开发者不能越过的一个框架。接下来本文将从顶层往下去一步步掀开transformer的面纱。 transformer概述 Transformer模型来自论文Attention Is All You Need。 在论文中最初是为了提高机器翻译的效率,它使用了Self-Attention机制和Position Encoding去替代RNN。后来大家发现Self-Attention的效果很好,并且在其它的地
18 9
|
20小时前
|
JSON 关系型数据库 API
ElasticSearch 的概念解析与使用方式(二)
ElasticSearch 的概念解析与使用方式(二)
8 1
|
20小时前
|
存储 搜索推荐 Java
ElasticSearch 的概念解析与使用方式(一)
ElasticSearch 的概念解析与使用方式(一)
6 1
|
16天前
|
网络协议 安全 Linux
网卡接口跃点数:概念与重要性解析
在计算机网络中,跃点数(Hop Count)是指数据包从源设备传输到目标设备时经过的路由器或网关数量,是衡量路径长度的关键指标。本文详细介绍了跃点数的概念、计算方法及其在网络管理中的重要性,包括性能评估、故障排除、网络优化及路由选择等方面的应用。通过使用traceroute或tracert命令,网络管理员可以轻松获取跃点数信息,并据此优化网络结构,提高数据传输效率和安全性。尽管跃点数是重要指标,但仍需与其他因素结合分析以全面评估网络性能。
|
20天前
|
安全 C#
C# 面向对象编程的三大支柱:封装、继承与多态
【9月更文挑战第17天】在C#中,面向对象编程的三大支柱——封装、继承与多态,对于编写安全、可维护、可复用的代码至关重要。封装通过访问修饰符和属性保护数据;继承允许子类继承父类的属性和方法,实现代码复用和多态;多态则提高了代码的灵活性和通用性。掌握这三大概念能显著提升C#编程能力,优化开发效率和代码质量。
|
1月前
|
机器学习/深度学习 人工智能 自然语言处理
Transformer图解以及相关的概念解析
前言 transformer是目前NLP甚至是整个深度学习领域不能不提到的框架,同时大部分LLM也是使用其进行训练生成模型,所以transformer几乎是目前每一个机器人开发者或者人工智能开发者不能越过的一个框架。接下来本文将从顶层往下去一步步掀开transformer的面纱。 transformer概述 Transformer模型来自论文Attention Is All You Need。 在论文中最初是为了提高机器翻译的效率,它使用了Self-Attention机制和Position Encoding去替代RNN。后来大家发现Self-Attention的效果很好,并且在其它的地
40 2
|
1月前
|
存储 C#
C# 一分钟浅谈:继承与多态性的实践
【9月更文挑战第2天】本文从基础入手,详细介绍了面向对象编程中继承与多态性的核心概念。通过 `Animal`、`Dog` 和 `Cat` 类的示例代码,展示了如何利用继承重用代码及多态性实现不同对象对同一方法的多样化响应,帮助读者更好地理解和应用这两个重要概念,提升面向对象编程能力。
34 3
|
1月前
|
C# 数据安全/隐私保护
C# 一分钟浅谈:类与对象的概念理解
【9月更文挑战第2天】本文从零开始详细介绍了C#中的类与对象概念。类作为一种自定义数据类型,定义了对象的属性和方法;对象则是类的实例,拥有独立的状态。通过具体代码示例,如定义 `Person` 类及其实例化过程,帮助读者更好地理解和应用这两个核心概念。此外,还总结了常见的问题及解决方法,为编写高质量的面向对象程序奠定基础。
17 2
|
2月前
|
前端开发 开发者 C#
深度解析 Uno Platform 中的 MVVM 模式:从理论到实践的全方位指南,助你轻松掌握通过 C# 与 XAML 构建高效可维护的跨平台应用秘籍
【8月更文挑战第31天】本文详细介绍如何在优秀的跨平台 UI 框架 Uno Platform 中实施 MVVM(Model-View-ViewModel)模式,通过一个简单的待办事项列表应用演示其实现过程。MVVM 模式有助于分离视图层与业务逻辑层,提升代码组织性、易测性和可维护性。Uno Platform 的数据绑定机制使视图与模型间的同步变得高效简便。文章通过构造 `TodoListViewModel` 类及其相关视图,展示了如何解耦视图与模型,实现动态数据绑定及命令处理,从而提高代码质量和开发效率。通过这一模式,开发者能更轻松地构建复杂的跨平台应用。
29 0

推荐镜像

更多