這次主要分享的內(nèi)容是關(guān)于繼承的知識。
首先,我們先來看看繼承;
既然有繼承,就要有父類和子類,來看下面的一段代碼:
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()函數(shù)中實(shí)例化子類的對象,代碼如下:
static void Main(string[] args)
{
Student stu1 = new Student();
}
那么在這個(gè)過程中內(nèi)存中發(fā)生了些什么呢?
我們先來看misl的中間代碼,看看那能發(fā)現(xiàn)些什么
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/5.1.png" alt="" />
由此我們可以發(fā)現(xiàn)子類繼承了父類的所有成員包括Private和Protect,并為這些成員開辟了空間來存儲。
我們再來實(shí)例化我們的子類,然后訪問父類的字段和方法,會發(fā)現(xiàn),如下的現(xiàn)象
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/5.2.png" alt="" />
所以雖然子類為父類的所有成員在堆中都開辟了空間,但是父類的私有成員(Private)子類訪問不到,
而受保護(hù)的成員(protected)可以通過實(shí)例化對象訪問的到。
所以在內(nèi)存中的情況如下圖:
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/5.3.png" alt="" />
看下面的代碼,我們來探究一下在子類中this關(guān)鍵字和base關(guān)鍵字所訪問的類的成員有哪些,代碼如下:
class Student : Person
{
private string strClass;
private string strAddress;
public void Address(string cla, string adre)
{
//這里的this關(guān)鍵字調(diào)用了子類的成員和父類的非似有成員
this.strClass = "五";
this.strAddress = "北京";
this.strName = "子強(qiáng)";
//這里base關(guān)鍵字調(diào)用了是父類的非似有成員
base.strName = "強(qiáng)子";
Console.WriteLine("我是{0}年紀(jì),來自{1}", cla, adre);
}
public void Sing()
{
this.strClass = "";
Console.WriteLine("我可以唱歌!");
}
}
所以在子類中this關(guān)鍵字和base關(guān)鍵字的訪問范圍的示意圖如下:
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/5.4.png" alt="" />
二,關(guān)于子類對象的構(gòu)造函數(shù)和父類構(gòu)造函數(shù)的執(zhí)行順序
我們分別為父類和子類添加顯式的構(gòu)造函數(shù),代碼如下
class Person
{
private int nAge;
protected string strName;
double douHeight;
public string strEateType;
//父類的構(gòu)造函數(shù)
public Person()
{
Console.WriteLine("我是父類的構(gòu)造函數(shù)");
}
public void Hello()
{
Console.WriteLine("我可以說Hello!");
}
public void Run()
{
Console.WriteLine("我可以跑!");
}
}
class Student : Person
{
private string strClass;
private string strAddress;
//子類的構(gòu)造函數(shù)
public Student ()
{
Console.WriteLine("我是子類的構(gòu)造函數(shù)");
}
}
我們使用VS的單步調(diào)試,來看父類和子類顯式構(gòu)造函數(shù)的執(zhí)行順序,如下圖(動(dòng)態(tài)圖片,可以看到過程):
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/5.5.gif" alt="" />
很容易的可以發(fā)現(xiàn),當(dāng)創(chuàng)建子類對象的時(shí)候
①先調(diào)用了子類的構(gòu)造函數(shù)
②調(diào)用了父類的構(gòu)造函數(shù)
③執(zhí)行了父類的構(gòu)造函數(shù)
④執(zhí)行了子類的構(gòu)造函數(shù)
那么為什么會這樣呢?
我嘗試通過反編譯看源碼來解釋這個(gè)原因,但是反編譯的結(jié)果如下,
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/5.6.png" alt="" />
沒有發(fā)現(xiàn)有什么特別的地方可以解釋這個(gè)原因。
最后還是查閱微軟的MSDN官方文檔找到了答案(原文地址點(diǎn)擊這里)
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/5.7.png" alt="" />
根據(jù)微軟官方的代碼示例,那么下面的代碼的效果也是相同的
//子類的構(gòu)造函數(shù)
public Student ()
{
Console.WriteLine("我是子類的構(gòu)造函數(shù)");
}
//這里的代碼和上面的代碼效果是相同的
public Student()
:base()
{
Console.WriteLine("我是子類的構(gòu)造函數(shù)");
}
也就是說只要在子類顯式的聲明了無參的構(gòu)造函數(shù),在實(shí)例化子類的對象是,子類的無參構(gòu)造函數(shù)都會去調(diào)用父類無參的構(gòu)造函數(shù)。
那么,如果父類沒有這個(gè)無參的構(gòu)造函數(shù)則會報(bào)錯(cuò)。
如下面的代碼:
class Person
{
private int nAge;
protected string strName;
double douHeight;
public string strEateType;
//父類的構(gòu)造函數(shù)
//public Person()
//{
// Console.WriteLine("我是父類的構(gòu)造函數(shù)");
//}
//父類的有參數(shù)的構(gòu)造函數(shù),這里覆蓋了無參的構(gòu)造函數(shù)
public Person (string str)
{
Console.WriteLine("我是父類的構(gòu)造函數(shù){0}",str);
}
public void Hello()
{
Console.WriteLine("我可以說Hello!");
}
public void Run()
{
Console.WriteLine("我可以跑!");
}
}
class Student : Person
{
private string strClass;
private string strAddress;
//子類的無參構(gòu)造函數(shù)
public Student ()
{
Console.WriteLine("我是子類的構(gòu)造函數(shù)");
}
public Student(string strName)
{
Console.WriteLine("我的名字叫{0}",strName);
}
}
這時(shí)候編譯會報(bào)錯(cuò),
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/5.8.png" alt="" />
因?yàn)樵诟割愔杏袇?shù)的構(gòu)造函數(shù)覆蓋了無參數(shù)的構(gòu)造函數(shù),所以在子類的無參數(shù)的構(gòu)造函數(shù)沒辦法回調(diào)父類的無參數(shù)的構(gòu)造函數(shù)初始化父類的成員變量。所以報(bào)錯(cuò)。
那么在初始化子類的時(shí)候,為什么要調(diào)用父類的構(gòu)造函數(shù)呢?
在初始化子類之前需要通過構(gòu)造函數(shù)初始化父類的成員變量
父類的構(gòu)造函數(shù)先于子類的構(gòu)造函數(shù)執(zhí)行的意義是什么呢?
當(dāng)在父類的構(gòu)造函數(shù)中和子類的構(gòu)造函數(shù)中為父類的非私有成員變量賦不同默認(rèn)值。當(dāng)實(shí)例化子類,子類要調(diào)用構(gòu)造函數(shù)初始化成員變量,如果先執(zhí)行了子類的構(gòu)造函數(shù),再執(zhí)行父類的構(gòu)造函數(shù),父類成員字段的值會覆蓋子類成員字段的值。但是我們想得到的是子類的屬性值。所以為了解決數(shù)據(jù)沖突,父類的構(gòu)造函數(shù)要先于子類的構(gòu)造函數(shù)執(zhí)行。
如下面的代碼:
class Person
{
private int nAge;
private string strName;
double douHeight;
public string strEateType;
// 父類的構(gòu)造函數(shù)
public Person()
{
//再父類中對strEateType賦初始值
this.strEateType = "吃飯";
Console.WriteLine("我是父類的構(gòu)造函數(shù){0}", strEateType);
}
}
class Student : Person
{
private string strClass;
private string strAddress;
//子類的構(gòu)造函數(shù)
public Student()
{
//在子類中對strEateType賦初始值
this.strEateType = "吃面條";
Console.WriteLine("我是子類的構(gòu)造函數(shù){0}",strEateType);
}
}
這時(shí)候我們通過,聲明子類對象訪問strEateType的值,如下:
Student stu1 = new Student();
//stu1.
string str = stu1.strEateType.ToString();
Console.WriteLine(str);
Console.ReadKey();
這里肯定是要打印出子類的屬性strEateType的值,如果先執(zhí)行子類構(gòu)造函數(shù)對strEateType賦值,然后父類的構(gòu)造函數(shù)賦值覆蓋strEateType的初始值。那么打印出的將是父類成員字段的值。所以,父類的構(gòu)造函數(shù)先于子類的構(gòu)造函數(shù)執(zhí)行。
打印結(jié)果如下:
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/5.9.png" alt="" />
三,子類是否可以有和父類的同名方法
看下面的代碼,我們聲明一個(gè)父類Person:
class Person
{
private int nAge;
private string strName;
double douHeight;
public string strEateType;
public readonly string strrrr;
// 父類的構(gòu)造函數(shù)
public Person()
{
this.strEateType = "吃飯";
Console.WriteLine("我是父類的構(gòu)造函數(shù){0}", strEateType);
}
public Person(string str)
{
this.strName = str;
Console.WriteLine("我是父類的構(gòu)造函數(shù){0}", str);
}
public void Hello()
{
Console.WriteLine("我可以說地球人的Hello!");
}
public void Run()
{
Console.WriteLine("我可以跑!");
}
}
聲明一個(gè)子類繼承Person,代碼如下:
class Worker:Person
{
public void Hello()
{
Console.WriteLine("我是工人會說Hello!");
}
public new void Run()
{
Console.WriteLine("我是工人我會奔跑!");
}
}
然后實(shí)例化Worker對象,打印Hello方法,結(jié)果如下圖:
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/5.10.png" alt="" />
這是為什么呢?編譯器已經(jīng)告訴了我們,如下圖:
http://wiki.jikexueyuan.com/project/csharp-confusing-concepts-summary/images/5.11.png" alt="" />
**看出來是子類的方法隱藏了父類的方法。
既然子類可以定義和父類同名的方法,那么是否可以定同名的字段呢?答案是肯定的,而且會像同名方法一樣,子類同名字段會隱藏父類同名的字段。