程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C++中引用傳遞和指針傳遞函數參數的詳解,函數詳解

C++中引用傳遞和指針傳遞函數參數的詳解,函數詳解

編輯:C++入門知識

C++中引用傳遞和指針傳遞函數參數的詳解,函數詳解


先來分析指針這個東東:

從概念上講,指針本質上就是存放變量地址的一個變量,在邏輯上是獨立的,它可以被改變,包括其所指向的地址的改變和其指向的地址中所存放的數據的改變。

clip_image002 clip_image004

上面的圖表示了程序運行時變量的值和地址,這時的內存長什麼樣子呢?

clip_image006

注意指針是一個變量,它當然有內存空間,裡面存的就是一個地址,通過這個地址我們就能找到它所指向的對象。

說明:上圖中兩個字母p和n在最左邊,代表什麼?後面在介紹程序的編譯過程中用到,先賣個官司。如果下面的寫的東西你看不懂,沒關系,往下看,我不相信你看完最後的編譯原理的一點點知識,你仍然不懂!

再來分析引用這個東東:

而引用是一個別名,它在邏輯上不是獨立的,它的存在具有依附性,所以引用必須在一開始就被初始化,而且其引用的對象在其整個生命周期中是不能被改變的(自始至終只能依附於同一個變量)。

上面的這段話,如果不理解,沒關系,往下看。

在C++中,指針和引用經常用於函數的參數傳遞,然而,指針傳遞參數和引用傳遞參數是有本質上的不同的:

指針傳遞參數本質上是值傳遞的方式,它所傳遞的是一個地址值。值傳遞過程中,被調函數的形式參數作為被調函數的局部變量處理,即在棧中開辟了內存空間以存放由主調函數放進來的實參的值,從而成為了實參的一個副本。值傳遞的特點是被調函數對形式參數的任何操作都是作為局部變量進行,不會影響主調函數的實參變量的值。(這裡是在說實參指針本身的地址值不會變)

clip_image008

說明:紅線上面是另一個函數的占空間,該函數可以通過指針的方式修改n的值,或者修改自己的值,讓自己指向其他的地址,不再指向n。但是不管怎樣,它永遠修改不了p的值。因為參數傳遞的方式是值傳遞。

注意:什麼叫能修改p值?能修改p這個變量標識符對應的內存空間就叫做修改了p的值。

而在引用傳遞過程中,被調函數的形式參數雖然也作為局部變量在棧中開辟了內存空間,但是這時存放的是由主調函數放進來的實參變量的地址。被調函數對形參的任何操作都被處理成間接尋址,即通過棧中存放的地址訪問主調函數中的實參變量。正因為如此,被調函數對形參做的任何操作都影響了主調函數中的實參變量。

clip_image010

說明:ref不再是指針方式存放n的地址,它轉而存放p的地址,並且透明的對ref執行間接尋址,所以對ref的任何操作都會修改變量標識符p對應的內存空間的值。

例如程序裡執行:ref =0012ff23(隨意寫的一個內存地址,意思就是修改指針p的內存值);

ref第一次尋址,根據編譯器符號表中ref對應的內存空間地址0x0012FF36,找到這個變量標識符對應的內存空間中放的是0012FF40。

第二次尋址,把0012FF40當作地址,找到內存空間中存放的是0012FF44。

然後把0012FF44修改成2(十進制)。

引用傳遞和指針傳遞是不同的,雖然它們都是在被調函數棧空間上的一個局部變量,但是任何對於引用參數的處理都會通過一個間接尋址的方式操作到主調函數中的相關變量。而對於指針傳遞的參數,如果改變被調函數中的指針地址,它將影響不到主調函數的相關變量。如果想通過指針參數傳遞來改變主調函數中的相關變量,那就得使用指向指針的指針,或者指針引用。

clip_image012clip_image014

說明:

(1)指針傳遞參數的時候,情況是上面的那張圖片,指針p把自己的內存的內容copy一份到q的內存空間中去。所以p和q都是指向堆空間中的某個實例對象的,也就是說他們都存放了堆空間中這個實例對象的地址。但是我如果在被調函數中修改了q的內存中的地址,也就是說不讓q指向這個實例對象了,那麼這些都是合法的。並且我們無法修改主調函數中p對應的內存中的內容,也就是說無法通過p,修改p指向的堆內存實例對象。

(2)但是利用應用傳遞參數就不一樣了,其實引用的本質仍然是指針,只不過引用對應的內存空間中存放的不是p對應的內存空間內容的備份。引用內存空間中存放的是p(也就是p對應的內存空間的地址,比如說上面的0012FF40,也就是我們之前說的編譯器的符號表中的地址,p就是對應0012FF40,你在程序中寫p和寫0012FF40都一樣,都是同一塊內存空間對應的地址,只不過p更容易程序員記憶)。

並且這個引用在被調函數中的任何使用的時候都是采用透明的間接地址訪問的方式,也就是說你只要對ref修改(修改的意思就是修改ref對應的內存中的東西),編譯器都會做一次透明(之所以說透明,是因為這個過程是編譯器私自做的,程序員是看不到的,也無法控制。當然如果你要修改編譯器那就可以了)的間接尋址,把你得任何的修改都作用到指針p對應的內存空間中去。

