目錄:
【C#小知識】C#中一些易混淆概念總結--------資料類型儲存位置,方法調用,out和ref參數的使用
繼上篇對一些C#概念問題進行細節的剖析以後,收穫頗多。以前,讀書的時候,一句話一掠而過,但是現在再去重讀的時候,每句話發現都包含大量的資訊。這一篇繼續總結自己的學習筆記,給大家深度的剖析一些概念性問題,有助於大家對C#的理解。
--------------------------------------------------分割線---------------------------------------------
一,建構函式
我們先建立一個類,如下面的代碼:
class Program { static void Main(string[] args) { } } //建立一個Person類 class Person { }
然後產生代碼。
我們使用.NET Reflector反編譯該程式集。會發現該類一被編譯,CLR會自動的為該類建立一個預設的建構函式。如:
所以在建立該對象的時候,會預設的為該類產生一個無參數的空方法體的建構函式。如果我們不顯式的寫明建構函式,CLR會為我們調用預設的建構函式。
class Person { //聲明有實現的建構函式 public Person() { Console.WriteLine("我是超人!"); } }
再次反編譯該程式集,會發現添加的建構函式覆蓋了C#編譯器預設為該類產生的建構函式,如:
所以,當程式員手動添加了任意類型的建構函式,C#編譯器就不會為該類添加預設的建構函式。
建構函式的特點:
①存取修飾詞一般是Public②沒有返回值,方法名與類名稱一致;
二,This關鍵字的作用
①this關鍵字代表當前對象,當前運行在記憶體中的那一個對象。我們添加如下的代碼:
private int nAge; public int NAge { get { return nAge; } set { nAge = value; } } //聲明有實現的建構函式 public Person() { this.NAge = 100; Console.WriteLine("我是超人!"); }
這時候我們反編譯該程式集,會看到如下結果:
可以看到this關鍵字代替的就是當前的Person對象。
②this關鍵字後面跟“:”符號,可以調用其它的建構函式
我們再添加如下的代碼:
#region 對象的建構函式 //聲明有實現的建構函式 public Person() { this.NAge = 100; Console.WriteLine("我是超人!"); } public Person(int nAge) { Console.WriteLine("超人的年齡{0}", nAge); } //使用this關鍵字調用了第二個一個參數的建構函式 public Person(int nAge, string strName) : this(1) { Console.WriteLine("我是叫{0}的超人,年齡{1}", strName, nAge); } #endregion
我們建立該對象看看是否調用成功。在Main函數中添加如下代碼:
Person p = new Person(10,"強子");
我們運行代碼,看到的列印結果如下:
由結果我們可以分析出,當含有兩個預設參數的對象建立的時候應該先調用了一個參數的建構函式對對象進行初始化,然後有調用了含有兩個參數的建構函式對對象進行初始化。
那麼到底是不是這個樣子呢?看下邊的調試過程:
通過上面的調試過程我們會發現,當建構函式使用this關鍵字調用其它的建構函式時,首先調用的是該調用的建構函式,在調用被調用的建構函式,先執行被調用的建構函式,在執行直接調用的建構函式。
為什麼要這個順序執行?因為我們預設的傳值是10,我們需要列印的超人的年齡是“10”,如果先執行直接調用的建構函式,就會被被調用建構函式覆蓋。
三,部分類
在同一命名空間下可以使用partial關鍵字聲明相同名稱的類(同一命名空間下預設不允許出現相同的類名稱),叫做部分類或者夥伴類。
如,當在同一命名空間下聲明相同名稱的類,編譯器報錯:
當我們使用Partial關鍵字時,可以順利編譯通過,如:
分別添加如下的代碼:
partial class Person { private string strAddress; public string StrAddress { get { return strAddress; } set { strAddress = value; } } private string strNumber; public string StrNumber { get { return strNumber; } set { strNumber = value; } } public void Run() { } } partial class Person { #region 對象屬性 private int nAge; public int NAge { get { return nAge; } set { nAge = value; } } private string strName; public string StrName { get { return strName; } set { strName = value; } } #endregion #region 對象的建構函式 //聲明有實現的建構函式 public Person() { this.NAge = 100; Console.WriteLine("我是超人!"); } public Person(int nAge) { Console.WriteLine("超人的年齡{0}", nAge); } public Person(int nAge, string strName) : this(1) { Console.WriteLine("我是叫{0}的超人,年齡{1}", strName, nAge); } #endregion public void Sing() { } }
我們再次反編譯該程式集,會發現如下的結果:
我們會發現使用Partial關鍵字的兩個同名類,被編譯成了同一個類。
所以部分類的特點:
①必須在同一個命名空間下的使用Partial關鍵字的同名類
②部分類其實就是一個類,C#編譯器會把它們編譯成一個類
③在一個夥伴類中定義的變數可以在另一個夥伴類中訪問(因為他們就是一個類)。
四,Const關鍵字和Readonly關鍵字的區別
1)const關鍵字
在Main函數中添加如下的代碼:
const string strName = "強子"; Console.WriteLine("我的名字叫{0}",strName);
編譯過後,我反編譯該程式集發現如下結果:
發現定義的常量並沒有出現在反編譯的代碼中,而且使用Const常量的地方被常量代替了。
2)readonly關鍵字
添加如下代碼:
class cat { readonly string reOnlyName = "強子"; public cat() { Console.WriteLine(reOnlyName); } }
產生後反編譯該程式集發現,如下結果:
我們發現被readonly修飾的變數並沒有被賦值,這是什麼回事呢?我們點擊cat類的建構函式時,看到如下結果:
我們發現被readonly修飾的變數是在被調用的時候賦值的。
那麼被readonly修飾的變數的是就是不可變的嗎?當然不是,由反編譯的結果我們知道,readonly修飾的變數是在被調用的時候在建構函式中被賦值的,那麼我們可以在建構函式中修改readonly的預設值
添加如下代碼:
class cat { readonly string reOnlyName = "強子"; public cat() { this.reOnlyName = "子強"; Console.WriteLine(reOnlyName); } }
在Main()函數中添加如下的代碼:
cat ct = new cat();
運行結果如下:
說明我們成功在建構函式中修改了readonly變數的值。
readonly和const的區別:
const常量在聲明的時候就必須賦初始值,這樣聲明變數可以提高程式的運行效率。而readonly變數聲明時可以不賦初始值,但一定要早建構函式中賦初始值。
也就是說,const變數在編譯的時候就要確定常量的值,而readonly是在啟動並執行時候確定該變數的值的。
五,解析枚舉
枚舉的層級和類的層級一樣,可以自訂資料類型,可以在枚舉名稱後使用“:”來指明枚舉類型。看如下代碼:
//定義一個方向的枚舉類型,枚舉成員使用","分割 enum Direction:string { east, west, south, north }
編譯會報錯,錯誤資訊如下:
由此我們可以知道枚舉的資料類型是值類型。
因為枚舉是資料類型,所以可以直接聲明訪問,如下代碼:
class Program { static void Main(string[] args) { //枚舉是資料類型可以直接聲明 Direction dr = Direction.east; Console.WriteLine(dr); Console.ReadKey(); } } //定義一個方向的枚舉類型,枚舉成員使用","分割 enum Direction { east, west, south, north }
也可以這樣訪問枚舉類型
class Program { static void Main(string[] args) { //枚舉是資料類型可以直接聲明 // Direction dr = Direction.east; Person p=new Person(); //直接調用枚舉變數 p.dir = Direction.east; Console.WriteLine(p.dir); Console.ReadKey(); } } class Person { private string strName; //直接聲明枚舉變數 public Direction dir; }
每一個枚舉成員都對應了一個整型的數值,這個數值預設從0開始遞增,可以通過強制轉換擷取該枚舉所代表的值。可以通過如下的代碼訪問:
Direction dr = Direction.east; int i = (int)dr;
我們還可以手動為每一個枚舉成員賦值,代表的是整型數值,賦值後該枚舉成員所代表的值就是所賦的值。如下代碼:
enum Direction { east=1, west=0, south=2, north=3 }
將字串轉換成枚舉
string strDir = "east"; //將字串轉換成枚舉類型 Direction d1=(Direction)Enum.Parse(typeof(Direction),strDir); //轉換的時候忽略大小寫 Direction d2 = (Direction)Enum.Parse(typeof(Direction), strDir,true);
--------------------------------分割線----------------------------------------
最後我們再來探究一個null 指標異常的問題
首先我們先聲明一個Dog類:
class Dog { private int nAge; public int NAge { get { return nAge; } set { nAge = value; } } private string strName; public string StrName { get { return strName; } set { strName = value; } } }
在Main()函數中我們這樣調用
Dog d = null; d.StrName = "WangWang";
結果會報錯,如
我們已經為屬性,封裝欄位了,但是為什麼沒有辦法給欄位賦值呢?我們就來探究一下這個問題。
當我們執行個體化Dog對象,即
Dog d = new Dog();
.NET Framwork做了什麼工作呢?如:
那為什麼會報錯呢,原因如:
-----------------------------------------------分割線-----------------------------------------------------------------
這次分享到這裡就結束了。其實蠻享受寫這個過程的。因為在初次的學的時候理解了,如果再寫成部落格就又加深了印象,最後希望大家都能養成了良好的學習習慣。
畢業實習交流群:221376964。你也可以關注我的新浪微博進行交流。