程式設計開發 由指標傳參引發的一點分析

2021-09-06 19:01:05 字數 4425 閱讀 2008

昨天有同學(初學指標)在練習單鏈表和二叉樹的時候,程式老是崩潰,或者得不到正確結果,於是向我求助。問題就出在指標的引數傳遞上,沒傳好指標導致記憶體混亂,其它**基本全對。這個錯誤十分可惜。故在此我想做個記錄,可能顯得十分基礎。

假設函式的引數是普通的一級指標,那麼就意味著你僅僅能使用指標、改變指標指向或者改變指向的目標變數。不能試圖通過這個指標來申請記憶體。

void getmemory(int *p)

void func()

這是乙個很常見的錯誤。當發生函式呼叫的時候,函式的形參總是實參的乙個copy(副本),就算是指標也是如此。比如上述的**能夠看成:在呼叫函式的時候,把num指向的目標位址賦值給p指標。如今num指標和p指標為兩個全然不同的實體,它們同一時候指向了同乙個目標位址。通過以下的**能夠驗證這個說法。

#include using namespace std;

void getmemory(int *p)

int main()

這樣一來的話,形參上的p指標是乙個新的指標,它的指向和原來的num指標一樣,所以能夠正常的改動目標位址的變數和進行輸出。

如今函式中的p指標申請了新的記憶體,如今就非常明顯了:它的這個行為和原來的指標沒有一點關係,那是p指標自己的事情。

解決這個錯誤的方法有非常多,事實上怎麼做都行,僅僅要搞清楚申請過來的記憶體究竟是給誰用的即可了。

void getmemory(int **p)

void func()

為什麼這樣能夠呢?我們來分析一下。**p是乙個指向指標的指標,前面說過,num和p指向了同乙個目標,那麼num和p都能讀取或是改動目標。

既然這種話,那我直接拿實參,也就是num指標,作為我的目標。

假設你了解&p, p, *p的話,int **p事實上一點也不神奇。還記得int *p中的int是什麼意思嗎?既然它是指向指標的指標,那麼理所應該就應該這樣寫了:int* *p,由於它自身是指標,並且指向的是乙個int*型別的指標。只是我非常不建議寫成int* *p,畢竟二級指標和一級指標非常有不同,為了突出二級指標應該寫成:int **p(有些人寫成int** p)。

全然不須要搞得那麼混亂,二級指標p的目標是個指標,僅此而已。操作二級指標的目標,即*p,就相當於在操作指標num,即*p == num。*num是什麼意思呢?在num前面加上了個*,表示想要操作num指標所指向的物件,非常遺憾上圖中num指向的是null,不太好說明。又由於num == *p,所以*num == *(*p) == **p。

相信看了上圖你應該對*p和**p非常明白了。

好了,回過頭來看看**:

*p = (int *)malloc(sizeof(int) * 10);
*p指向的是num,也就是num自身。如今讓num自己去申請記憶體,而不是讓別人代為申請,當然就正確了:)

申請記憶體事實上非常easy,我們通常這樣寫:

int *pbuffer = (int *)malloc(sizeof(int) * 10);
意思是說,讓作業系統給我安排一塊位置,然後把這塊位置的位址告訴我。就是把這塊新記憶體的位址返回給pbuffer這個指標,這樣pbuffer指向這塊記憶體以後就能夠進行操作了。既然這種話,那我們就讓num指標直接做這件事情,**例如以下:

int* getmemory()

void func()

這段**應該非常easy理解。函式裡p申請了一塊記憶體,然後p指向了這快記憶體,最後p把新記憶體的位址返回給了num指標。

這是安全的,前面說過,p指標自身是函式的區域性變數,存放於棧中,但p指標申請來的記憶體是存放在堆中的,所以函式結束後p會被釋放,但這記憶體塊不會。假設記憶體塊也存在棧中那就不行了。

知道了上述原理依然是不夠的,我們再來看看例如以下**:

char* getstring()

void func()

getstring函式中的字串"hactrox"存放在文字常量區,顧名思義,既然是"常量"區,肯定是僅僅讀的,那麼換句話說不論什麼對這個字串的改動都是不同意的。

上述**儘管是全然正確的,但同一時候也埋下了不小的隱患。僅僅要str指標在不論什麼時候試圖改動這個字串,就會導致程式奔潰。

首先要說明一下引用是什麼概念。c語言沒有引用的概念,&符號僅僅作為取位址用。引用是c++裡的概念,非常easy就把引用和指標搞混了。引用就是別名。

先來看乙個普通到不能再普通的語句:

int value = 10;
這個int型的變數value,顯然不是指標,它就是個變數名,代表了這塊記憶體的名字。

int *p = &value;

int &nickname = num;

