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++ 的模版機制提供了強力的支援