[TOC]
點進這篇文章的朋友們,如果對「指針」沒有概念,那麼請面壁思過。
你不是一個正統的程序員,你是野路子,是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++ 的模版機制提供了強力的支持