第二行行語句的意思是說,我建立了乙個新的東西,這個東西是num變數的別名,nickname這個東西既不是變數也不是指標也不是副本,更不是字串,那它總得有個名字吧?就叫它引用好了。引用和指標的差別是,指標自身就是個實體,value手中掌握著10這個數字,指標指向了value手中的數字,他們的"共同目標"是這個數字10。而引用則全然不一樣,對引用來說,根本沒有"共同目標"這一說法,由於引用本身就是value他自身,是value這個變數的還有乙個名字。

引用和指標的一些差別例如以下:

1. 能夠先建立指標,然後再指向乙個目標。而引用在建立的時候就必須指定目標。總得現有這個人,然後這個人才有暱稱吧。

2. 相對於引用來說,指標很自由,指標能夠指向乙個目標也能夠指向null。可是卻不能有null引用。這不僅違背了引用的設計初衷,並且邏輯上也說只是去。乙個事物本身就不存在了,哪來的暱稱?就算能有暱稱的話,那它本來的名字叫什麼?

3. 一旦為乙個變數設立引用以後,這個引用就和這個變數繫結了。換句話說,就是這個引用就不能指向別的變數上。所以引用就相當於變數的屬性。試想,給乙個人取了外號以後,總不可能用這個外號去稱呼還有乙個人吧?假設是這種話,那麼肯定有非常多人搞不清楚究竟誰叫這個外號,系統也是。所以當然就不行了。

#include using namespace std;

void changebyreference(int &a)

void changebypointer(int *p)

int main()

使用引用的話,就能夠像操作乙個普通變數一樣方便,上述**中的引用a並沒有帶上*,而指標p在使用的時候,要帶上個*p。

使用引用的另乙個優點是安全,c++的指標太強大了,一旦沒用好就會造成非常多問題。而引用的功能則弱得多,在不須要那麼強大功能的時候使用引用顯得安全。

如今回過頭來看看使用引用怎樣來申請記憶體:

void getmemory(int *&p)

void func()

指標的引用就代表了指標自身,所以使用引用能正確申請到記憶體,這個應該沒什麼疑問。剩下的問題就是:指標的引用怎麼表示?通過上述**我們知道了是*&p。

我們來看看*&p和&*p的差別。

先來解析一下int型別的引用。由於引用的型別肯定是目標變數的型別,所以肯定是int,又由於規定&為引用符,所以int型的引用就非常好寫了。*&p == *(&p),相同的,對於指標變數,其引用的型別肯定也是指標型別,所以上述**中的引用變數肯定是int*型別的,指標是p,引用一下,就是&p。好了結果出來了,是int* &p,那不就是int *&p了。

那麼&*p又是什麼呢?&*p == &(*p),*p是指標所指向的目標,&這個目標變數,那麼結果就是"取指標指向的目標的位址",想想看,還有什麼地方存了這個位址?當然是指標自身記憶體上的值了。如果p指標指向變數num,那麼&*p、p、&num是等價的。知道了*和&的概念後,指標就能夠任意玩轉了:

#include using namespace std;

int main()

可能你會問,上面**中的*&p和&*p都是p的意思,為什麼申請記憶體的時候僅僅能寫*&p而寫了&*p就報錯了呢?

*&p是引用,引用是一種型別,而&*p是一種取位址的操作,不是型別。就好像int num = 5,在printf裡面你能夠寫num也能夠寫5,可是在**裡你僅僅能寫num而不能寫5,由於num是一種型別而5不是。

引用傳參和指標傳參的差別

在c 中,如果函式的實參的型別是資料型別比較大的資料型別,這是如果使用一般傳參就會有很大的不方便,這是如果能夠傳遞乙個位址或者是對原來引數的乙個引用對提高效能會有很大的幫助。本文研究一下引用傳參和指標傳參的區別和聯絡。其實引用傳參和指標傳參都能夠在條用函式內部對源資料進行修改,這是它們的共同點,但是...

C語言 函式傳參 傳值 傳指標 傳指標的指標

本文參考這兩篇文章 文章 一 文章二。在學習資料結構中建立單鏈表的時候,疑惑 已經定義了node,用到結構體指標的時候直接用 node就好了,為什麼還要再定義乙個 linklist呢,為什麼傳參的時候又要用linklist l呢?下面我們就來循序漸進,由淺入深來解決問題,這裡舉兩個例子作為比較。首先...

C 由指標常量和常量指標引發的問題

今天在寫乙個c 程式的時候,要用到stl的set容器,而且我想讓它利用自定義的排序準則來實現自動排序。而且set中元素型別是指向自定義的結構體的指標。結果執行總是出錯。下面先貼出起初的 include include using namespace std typedef struct test t...