1. 程式人生 > 其它 >Java的引用到底是什麼?和C/C++的指標有什麼區別?

Java的引用到底是什麼?和C/C++的指標有什麼區別?

點進這篇文章的朋友們,如果對「指標」沒有概念,那麼請面壁思過。

你不是一個正統的程式設計師,你是野路子,是faker,在技術這條路上註定走不遠。

閒話少述,正文開始。

1、從操作符說起

要看「引用」和「指標」的區別,首先要看操作符。

  • 在c/c++中,指標相關的操作符有3個:**& -> ***
  • 在Java中,引用相關的操作符有1個:.

What,引用就一個操作符???那我們就來看下,操作符各有什麼作用

注:指標使用結構體來舉例,便於和引用的物件來比較

1.1、C/C++中指標操作符 & -> * 的作用

  • 定義一個結構體和變數

    typedef stuct {
        int sex;
        int age;
    } student_t;
    
    student_t stu1 = {1, 20};
    
  • 操作符**&,將資料的地址讀取到一個指標**:

    int* p_addr = &stu1; // 建立一個指標p_addr,p_addr儲存了stu1的地址
    
  • 操作符**->,讀/寫一個指標所指向結構體地址的成員資料**

    int age = p_addr->age;
    p_addr->age = 44;
    
  • 操作符*****,讀/寫一個指標中地址的資料:

    student_t stu2 = *p_addr; // 將p_addr地址的資料讀取到stu2
    *p_addr = {2, 8}; // 將資料寫入p_addr地址
    

注:c++中也有引用,但不在本文討論範圍內

1.2、Java中引用操作符 . 的使用

  • 定義一個類和物件

    public class Student{
        public Integer sex;
        public Integer age;
        
        Student(Integer s, Integer a) {
            sex = s;
            age = a;
        }
    }
    Student stu1 = new Student(1, 20); // 建立一個引用stu1,stu1中儲存的不是物件的資料,而是是物件的地址,也即引用
                                       // stu1存放在stack,物件存放在heap
    
  • 將資料的地址讀取到一個引用

    Student p_addr = stu1; // 建立一個引用p_addr,把stu1中儲存的物件的地址,賦給p_addr
    
  • 讀/寫一個引用所指向物件地址的成員資料

    Integer age = stu.age;
    stu.age = 44;
    

    注:Java只有引用,沒有指標,而引用弱化了地址和資料的概念,所以程式設計師們更要深刻理解引用的本質,寫出更健壯的程式碼。

如此看來,C/C++指標的操作符 * 能幹的活,Java的引用幹不了,也就是指標能直接對地址的資料進行讀/寫,引用則不能。

那咱就來看看,具體那些活是指標能幹,引用幹不了。。。

2、指標能幹,引用幹不了的活~

2.1、指標可以指向任意一個地址,引用只能指向一個物件

  • 指標可以給用操作符&給其一個數據的地址,也可以直接給其一個地址,甚至空地址

    student_t* p_addr = &stu1;
    student_t* p_addr = 0x12000;
    student_t* p_addr = NULL;
    
  • 指標可以對地址進行加減操作,從而修改相鄰地址的資料,比如修改一個數組

    int data[4] = {1,2,3,4};
    int* p_addr = data;
    *p_addr = 6;
    p_addr += 1;
    *p_addr = 7;
    p_addr += 1;
    *p_addr = 8;
    p_addr += 1;
    *p_addr = 9;
    // 此時陣列內資料為:{6,7,8,9}
    
  • 引用只能指向一個物件,不能直接給其一個地址,也不能空引用

    Student stu = new Student();
    Student stu = 0x12000; // 對不起,編譯不通過。。。
    

2.1.1、有什麼用?

在底層驅動開發時,暫存器的地址是固定的,

  • 想要修改暫存器的資料,需建立一個指標,把暫存器地址賦給指標,然後去修改暫存器。

    // 點亮一個LED
    int* p_led_addr = 0x1233; // LED暫存器地址是0x1233,將其賦給指標p_led_addr
    *p_led_addr = 1; // LED亮
    *p_led_addr = 0; // LED滅
    int state = *p_led_addr; // 讀取LED的亮滅狀態
    
  • 修改連續地址的多個暫存器

    // 點亮多個LED
    int* p_led_addr = 0x1233; // LED暫存器地址是0x1233,將其賦給指標p_led_addr
    *p_led_addr = 1; // LED亮
    *(p_led_addr+1) = 1; // LED2亮
    *(p_led_addr+2) = 1; // LED3亮
    

