類和柏拉圖的理念世界
我們知道面向對象編程中到處是一個個的類,但類只是個概念性的東西,不是個實體,不占內存,你沒實例化之前也不能用它.只有把類實例化成一個對象后,它才是一個真正存在的實體.占有內存,能被我們使用.類就有點像柏拉圖所說的理念世界一樣,柏拉圖認為存在着兩個世界,一個是我們生活於其中的現實世界,與之對立的是理念世界,理念世界有些啥東東呢?那是個永恆不變的世界,由一堆堆的理念組成,比如人,馬,鳥....理念的人只是個概念,不是任何實際的人.而我們現實世界是根據理念世界模擬出來實際的一個個的人,一匹匹的馬.有些人可能模擬的比較漂亮,於是就有帥哥美女嘛,有些人就是失敗品,於是啥恐龍青蛙的也數量眾多.至於模擬的動作誰做的呢?柏拉圖沒說.有人就認為就是上帝在干這活.
那我們實例化一個類時就有點像上帝干的活,從理念世界拿個空的理念來,然后用個啥構造函數實例化成一個個的對象.
C#類初始化
我們實例化類時一般是用個new關鍵字比如MyClass arwen = new MyClass().這和C++中的蠻類似.C++這可以這樣實例化類表示在heap中開辟一塊內存來保存一個對象.用完了之后自己去釋放內存.但也可以直接這樣MyClass weiwen; 表示是在stack中保存對象,但stack的分配和收回由系統控制. C#則只能用new在heap中開辟內存這一個選擇,而且heap中的內存由CRL去管理,用完了它給你去釋放,所以此處的heap叫托管堆.
實例化類時我們只是簡單的用了個new,但實際上后台還做了其他很多操作.具體做了些啥呢?按順序做了如下事情
1.初始化類中靜態變量
2.調用類中靜態構造函數
3.初始化類中非靜態變量
4.初始化父類中靜態變量
5.調用父類靜態構造函數
6.初始化父類非靜態變量
7.調用父類構造函數
8.調用自己的構造函數
當然如果沒有繼承某個父類就不用管父類的事了.如果父類還繼承了某個父類,那父類繼承重復上面的操作.舉個簡單的例子看下吧.假如有父類Father,子類Son
class Father
{
public static int age; //4.賦值為0
public string name; //6.賦值為null
public Father() //7.調用此構造函數
{
Console.WriteLine("I am a lazy father construtor,don't do anything.");
}
public Father(string myName)
{
Console.WriteLine("I am a diligent father constructor.");
name = myName;
}
static Father() //5.賦值age為110
{
age = 110;
Console.WriteLine("Father static construtor will never do anyting after setting age with a value 110.");
}
}
class Son : Father
{
public static int age; //1.初始化age為0
public string name; //3.初始化為null
public Son()
{
Console.WriteLine("I am a lazy son construtor,don't do anything.");
}
public Son(string myName) //8.調用此構造函數
{
Console.WriteLine("I am a diligent son construtor.");
name = myName;
}
static Son() //2.賦值age為11
{
age = 11;
Console.WriteLine("Son static construtor will never do anyting after setting age with a value 11.");
}
}
我們實例化類Son.
Son arwen = new Son("arwen");
輸出的結果為
Son static construtor will never do anyting after setting age with a value 11 //類靜態構造函數
Father static construtor will never do anyting after setting age with a value 110. //父類靜態構造函數
I am a lazy father construtor,don't do anything. //父類構造函數
I am a diligent son construtor //類構造函數
執行順序都是按那8步來的.不過里面有些要注意的地方.
1.)靜態構造的定義只能是static 加類名,不能有參數,不能有public等任何修飾符.在里面只能給靜態變量賦值,不能給一般字段賦值,靜態構造函數只在類第一次實例化時調用一次.以后再實例化類時不調用.它只能是被默認調用,不能顯示調用.除了類第一次實例化時會被調用,第一次使用類中的靜態變量前也會被調用.比如你不實例化類Son,而是直接調用
Console.WriteLine(Son.age); 打印結果為
Son static construtor will never do anyting after setting age with a value 11
11
另外雖然const類型的字段也默認是static的.但如果你調用const字段時靜態構造函數還是不會執行.
2.)如果類中有很多個構造函數,不管你用哪一個來實例化類,調用父類的構造函數時只會調用無參數的那個.所以如果你沒有無參的構造函數,而且有帶參的構造函數,而你又作為某個類的父類.那編譯時會出錯的.如果你啥構造函數都沒的話反而沒問題.因為如果你任何構造函數都沒有,系統會為你默認生成一個.但你如果自己寫了任何構造函數帶參的或不帶參的,系統就不為你默認生成了.
3.)如果想調用父類的有參構造函數必須指定.用關鍵字base.比如在Son中調用有參構造函數時還要同時調用父類的有參構造函數就像下面這樣
public Son(string myName) :base(myName)
{
Console.WriteLine("I am a diligent son construtor.");
name = myName;
}
這樣的話就不會調用父類的無參構造函數了.而有調用有參的那個.不過盡管你這會沒調用無參構造函數.但你也不能省了它.無參構造函數必須得寫好放那.
4.)我們知道如果子類中有和父類同名的函數的話就會隱藏父類的函數.其實有同名的字段也一樣會隱藏.像我舉的例子中就有.編譯時會有警告,會提醒你加個new關鍵字.
new public static int age;
new public string name;
改成上面這樣就不會有警告.不過看着挺怪的啊.實際上不加new效果也一樣的.默認是加了new,只不過顯式的再加下new好點吧.
C++類初始化
C++中沒有靜態構造函數了.調用構造函數的順序和方式跟C#一樣的.只有調用父類的構造函數時有一點點寫法上的不一樣.把C#中的base換成父類的類名.
另外C++的static變量成員和C#的用法是不一樣的.實例化類時也不會默認給初始化.必須自己在類外面去初始化.在C++中的成員變量除了const static類型的可以在聲明時同時賦值,其他的都不行.
class Father
{
public:
static int age;
string name;
Father()
{
cout<<"I am a lazy father constructor,don't do anything."<<endl;
}
Father(string myName)
{
cout<<"I am a diligent father constructor."<<endl;
name = myName;
}
};
class Son :public Father
{
public:
static int age;
string name;
Son()
{
cout<<"I am a lazy son constructor,don't do anything."<<endl;
}
Son(string myName):Father(myName)
{
cout<<"I am a diligent son constructor."<<endl;
name = myName;
}
};
int Son::age = 110; //只能在類外面這樣去初始化static變量.前面還得加個int,不過不用加static,也不能再加static.
這樣初始化完了你在其他地方就可以Son:age這樣去引用了,並且可以直接改變它的值.比如Son::age = 911;
C#與C++初始化對比
從上面我們可以看出,兩者構造函數的用法基本上差不多.只不過C++沒有靜態構造函數.另外調用父類有參構造函時不用base關鍵字而直接用父類名.
兩者的主要區別是在初始化類中成員上.
1.)C++初始化時只要負責根據類中的類型來分配多少內存,沒有對成員變量做任何初始化賦值.而且static變量所在占的內存不算在類里面.static變量是保存在靜態內存區.而且實例化類時實際還沒有給static變量分配內存.只有你在類外面初始化時才會分配內存.
2.)另外你如果在類中定義了一個普通public成員變量,你沒有給賦值時用cout打印會發現有一個值.不過那值不確定,而且可能是個蠻大的值.咋回事呢?
實際上我們申請來一塊內存,它不是空白的.里面有內容的.比如你用new申請一塊內存,然后delete,不是說把內存清空,里面啥都沒有了.實際上只是告訴系統這塊內存我不要了.然后其他誰申請到這塊內存,里面還有你的內容在.只有在它重新賦值后才能擦除掉你的內容.
沒初始化成員變量前就使用它自然很危險.所以一定要初始化,C++中這重要的工作就留給構造函數了啊,而C#由於默認給初始化,構造函數就沒顯得像C++那么重要.
補充
static變量在C#,C++中含義差不多.不過const變量就差遠了.C#中const變量默認是static.而C++中const變量不是static.它其實有點像C#中的readonly. const成員變量只能在構造函數中賦值.而且只能在初始化參數列表中賦值.假如類Son中有有成員變量const int no.則只能這樣賦值
Son() : no(120)
{
}