並且這用關聯是初始化以後不能修改的。到底怎麼就不能修改了,看下面:

例子前面已經說過了。

那麼,編譯器是怎麼做到透明的間接尋址的呢?下面看一下編譯過程:

為了進一步加深大家對指針和引用的區別,下面我從編譯的角度來闡述它們之間的區別:

程序在編譯時分別將指針和引用添加到符號表上,符號表上記錄的是變量名及變量所對應地址。指針變量在符號表上對應的地址值為指針變量的地址值,而引用在符號表上對應的地址值為引用對象的地址值。符號表生成後就不會再改,因此指針可以改變其指向的對象(指針變量中的值可以改),而引用對象則不能修改。

為了說明這些,下面先說一下編譯原理的相關知識:

編譯原理的一些簡單知識:

編譯器在編譯我們的程序的時候,一般的編譯器要對我們的源程序掃描兩遍才能完成編譯(當然如果用拉鏈-回填也可以一遍掃描,這不是我們的重點)。第一遍中很重要的一個工作就是建立符號表,第二遍就是使用這些符號進行對內存空間操作。編譯器會掃描源程序中所有的變量,然後給他們建立一個表格,表格的內容就是下圖所示,包括你得變量的名稱(符號),對已的內存空間中的地址,變量的類型。

注意:符號表中不會有這個變量的內存空間中存儲的內容。就好像我們的酒店的管理,前台可能也有一張表,裡面對應著房間編號、房間地址、房間大小。

如:

1號房—>B區A棟三樓302房間—>45平

2號房—>B區C棟三樓432房間—>80平

“1號房”、“2號房”就是為了好記憶,前台就是覺得“B區A棟三樓302房間”這樣的字眼太麻煩。

計算機的內存就類似,唯一不同的是我們不關心計算機給我存在哪了,也就說我只要知道我的是“幾號房“,並且我記住,計算機也記住。我不管你給我放在哪個區哪棟樓幾號房間。我每次想用的時候,我就說“幾號房”,然後計算機通過上面的那張表給我對應找到房間的具體的位置。

編譯器就是前台,維護一張表。程序員就是住戶,我只知道我是“幾號房”。

clip_image002[1]clip_image004[1]clip_image016clip_image006[1]

看到了吧,其實p和n等變量符號,就是一個地址。如上面的圖n就是0x0012ff40開始的四個內存存儲單元(因為n是int類型的變量,占用四個存儲單元,這個跟計算機有關),之所以用n就是為了簡單,不然老說0x0012ff40,一會就懵了。

還有一個東西就是p對應的內存空間中存儲的東西,這個是在符號表中沒有的,必須等到程序跑起來,訪問內存的時候才能看到。在程序運行時,我們對變量符號(也就是對應的內存單元的地址)的任何操作,都會轉化為對對應的內存單元的操作。

比如:

n=2;這個操作。

計算機會首先去符號表中查找這個變量符號,找到它對應的內存地址,如上面個的圖中所示,拿著n找到了0x0012ff44,然後就根據這個地址,找到了內存的真實的存儲單元(n只是為了便於我們記憶弄的一個符號,物理內存中沒有這個東西的,但是我們有符號表啊,能轉換到實際的內存地址)。然後就對這個內存空間按照程序的需要讀寫。

如果你敢沒有定義這個變量n直接用,那麼第一遍掃描的時候就沒有登記你的n對應的內存地址是多少,你這裡直接訪問,那麼編譯器就提示變量未定義的錯誤。

是不是很好玩?下面回歸今天的正題:

編譯器就是利用符號表實對引用變量實現透明的間接地址訪問的,之所以說這麼多就是為了說明引用在符號表中是怎麼弄的。

clip_image018

如果內存空間是這樣的,這時的符號表長什麼樣子呢?

clip_image020

看見了吧,ref這個貨竟然在符號表中不對應自己的地址(0x0012ff40),直接對應別人的地址。你如果對ref操作,計算機就拿著ref查它的內存地址,然後在對這個內存空間讀寫。但是一查表,就是p對應的內存空間的地址,自然而然的就操作p對應的內存空間了。

符號表在編譯器第一遍讀源程序後生成,然後就不會再改,因此指針可以改變其指向的對象(改變的是指針變量p的內存空間中的存儲的數據),而引用對象則不能修改,也就是說ref一旦是p的引用,它就得一輩子是p的引用,因為變量符號表改不了,你只要操作ref直接就對應到了p的內存空間(ref真正的內存空間已經不重要了,誰也看不見,誰也用不到)。也許有人說,那我輸出ref的地址看看,不好意思,地址就是上面的符號表中的內容。(如果真的想看,就得給編譯器解刨,看看它裡面是什麼)

說明:由於引用本身就是目標的一個別名,引用本身的地址是一個沒有意義的值,所以在c++中是無法取得引用的內存地址的。取引用的地址就是取目標的地址,c++本身就根本不提供獲取引用內存地址的方法。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved