博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
【C#小知识】C#中一些易混淆概念总结(五)---------深入解析C#继承
阅读量:6327 次
发布时间:2019-06-22

本文共 5204 字,大约阅读时间需要 17 分钟。

目录:

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

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

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

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

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,如需转载请自行联系原作者

你可能感兴趣的文章
第174天:面向对象——公有属性、私有属性和静态属性
查看>>
读书笔记 effective c++ Item 26 尽量推迟变量的定义
查看>>
分享手淘过年项目中采用到的前端技术
查看>>
Git基本命令 -- 别名 + 忽略 + 推送
查看>>
MCMC(二)马尔科夫链
查看>>
设计模式是什么鬼
查看>>
【vuejs深入三】vue源码解析之二 htmlParse解析器的实现
查看>>
kubernetes ConfigMap
查看>>
Dubbo 同步、异步调用的几种方式
查看>>
Flask入门 表单Flask-wtf form原生与Bootstrap渲染(七)
查看>>
Android统计图表MPAndroidChart
查看>>
阿里云容器Kubernetes监控(二) - 使用Grafana展现Pod监控数据
查看>>
区块链应用 | 不知道什么时候起,满世界都在谈区块链的事情
查看>>
小程序爆红 专家:对简单APP是巨大打击
查看>>
FarBox--另类有趣的网站服务【转】
查看>>
在非纯色背景上,叠加背景透明的BUTTON和STATIC_TEXT控件
查看>>
Distributed2:Linked Server Login 添加和删除
查看>>
海量数据处理相关面试问题
查看>>
美国诚实签经验——医院预约单和医院资料,医生预约收据和报价表,赴美预算,赴美行程,保险是加分项,工作证明(勾出职位和薪酬),附上名片或者工卡,全家福照片...
查看>>
Python-time
查看>>