前一段時間,我在 cnblogs 別人的博客中,談到:
java 中的引用/指針,與 c++/C# 中的引用/指針不是一個概念.
Java 引用,相當於 c++ 指針(fun3)。Java 引用可以賦值 null, 而 c++ 引用 (見 fun2) 不能賦值 null,c++ 指針可以賦值 null(fun3).
Java 中,無 c++ 引用(fun2)對應的語法。
結果引起不必要的質疑,特此,寫博客,對c++/java/c# 幾種編程語言的指針、引用,進行比較,期望引起更多的人,對此有所關注。
從語法上看,三種開發語言中,C++ 的指針、引用,最為復雜,因此,下面的舉例,都從 C++ 代碼開始,然后與 java/c# 的語法進行比較。
1) C++ 簡單類型變量,有直接變量定義、指針定義、引用定義。
int aa = 10;//c++ int &bb = aa;//c++ int *cc = &aa;//c++
上述三行代碼,最后三個變量指向同一個數據。相比較而言,java/c# 都只有變量定義,無引用定義、指針定義。補充:感謝 xiaotie 、飛浪 的提醒:C#中是有指針的,在unsafe狀態下,可以定義和使用指針。特更正。
2) C++ 函數調用參數,簡單類型變量,有直接變量定義、指針定義、引用定義,后兩個,在函數內部改變數據,退出函數,能看到改變后的數據。
void simple_by_val(int a, const int b) { a=15; //b=13; //error C2166: l-value specifies const object //a=NULL; //good //b=NULL; //error C2166: l-value specifies const object } void simple_by_ref(int &a, const int &b) { a=25; //b=23; //error C2166: l-value specifies const object //a=NULL; //good //b=NULL; //error C2166: l-value specifies const object } void simple_by_pointer(int *a, const int *b) { *a = 35; //*b = 33; //error C2166: l-value specifies const object a = NULL; //ok b = NULL; //ok }
java 沒有這么多名堂,只有直接變量定義。C# 略為復雜一點,有引用,有 out 參數。
static void M(int a, ref int b, out int c) { c = 13; }
相比較而言,C# 的函數參數( ref int b), 類似於C++的函數參數( int &a),都是調用函數前要賦初值,在函數內部改變數據,退出函數,能看到改變后的數據。
而 C# 的 (out int c),在 C++/Java 中,無對應的語法。這個可以調用函數前,不賦初值。在 C# 之前,也很少見到這種語法,只在一些數據庫的存儲過程、函數定義中,見過類似語法。估計是從數據庫編程語法中抄襲過來的語法。
特別注明:C# 的引用( ref int b),只是用在函數參數變量定義上,不能用在函數內部的局部變量中。C++ 中的引用( int &a),可以用在函數內部的局部變量中。
3) C++ 的類對象變量定義語法,較為復雜,可以定義在stack 上(不用 new),可以定義在 heap(用 new)。
CMyClass obj; //stack CMyClass *p2 = new CMyClass(); //heap
java/C# 中,沒有這么復雜,可以認為是上述兩種“綜合+簡化”了。
4) 在 java/C# 中,如下用法是錯誤的,會報空指針異常;但是在 C++ 里是合法的。
CMyClass obj;
obj.run();
在 C++ 中,
CMyClass obj;
以上一行代碼已經調用了構造函數,完成了變量初始化。而在 java/C# 中,這一行代碼相當於:
CMyClass obj = null;
5) C++ 中,stack 變量出了作用范圍,內存自動回收;heap 變量,需要手工 delete。
java/C# 中,變量是空閑時自動回收的(理論上的),不是變量出了作用范圍,就內存回收。
{ CMyClass obj; //stack obj.test(); }//此處, stack 變量自動被 delete ,內存自動回收 { CMyClass *p2 = new CMyClass(); //heap p2->test(); } //此處,超出變量 p2 的作用范圍,下面不能再用 p2 變量了,但是,內存並未釋放,有內存泄露。
6) 以下代碼在 C++ 中是正確的,在 java/C# 是錯誤的。在 java/C# 語法中,沒有定義變量加 * 的,也不能用 -> 來調用類的函數或類的成員變量,也不能用 delete。
CMyClass *p1 = null; CMyClass *p2 = new CMyClass(); p2->ab(); delete p2; p2 = null;
7) 以下代碼,在java/C# 語法中,是正確的,在 C++ 是錯誤的。C++ 中,這種賦值要用指針 (CMyClass *p1 = null;)。
CMyClass p1 = null; CMyClass p2 = new CMyClass();
8) 以下代碼,在 C++ 代碼中,會調用“拷貝構造函數”、"等於號重載函數"。這兩個函數,在 C++ 中,默認會由編譯器自動生成。
//C++
CMyClass obj; //調用構造函數 CMyClass obj2 = obj; //調用拷貝構造函數 obj2 = obj; //調用 = 操作符重載函數
以上代碼,大致相當於 java/C# 中的 克隆"clone"。但更隱蔽(初學者不知道調用了 C++ 構造函數、拷貝構造函數、= 操作符重載函數)、更復雜。java/C# 無操作符重載函數。
//C# CMyClass obj = new CMyClass(); CMyClass obj2 = (CMyClass)obj.Clone();
而在 C# 中,Clone 函數並不會自動生成。在 Java 中,可以調用 super.clone() ---- Java 基類 Object 默認有一個 clone 函數。
在 C++ 中,默認會由編譯器自動生成“拷貝構造函數”、"等於號重載函數",這一點,很多時候會造成問題,要特別注意。
在 C++ 中,函數返回值不要用 CMyClass ,這會造成不必要地調用“拷貝構造函數”、"等於號重載函數";也不要返回引用 CMyClass&, 對函數內局部變量的引用,退出函數后無法繼續使用。而要返回指針 CMyClass *(最好用智能指針包裝后的指針變量)。這一點很多初學者不明白。
但是 C++ 的 std:string 除外。std:string 的“拷貝構造函數”、"等於號重載函數"經過優化,拷貝后的變量,與拷貝之前的變量,內部使用相同的 char[] 數組,只有當一個 string 變量改變時,才會把 char[] 數組復制成兩份。std:string 的“拷貝構造函數” 沒有性能上損失,又比 string 指針減少了內存泄露,因此,對 std:string ,使用時盡量用 對象變量、對象引用、對象拷貝構造,避免使用 std:string 指針。
另,java/C# 的 String 變量不可改變(有其它類,比如 java StringBuilder類是可變的),C++ 的 string 變量可以改變。這個細微差異,很多人不明白。
9) C++ 引用語法,有一些是 Java/C# 程序員不知道的語法:
//C++ CMyClass &a1; //錯誤,C++ 引用變量定義的時候就要初始化;//Java/C# 對象變量,沒有要求變量定義的時候,就要初始化 CMyClass &a1 = NULL; //錯誤,C++ 引用變量不能賦值 null CMyClass &a1 = new CMyClass(); //錯誤,C++ 引用變量不能賦值給一個 new 對象,這種情況,要用 C++ 指針。 //以下C++ 代碼是正確的: CMyClass a; CMyClass &a1 = a; CMyClass *b =new CMyClass(); CMyClass &b1 = *b; //這種寫法不常用。
10) Sun 自稱 java 中消滅了 C++ 中萬惡的指針,自己的對象變量,都是引用。做個比較:
C++ 引用不能賦值 null, 不能賦值 new XXX();C++ 指針可以賦值 null, 可以賦值 new XXX()。
C++ 引用對象通常在 stack 中,而C++ 指針 new 出來的對象則在 heap 中。
java/C# 中的對象變量,可以賦值 null, 可以賦值 new XXX()。java/C# 中的對象變量在 heap 中。
因此,java/C# 中的對象變量,更像是 C++ 中的指針,而不是 C++ 中的引用。
11) C++ 中,指針變量是一個 long 型整數,可以亂指的:
CMyClass *obj = (CMyClass *) 99; //compile/run good, should not use
如果我知道一個內存地址,就可以定義一個C++指針變量,指向這個內存地址。C++ 的“引用”沒有這個功能。C#/Java 的對象變量更沒有這個功能。
“指針亂指” 是 C++ 指針功能強大、靈活的體現(PC 上最早出現播放視頻的時候,大概是 intel 486 CPU 時代,C++軟件通常都直接寫顯存,據說這樣速度更快),也是最容易出問題的地方。估計是因為這個原因,所以C#/Java 都去掉了這個功能。所謂“萬惡的C++指針”,多半,也是指的是“指針亂指”。
12) C++ 有野指針,即已經刪除對象,但指針還是指向刪除對象,還可以繼續操作,但運行結果不保證正確。
CMyClass *p = new CMyClass(); ...//給 p 指向的內存賦值 delete p; //這時 p 仍然指向之前的內存地址,該內存地址數據,一般情況下、短時間內,並沒有被清空或者覆蓋,仍然可以讀/寫。這就是“野指針”。 p->run(); //運行結果可能正確,可能不正確,沒有保證。 //此時指針 p 對應的內存,可能被下一個 new XXX() 代碼,用了這個內存,因此,理論上講,delete 之后的指針,不應再用來操作對象。 p= NULL; //將指針指向“空”,可以避免“野指針”問題。 p->run(); //這里會報運行時錯誤。也就是空指針異常。空指針異常在 java/c# 中都有。
C++ 中,delete 與將變量賦值 null , 理應放在一起,可以認為是一個“數據庫事務”一樣的,要么都成功、要么都失敗。其實,delete 關鍵字,是由 C++ 標准定義的,標准中,完全可以要求: delete 所在行的代碼,執行之后,把指針變量變成 null(C++ 標准的規范,很多都是規定編譯器做什么,因此可以加這個規定)。這樣可以避免野指針問題。可惜,C++ 標准,在這方面沒有考慮周全。
另,有人抱怨,面試做題,看不是是 C++ 還是 Java、C# , 期望通過看本文,可以幫助一二。
---------------------------------------
歡迎大家下載試用折桂單點登錄系統, http://zheguisoft.com