【C#小知识】C#中一些易混淆概念总结(六)---------解析里氏替换原则,虚方法

简介:

目录:

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

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

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

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

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

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

这一系列的文章在园子里还是比较受欢迎的。有一些留言指出了其中理论性的错误,怎么写出来这些文章的,有没有培训过等等问题。

下面就一并的回答这些问题吧。
1)自己今年六月份毕业,现在在帝都实习。不过在学校已经做过一些C#开发了,现在也是做.NET开发工作。

2)文章中很多知识是自己以前在网上下载的视频教程,学习过程中所记的笔记。也就是在年前的时候,突然有一天发现自己的笔记本记了差不多块一本了,之前也没时间整理过,所以就想着把它们整理成博客文章,顺便温习一下这些笔记知识。

3)有园友问自己是不是在传智培训过。首先说我没有培训过,但是非常感谢传智公开的一些自学教程。因为自己也是这些视频的受益者,学到了很多知识,养成了一些好的学习习惯。

4)在整理笔记的过程中遇到了很多问题,其中自己参考了《C#本质论》,《CLR via C#》还有就是MSDN的官方文档。

3)不管怎样还是会遇到一些自己解决不掉或者弄不清楚的问题,这个过程使用了Google搜索并和请教了一些园友。

4)错误总是会存在。谢谢看我博客的读者你们的细心,指出了我博文中的错误。确定这些错误后,我都立即修改了自己的文章。

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

今天开始上班了。这几天研究学习了一下思维导图,感觉用它整理自己的知识非常的方便。所以,以后写博客完成一个知识点,都会用思维导图做一个总结。也能让大家对所要读的内容有一个整体的把握。

我用的思维导图软件是FreeMind(免费的,但是得装JDK),因为刚开始学习使用,很多操作技巧不是很熟练,做出来的导图估计也不是很好,希望大家见谅。

首先,里氏替换原则。

这是理解多态所必须掌握的内容。对于里氏替换原则维基百科给出的定义如下:

072225093508573.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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class  Program
     {
         static  void  Main( string [] args)
         {
             Person p =  new  Person();
             Person p1 =  new  Student();
             Console.ReadKey();
         }
     }
     class  Person
     {
     //父类的私有成员
     private  int  nAge;
         public  Person()
         {
             Console.WriteLine( "我是Person构造函数,我是一个人!" );
         }
         public  void  Say()
         {
             Console.WriteLine( "我是一个人!" );
         }
     }
     class  Student : Person
     {
         public  Student()
         {
             Console.WriteLine( "我是Student构造函数,我是一个学生!" );
         }
         public  void  SayStude()
         {
             Console.WriteLine( "我是一个学生!" );
         }
     }
     class  SeniorStudent : Student
     {
         public  SeniorStudent()
         {
             Console.WriteLine( "我是SeniorStudent构造函数,我是一个高中生!" );
         }
         public   void  SaySenior()
         {
             Console.WriteLine( "我是一个高中生!" );
         }
     }

我们运行打印出的结果是:

072240214751281.png

根据前面的构造函数的知识很容易解释这个结果。那么我们在Main()函数中添加如下的代码:

1
2
3
4
5
6
7
8
9
static  void  Main( string [] args)
         {
             Person p =  new  Person();
             p.Say();
  
             Person p1 =  new  Student();
             p1.Say();
             Console.ReadKey();
         }

在访问的过程中,可以发现p只可以访问父类的say

072249159884254.png

而p1也只可以访问父类的Say方法

072247273693436.png

其实在上面的代码中,就满足了里氏替换原则。子类的Student对象,替换了父类Person对象的位置。


那么它们在内存中发生了些什么呢?如下图:

072355391281450.png

由上可以知道,当一个父类的变量指向一个子类对象的时候只能通过这个父类变量调用父类成员,子类独有的成员无法调用。

同理我们可以推理出,子类的变量是不可以指向一个父类的对像的

080009598379294.png

但是当父类变量指向一个子类变量的时候,可以不可以把父类的变量转化成子类的对象呢?看下图

080020564982450.png

关于引用类型的两种转换方式:

由上面的代码我们已经知道了一种转换,就是在变量钱直接加需要转换的类型,如下代码:

Student s2 = (Student)p1;

那么第二种转换方式就是使用as关键字,如下代码:

//将指向子类对象的变量转化成子类类型
Student s2 = (Student)p1;            
//使用as关键字,转换失败返回一个null值
Student s3 = p1 as Student;

使用as关键字和第一种强制转换的区别就是,第一种如果转换失败会抛异常,第二种转换失败则返回一个null值。

思维导图总结如下:

080046083779396.png


二,虚方法

使用virtual关键字修饰的方法,叫做虚方法(一般都是在父类中)。

看下面的一段代码:


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
class  Person
     {
         private  int  nAge;
         public  Person()
         {
             Console.WriteLine( "我是Person构造函数,我是一个人!" );
         }
         //这里定义了一个虚方法
         public  virtual  void  Say()
         {
             Console.WriteLine( "我是一个人!" );
         }
     }
     class  Student : Person
     {
         //子类使用override关键字改写了父类的虚方法
         public  override  void  Say()
         {
             Console.WriteLine( "我是一个学生!" );
         }
         public  Student()
         {
             Console.WriteLine( "我是Student构造函数,我是一个学生!" );
         }
         public  void  SayStude()
         {
             Console.WriteLine( "我是一个学生!" );
         }
     }


紧接着在main()函数中添加如下的代码:

1
2
3
4
5
6
7
8
9
10
static  void  Main( string [] args)
         {
             Person p =  new  Person();
             p.Say();
             Person p1 =  new  Student();
             p1.Say();
             Student s =  new  Student();
             s.Say();
             Console.ReadKey();
         }

打印结果如下:


080113432039737.png


我们很明显的可以发现,第二个表达式满足里氏替换原则,p1.Say()执行的应该是父类的Say()方法,但是这里却执行了子类的Say()方法。

这就是子类使用override关键字的Say()方法覆盖了父类的用Virtual关键字修饰的Say()方法。

我们使用动态图片看一下调试过程,

①首先是没有使用任何关键字:

080123570655066.gif


由上可以看出直接跳入父类,执行了父类的Say()方法;


②再看使用virtual和override关键字的动态调试图片,如下

080126577856689.gif

可以看到直接到子类去执行override关键字修饰的Say()方法。


那么如果父类使用virtual关键字修饰,而子类没有重写该方法时会怎么样呢?如下面的代码:


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
class  Program
     {
         static  void  Main( string [] args)
         {
                
             Person p1 =  new  Student();
             p1.Say();
             Console.ReadKey();
         }
     }
     class  Person
     {
         private  int  nAge;
         public  Person()
         {
             Console.WriteLine( "我是Person构造函数,我是一个人!" );
         }
         //这里定义了一个虚方法
         public  virtual  void  Say()
         {
             Console.WriteLine( "我是一个人!" );
         }
     }
     class  Student : Person
     {
         //子类中没有出现override关键字修饰的方法
         public  void  SayStude()
         {
             Console.WriteLine( "我是一个学生!" );
         }
     }


执行结果如下:


080132502632769.png



所以,如果子类找不到override方法,则会回溯到该子类的父类去找是否有override方法,知道回溯到自身的虚方法,并执行。

虚方法知识总结的思维导图如下:

080048580514226.png




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




相关文章
|
2月前
|
开发框架 容器
SAP UI5 控件的 aggregation 的概念解析
SAP UI5 控件的 aggregation 的概念解析
36 0
|
29天前
|
Python
继承概念深度解析:代码视角下的科普之旅
继承概念深度解析:代码视角下的科普之旅
6 0
|
1月前
|
存储 编解码 算法
音视频编程ffmepg中的关键术语与概念:深度解析与实践(二)
音视频编程ffmepg中的关键术语与概念:深度解析与实践
59 0
|
1月前
|
存储 编解码 网络协议
音视频编程ffmepg中的关键术语与概念:深度解析与实践(一)
音视频编程ffmepg中的关键术语与概念:深度解析与实践
72 0
|
1月前
|
数据库 数据库管理
构建信息蓝图:概念模型与E-R图的技术解析
构建信息蓝图:概念模型与E-R图的技术解析
28 0
|
2月前
|
并行计算 API 计算机视觉
Python多线程与多进程:概念、区别及应用场景解析
Python多线程与多进程:概念、区别及应用场景解析
|
3月前
|
监控 安全 数据安全/隐私保护
SAP RBAM (Role-Based Authorization Management) 概念解析
SAP RBAM (Role-Based Authorization Management) 概念解析
32 0
|
6天前
yolo-world 源码解析(六)(2)
yolo-world 源码解析(六)
16 0
|
6天前
yolo-world 源码解析(六)(1)
yolo-world 源码解析(六)
8 0
|
6天前
yolo-world 源码解析(五)(4)
yolo-world 源码解析(五)
16 0

推荐镜像

更多