顯而易見,引用能不能幹???幹不了!

2.2、指標可以隨意修改所指向地址的資料

  • 指標大法

    student_t* p_addr = &stu1; // 建立Student型別的指標,指向一個stu1
    *p_addr = 24242; // 將24242寫入stu1的地址
    
  • 引用只能修改所指向物件的固定成員,或者通過所指向物件提供的固定方法來修改資料

  • 有什麼用?

    • 好像沒啥用。。。

3、指標的缺陷

3.1、野指標

  • 定義
    • 指標在建立時,未初始化,此時指向的地址是隨機的!此時指標讀寫,破壞程式執行!
    • 指標所指向地址的資料已經被釋放,此時指標讀寫,則破壞程式執行!
  • 原因
    • 指標可以指向任意一個地址;而引用必須指向一個確定的物件
    • 指標不能自動解除指向;而引用在指向的物件銷燬時,會自動解引用
  • 後果
    • 程式奔潰、不能按預期執行、程式碼漏洞

3.2、C語言強制型別轉換造成的記憶體誤修改

  • 定義
    • 將型別A的變數s,強制轉換成型別B,然後將其s的地址賦給指向型別B的指標p,對指標p讀寫
    • 此時型別B的資料結構可能並不相容型別A,導致對變數s的誤修改
  • 原因
    • C語言強制型別轉換的不嚴格檢查,過於粗魯
    • 這是C++為什麼要引入四個轉換符的原因
  • 後果
    • 程式奔潰、不能按預期執行、程式碼漏洞

4、總結

4.1、引用能做到的,指標都能無損的做到——反之則不行

  • 指標的操作符 * 能幹的活,引用幹不了,也就是指標能直接對地址的資料進行讀寫,引用則不能

  • 指標可以指向任意一個地址(甚至空地址),引用只能指向一個物件(不可空引用)

    • 指標可以對地址進行加減操作,從而修改相鄰地址的資料,比如修改一個數組
    • 指標不能自動解除指向;而引用在指向的物件銷燬時,會自動解引用
  • 指標可以隨意修改所指向地址的資料

    • 引用只能修改所指向物件的固定成員,或者通過所指向物件提供的固定方法來修改資料

4.2、指標的靈活帶來缺陷,引用的不靈活帶來安全

引用避免了對地址的直接讀寫,增強了記憶體操作的規範,從而增強了語言記憶體安全性,降低了對開發者的要求。

指標和引用,各有各的用途,我們理解本質後,在不同的場景選擇合適的工具即可!

4.3、題外話:C++引用和Java引用的區別

C++中一個引用指向的地址不會改變,改變的是指向地址的內容,然而Java中引用指向的地址在變!!

如果非要對比著看,那麼Java中的“引用”倒是和C/C++的指標更像一些,和C++的“引用”很不一樣。

java去除指標概念,就用引用羅

你看 java:

A a = new A(1); 
A b = new A(2); 
b = a; 

沒有問題,a 和 b引用同一個物件A(2),原來的A(1)成為沒有被引用的物件。 垃圾回收機制會在之後的某個時刻把A(1)幹掉。

而C++則不然。C++的引用就語義上說是“別名”【本質是個const指標,又叫指標常量】,而並不是指標的另一種用法:

A a = A(1); 
A b = A(2); 
A& c = b; //c 是 b的別名 
c = a; //並不是 c 引用 a,而是拷貝操作 c.operator= ( a ) 

就語言機制來說,java的引用是用來管理和命名物件;

而,C++的引用機制是很純粹的,就是別名而已,一旦定義就無法修改,即無法再指向其他變數。

每種語言的特性都是整體的有機部分。

我們知道, java的引用機制是一個很複雜的機制。他必須區分“基本物件”和“複合物件”,你可以想象一下,如果其中沒有基本物件,那麼我們如何完成物件的複製? 唯一的解決方案是提供兩個等於號,或者一律用建構函式… 但是綜合來看,他和垃圾回收形成了相當完美的組合方案。

而C++ 的引用機制為運算子過載提供了大幅度的支援。C++ 的引用是用類“模擬”基本物件的根本要求。 如果C++使用java那種引用,那麼原本漂亮的 operator[]、 proxy class 等就很難實現了。 更進一步, C++ 的運算子過載對 C++ 的模版機制提供了強力的支援