點進這篇文章的朋友們,如果對「指針」沒有概念,那么請面壁思過。
你不是一個正統的程序員,你是野路子,是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++ 的模版機制提供了強力的支持
審核編輯:湯梓紅
評論
查看更多