1. 程式人生 > >有關一級指標和二級指標的歸納總結

有關一級指標和二級指標的歸納總結

           計導課講到指標和動態記憶體分配,這裡真是一個難點,很多概念容易混淆,有時候一個概念要查閱好多網站,看過很多博文才能理解,不過學習程式設計不就是這樣嘛~~一定要想辦法理解才行,所以今天歸納了關於一級指標和二級指標的知識供大家學習(中間有一些摘錄,不太記錄來源,如果有涉及到哪位仁兄的文章,請跟我說,我會加上來源連結噠~)(http://blog.csdn.net/bearray123/article/details/7209356  這個博友寫的二級指標理解很不錯)

一級指標與二級指標的含義:

首先任何值都有地址,一級指標的值雖然是地址,但這個地址做為一個值亦需要空間來存放,是空間就具有地址 ,這就是存放地址這一值的空間所具有的地址,二級指標就是為了獲取這個地址,一級指標所關聯的是其值(一個地址)名下空間裡的資料,這個資料可以是任意型別並做任意用途,但二級指標所關聯的資料只有一個型別一個用途,就是地址,指標就是兩個用途提供目標的讀取或改寫

麼二級指標就是為了提供對於記憶體地址的讀取或改寫。指標的表現形式是地址,核心是指向關係指標運算子“*”的作用是按照指向關係訪問所指向的物件.如果存在A指向B的指向關係,則A是B的地址,“*A”表示通過這個指向關係間接訪問B.如果B的值也是一個指標,它指向C,則B是C的地址,“*B”表示間接訪問C如果C是整型、實型或者結構體等型別的變數或者是存放這些型別的資料的陣列元素,則B(即C的地址)是普通的指標,稱為一級指標,用於存放一級指標的變數稱為一級指標變數。A(即B的地址)是指向指標的指標,稱為二級指標,用於存放二級指標的變數稱為二級指標變數.根據B的不同情況,二級指標又分為指向指標變數的指標和指向陣列的指標.

*****總結:指標也是傳值傳遞,當我們要在被調函式裡面改變呼叫函式一級指標的值時,就需要以二級指標作為引數。這種情況是經常碰到的,比如在連結串列(無頭結點)操作時是通過連結串列第一個元素來找到其他所有連結串列中的元素,如果刪除操作時刪除的正好是第一個 元素,那麼這時就要改變連結串列頭指標的指向了。當然還有在二叉樹操作時當刪除的剛好是樹根結點,此時也要改變一級指標的指向。

為什麼要用二級指標(見上傳的圖片)


怎麼理解利用二級指標申請動態記憶體

以前在學習資料結構的時候一直沒弄懂函式引數裡面傳遞 ** p  以及使用 &的含義,這裡摘抄了一小段文章方便理解。不懂的時候再看看這幾段程式碼。

指標引數是如何傳遞記憶體的?

    如果函式的引數是一個指標,不要指望用該指標去申請動態記憶體。見如下例子:

void GetMemory(char *ip, int num)

{

 ip= (char *)malloc(sizeof(char) * num);

}

void Test(void)

{

 char*str = NULL;

 GetMemory(str,100); // str 仍然為 NULL

 strcpy(str,"hello"); // 執行錯誤

}

試圖用指標引數申請動態記憶體

    毛病出在函式GetMemory中。編譯器總是要為函式的每個引數製作臨時副本,指標引數ip的副本是 _ip,編譯器使 _ip= ip.如果函式體內的程式修改了_ip的內容,就導致引數ip的內容作相應的修改。這就是指標可以用作輸出引數的原因。在本例中,_ip申請了新的記憶體, 只是把_ip所指的記憶體地址改變了,但是ip絲毫未變。所以函式GetMemory並不能輸出任何東西。事實上,每執行一次GetMemory就會洩露一 塊記憶體,因為沒有用free釋放記憶體。

    如果非得要用指標引數去申請記憶體,那麼應該改用“指向指標的指標”,見如下示例:

 void GetMemory(char **p, int num)

{

 *p = (char *)malloc(sizeof(char) * num);

}

void Test(void)

{

 char *str = NULL;

 GetMemory(&str, 100); // 注意引數是 &str,而不是str

 strcpy(str, "hello");

 std::cout<< str << std::endl;

 free(str);

}

這裡寫一段自己的理解:這個程式是想通過函式給str指標分配一點記憶體 --malloc,然後再給str指標賦值。。。注意這是在另一個函式內部改變str'指標的指向(str原來是指向NULL的,現在要使之指向函式 GetMemory內分配的記憶體區域)所以要使用二級指標來實現,其實質就是改變str指標的指向(比如從指向A 改為指向B),而不是改變str指標指向地址的內容哦。

   用指向指標的指標申請動態記憶體

    當然,我們也可以用函式返回值來傳遞動態記憶體。這種方法更加簡單,見如下示例:

 char *GetMemory(int num)

{

 char *ip = (char *)malloc(sizeof(char) * num);

 return ip;

}

void Test(void)

{

 char *str = NULL;

 str = GetMemory(100);

 strcpy(str, "hello");

 std::cout<< str << std::endl;

 free(str);

}

我們在做連結串列的時候,我們肯定希望在用一個函式creatLink(...)函式來增加連結串列節點。那麼我們可以有2種方法來實現
    第一種,用一級指標

  1. typedef struct node{    
  2.     ...    
  3.     ...    
  4. }list;    
  5. node *create(list *l){    
  6.    list *head;    
  7.    head = l;    
  8.    malloc...//為節點申請記憶體空間     
  9.    ...    
  10.    ...    
  11.    //操作     
  12.    return head;    
  13. }    
  14. int main(...){    
  15.     ...    
  16.     ...    
  17.     list *listhead    
  18.     createList(listhead);    
  19.     ....    
  20.     //以後的任何操作,我們都要考慮,我們是否拿到的是連結串列頭指標,到底哪個是連結串列波的頭指標,我們是否要renturn下來返回連結串列頭指標??等。。。。     
  21. }    

這樣做可以達到刪除增加 節點的目的,但是,在任何情況下,我們的操作都得死死地抓住頭指標,也即是我們增加刪除節點後,任何對連結串列長度的修改,我們都要連結串列頭指標返回,即 return head;所以,我們要通過這個函式最後獲得頭指標,抓住他,死死地抓住他,然後操作。
   
    第二種方法:用雙指標,也即是二級指標。

  1. typedef struct node{    
  2.     ...    
  3.     ...    
  4. }list;    
  5. void   create(list **l){    
  6.    list *head;    
  7.    head = *l; //這裡不知道寫成 *l 對不對,原文是寫的l,我自己覺得應該是 *l    
  8.    malloc...//為節點申請記憶體空間     
  9.    ...    
  10.    ...    
  11.    //操作     
  12. }    
  13. int  main(...){    
  14.     ...    
  15.    ...    
  16.     list *st    
  17.     createList(st)    
  18.     ....    
  19.     ..    
  20. // 以後的任何操作,不管是刪除還 是插入,我們不需要考慮,我們是否已經return head了,不需要,我們在任何情況下,對連結串列的操作都只需要使用 st來完成,因為,st就是連結串列的頭指標,不變,因為在申明st的時候,已經為st分配 一個地址空間,它是存在的,一直存在,直到main函式結 束     
  21. }    

總結:  如果函式引數中傳遞的是指標,而且想改變傳入指標的指向的話,則可以使用 二級指標來實現。(當然如果不在函式中的情況則可以直接給一級指標重新賦值使之指向另一個數據)

為什麼改變主函式裡指標的內容,即所指變數的地址時,要用二級指標

先來說說函式的傳參機制

當陣列作為引數傳入時,是能夠實現雙向傳遞的

陣列名作函式引數與基本資料型別作函式引數相比,具有完全不同的特點。c語言規定,陣列名代表陣列的首地址,所以,陣列名作函式引數時,是將陣列的首地址由實參傳遞給形參,即實引數組與形引數組會公用一個相同的陣列首地址和一段相同的儲存單元。所以,當形引數組元素髮生變化時,實引數組元素的值也會隨著發生改變。
當形引數組元素髮生變化時,實引數組元素的值也會隨著發生改變。
陣列作為引數是按地址傳遞的
陣列名就是陣列的首地址。因此在陣列名作函式引數時所進行的傳送只是地址的傳送, 也就是說把實引數組的首地址賦予形引數組名。

函式呼叫時基本的引數傳遞方式有傳值和傳地址兩種,在傳值方式上是將實參的值傳遞給形參,因此實參可以是表示式或常量,也可以是變數(陣列元素),在傳址方式下,要將實參的地址傳遞給形參,因此,實參必須是變數(陣列名或陣列元素,不能是表示式或常量),在這種情況下,被調函式對形參進行修改其實就是對實參進行修改,因此客觀上能實現資料的雙向傳遞。

也解釋了當要改變主函式裡指標的內容,即所指變數的地址時,要用二級指標。

.指標作為形參時,如果只需要改變指標指向的值,可以使用一級指標,如果需要改變指標本身的地址

,則需要使用二級指標,相當於改變的是一級指標指向的值。
2.指標作為形參時,指標指向的內容變化是可以帶回的,指標地址的變化是不可帶回的,即指標作為引數,其地址不可改變,否則形參就無法傳回實參的值。

即指標作為形參,指標本身的地址改變,函式返回時這種變化將無效(注意!!!是當形參是指標時!!!!)

陣列指標與指標陣列(一些notes)

陣列指標:int(*p)[4] 指向陣列p的指標 a pointer to an array

指標陣列:int *p[4] orint *(p[4]) 陣列p中元素都為int型指標array of pointers

[ ]的優先順序高於*