c++/java/c# 幾種編程語言的指針、引用比較


前一段時間,我在 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM