關於C的指針,Java/Python的引用,形參與實參個人理解

最近稍微學習了下Go語言,Go語言真是大融合即視感,明明是靜態語言,卻結合了動態語言的諸多優點,寫起來感覺就像個動態語言,卻有着不輸給靜態語言的執行速度。略屌,可是現在應用還是比較窄,編程語言排行榜上前五十基本看不到他,囧。

然後Go裏面使用了指針和引用這兩種概念,因爲之前學過C++跟Java,所以對這兩者都挺有感覺的,當時指針真是整的我痛苦不堪,後來再看Java的時候,學習了引用這個概念,再回去看指針,我去,原來指針也就那麼回事。。。搞懂了內存就完全搞懂了,可是看的書硬是不說這些,讓人無語。所以決心自己寫個入門級的指針詳解,借鑑引用的概念,說說自己的理解。

先說下靜態語言跟動態語言,靜態語言必須先聲明變量才能使用,典型代表C跟C++,其實就是int num; num = 100,動態語言代表有python,不用聲明直接使用如num = 100.靜態跟動態,我們常說的就是編譯跟運行。把編譯時就能確定的數據類型叫做靜態語言,而在運行時才確定變量數據類型的叫做動態語言。這裏只是先講個題外話=_=。

 

好了,然後正式開始介紹。

搞懂內存就搞懂了指針跟引用。所以我們從內存概念入手研究指針。

 


 

一、指針初識

在C當中,是沒有引用這個概念的,當使用a = b這個操作時,不管a是什麼數據類型,他都默認發生了拷貝,但是我們有時,或者大多數時候,其實壓根就不希望他拷貝,我們想操作原來的數據,不然一個2G的數組來排序,每次都複製一下,內存可是扛不住的,所以在C當中就加入了指針這個概念,指針的運行邏輯是這樣的:

舉個例子,這個例子的代碼是C語言了。

int a = 10;

int b;

b = a;

b = 100;

 

int p = 10;

int *q;

q = &p;

*q = 100;

則結果a還是等於10,而p則變成100了。第一個例子就是我們剛纔說的,在C當中,賦值即是拷貝,這兩個已經不是同一個值了。而第二句我們都知道,因爲他們指向了同一個內存地址,所以改變了其中一個,另一個也就跟着改變了。

從內存的角度去理解這些就是。內存是一個存儲單元,它上面有很多很多格子,每個格子都有一個編號,我們把它叫做地址。如下:

0000

1000

0001

1001

0010

1010

0011

1011

0100

1100

0101

1101

0110

1110

0111

1111

 

每個編號對應一個地址,在這裏我只是舉個例子,實際的地址編號我記得是16進制的。

然後我們賦值a = 5,5保存在了0000這個編號地址裏。

所以我們知道了  

0000 -> 5

當b = a的時候,發生了拷貝,我們假設是在0001這個內存地址裏保存了新的5,這個5是b變量的值,則有   

0001 -> 5

所以從這裏我們知道對b的操作肯定不會影響到a,畢竟這兩都不是一個地址裏的東西了。

而若是這裏b是一個指針,有下面的語句:

int *b

b = &a      // b = 0000

*b = 100    

這裏發生的內存情況是這樣的,&是取地址符,也就是&a表示的是0000!!也就是b保存的是一個地址,而且是a的地址!在內存當中就是  

0001 -> 0000,

在0001這個內存地址當中保存了一個地址!而*是取值符號,也就是把地址裏保存的東西返回!b的值是0000,*b就是*0000,不就是5了嗎,當*b = 100的時候,我們就相當於改變了0000這個內存地址當中的值,所以a當然也改變了,因爲a本身代表的就是0000這個地址裏的值啊。

就是因爲這個原因,出現了形參和實參的概念,就像下面這個經典的例子:

int aa = 100;

int bb = 50;

swap(aa, bb)

 

//define

void swap(int a, int b){

int temp = a;

a = b;

b = temp;

}

最後發現aa跟bb的值並沒有交換,知道了內存的概念這就是很簡單的事了,因爲在傳入函數的時候實際發生了這樣一個過程:

int a = aa

int b = bb

沒錯,這就是一個拷貝,拷貝是分配了新地址,改變了新地址裏的值,原地址裏的值當然不會改變了,所以函數就應該寫成swap(int *a, int *b),所以也不用理會什麼實參形參了,知道了內存概念也就知道了道道。

我們可以很形象的用圖來表示:

 

因爲地址不夠直觀,所以我們纔會用指向來表示這個過程,也就是指針了。

最後要知道一個地址可以保持8位,剛好是一個字節大小。

 

 

 

二、Java的引用

建議大家也看下引用的概念,這玩意在python裏可是大放異彩,所謂的引用其實就是對指針的封裝!

在Java當中,數據類型分基本類型和引用類型。如下:



學過其他語言的話,大家對這些類型應該都不陌生。這裏爲了簡化,我們先把基本類型看做int,引用類型則是數組和類。

在Java當中聲明一些語句的時候,如下

int a,b

a = 5

b = a

a = 10

Java跟C一樣這個時候a就變成了10,但是b還是5。原因是因爲發生了拷貝,而如果是下面的語句:

 

int[] a = {1,2,3,4}   //聲明一個數組

int[] b

b = a

b[1] = 100

結果:

a :[1, 100, 3, 4]

b :[1, 100, 3, 4]

 

發現2者都改變了。這兩個例子之所以這樣,原因是因爲在內存中的存儲方式不同。在Java中有棧內存和堆內存的概念。

首先是int a = 5這句,5保存在了棧內存中,在Java當中,賦值的操作其實就是複製,java會額外複製一個5,將這個5給變量b,也就是這是兩個5,所以改變了a的值,不會影響到b的值。而在數組裏,我們前面說了這是一個引用類型。他在java當中情況是這樣的,

 

如圖,這是一句int[] a = {elel1, ele2, ele3, ele4, ele5}產生的結果,首先是變量a保存在了棧內存中,然後a所指的數組則是在內存中額外申請了一塊內存,將數組保存在了那塊內存中,我們把它叫做堆內存。

而當你使用 b = a的時候,實際的情況是不發生數組拷貝,而是b也指向了堆內存中的那塊數組,如下圖:

 

 

所以你通過b改變了這塊內存當中的值,a當然也會改變,因爲他們指向的是同一塊內存當中的數據。這就是引用類型。這是書上的解釋,可是看了指針之後我覺得更底層的情況應該是這樣的,b = a仍然是拷貝,但是因爲a本身就是地址,這個地址保存在了棧內存中,而指向堆內存中的數組,而賦值使得b拷貝了a所保持的地址!所以拷貝的是地址!!而當使用b[1]的時候,系統自動將地址轉換了,所以我們不用像操作指針一樣來操作他,這就是引用這個概念,對指針的封裝。

之所以加入了引用類型是爲了提高速度,如果每次賦值都發生拷貝的話,對於數組或對象這種比較大的數據,會造成比較多的時間和空間浪費。所以我才說Java真是一個偉大的語言T-T。指針那麼底層的東西,簡直就是來給初學者添堵的。。。

最後總結:在Java當中,賦值操作仍然是拷貝,不過是棧內存中的數據拷貝。其中基礎類型是值保持在棧內存中,而引用保存的值是最終指向的數據的地址。

當然使用的時候就用最上面圖中的指向來理解是最好的,比較不容易搞混~~

 

三、python的引用

最後說下python,因爲我用的比較多,我在知道了引用這個概念後,回去使用python,慢慢的就發現了,python在數據類型的處理上,跟Java是一個概念的!!!

瞬間就理解了之前出過的很多bug,還有很多神坑,如下:

a = {‘a’:1}

b = a

b[‘b’] = 2

發現a也改變了。所以我們知道這裏字典就是一個引用類型。

還有python裏的==跟is

 

a = 100

b = 100

c = 300

d = 300

a == b   #True

a is b   #True

c == d   #True

c is d   #False

 

嘿嘿,,我當時看到這個震驚了,這裏的道道是這樣的,==比較的是值,而is比較的是地址。

所謂值就是變量所指的內存裏保存的是什麼值,地址則是變量所指的值的地址是多少。也就是==做了C當中的*a == *b的比較而is做了&a == &b的比較。

所以我們看到兩個==輸出的都是True,因爲內存裏保存的都是100跟300,但是is的結果卻完全不同,這裏還有另外一個原因,爲什麼100的是True,300的卻變成False,照理不應該都是False嗎,因爲他們地址都不一樣啊,其實100的地址是一樣的,python默認將-5~256的數字緩存了起來(網上一說是0~255,可是我自己在解釋器裏實驗了下是這個範圍,=_=),也就是虛擬機在啓動的時候內存裏就保存了這些個值,當有賦值的時候,默認都讓他們指向了這個預先保存了的100,而300超過了這個範圍,所以300的內存地址已經不一樣了

最後要說的是python如果按照Java的概念來說的話所有的變量應該是隻有引用類型的。可以從以下幾個例子看出

a = 300

b = a

b is a   #True,說明b跟a指向了同一個300

 

b = 400

print a   #a當然還是輸出300,b是改爲指向了400,這裏要這麼理解,如下圖:

 


以上就是我自己在學習的時候發現的一些這幾個語言之間的聯繫。若有理解有誤的地方,歡迎大家拍磚!!!!!

 

注:部分圖片來自於<瘋狂Java講義>,侵刪。

 


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章