1. 程式人生 > >淺談指標

淺談指標

1. 什麼是指標

指標是一種資料結構(代表記憶體地址的無符號整數),使用它定義的變數叫做指標變數。打個比方:有個人讓你去麥當勞幫我買個雪糕,那麼麥當勞是這個變數a,但是那個人現在把麥當勞所在的地址寫在紙上給你,那麼這張紙就可以看做一個指向麥當勞的指標。

2. 為什麼使用指標,什麼情況下使用指標

  • 函式之間無法通過傳參共享變數
    函式的形參變數屬於被呼叫者,實參屬於呼叫者,函式之間名字看見互相獨立可以重名,函式之間的資料傳遞都是值傳遞(幅值、記憶體拷貝)。

  • 使用指標可以優化函式之間傳參的效率
    使用指標可以不用進行資料的拷貝,直接使用記憶體的資料

  • 堆記憶體無法與識別符號建立聯絡,只能配合指標

3. 如何使用指標

定義:型別* 變數名p;
  • 指標變數與普通變數使用方法有很大區別,一般以p結尾,與普通變數區分開。

  • 表示此變數是指標變數,一個*只能定義出一個指標變數,不能連續定義。

     int* p1,p2,p3; // p1是指標,p2,p3是int變數
     int *p1,*p2,*p3; // 三個指標變數
    
  • 型別表示的是儲存是什麼型別變數的地址,它決定當通過地址訪問這塊記憶體時訪問的位元組數。

  • 指標變數的預設值也是不確定,一般初始化為NULL(空指標)。

4. 使用指標要注意的問題

  • 空指標

     指標變數的值為NULL(大多數是0,也有特殊情況是1),這種指標變數叫空指標,空指標不能進行解引用(*指標變數),NULL被作業系統當作復位地址了(儲存了系統重啟所需要的資料),當作業系統察覺到程式訪問NULL位置的數以的時就會向程式傳送段錯誤的訊號,程式就會死亡。
     空指標還被當作錯誤標誌,如果一個函式的返回值是指標型別,實際返回的值是NULL,則說明函式執行失敗或出錯。
     在C語言程式碼中應該杜絕對空指標進行解引用,當使用來歷不明的指標(呼叫者提供的)前應該先判斷是否為NULL。
     #define NULL ((void*)0)
     if(NULL == p)
     {
     
     }
    
  • 野指標:指標變數的值是不確定的或都是無效的,這種指標叫野指標。

     使用野指標不一定會出問題,可能產生的後果如下:
     1、一切正常
     2、段錯誤
     3、髒資料
     雖然野指標不一定會出錯,但野指標比空指標的危險更大,因為野指標是無法判斷出來、也無法測試出來,也就意味著一旦產生無法杜絕。
     雖然野指標無法判斷也無法測試出來,但是所有的野指標都是人為製造出來的,最好方法就是不生產野指標:
     1、定義指標變數時要初始化。
     2、不返回區域性變數的地址。
     3、資源釋放後,指向它的指標及時置空。
    

5. 指標和陣列的關係

陣列名就是個指標(常指標),陣列名與陣列首地址是對映關係,而指標是指向關係。
由於陣列名就是指標,所以陣列名可以使用指標的解引用運算子,而指標也可以使用陣列的[]運算子。
使用陣列當函式的引數時,陣列會蛻變成指標,長度也就丟失,因此需要額外新增一個引數用來傳遞陣列的長度。

6. 指標的算術運算

指標的本質就是個整數,因此從語法上來說整數能使用的運算子它都能使用。
不是所有的運算子對指標運算都是有意義的。
指標+整數 <=> 指標+寬度乘整數 向右移動
指標-整數 <=> 指標-寬度乘整數 向左移動
指標-指標 <=> 指標-指標/寬度 計算出兩個指標之間相隔多少個元素。

7.指標的&和*

這裡&是取地址運算子,*是間接運算子。
&a 的運算結果是一個指標,指標的型別是a 的型別加個*,指標所指向的型別是a 的型別,指標所指向的地址嘛,那就是a 的地址。
*p 的運算結果就五花八門了。總之*p的結果是p 所指向的東西,這個東西有這些特點:它的型別是p 指向的型別,它所佔用的地址是p所指向的地址。

int a=12; int b; int *p; int **ptr;  
p=&a; //&a 的結果是一個指標,型別是int*,指向的型別是int,指向的地址是a 的地址。  
*p=24; //*p 的結果,在這裡它的型別是int,它所佔用的地址是p 所指向的地址,顯然,*p 就是變數a。  
ptr=&p; //&p 的結果是個指標,該指標的型別是p 的型別加個*,在這裡是int **。該指標所指向的型別是p 的型別,這裡是int*。該指標所指向的地址就是指標p 自己的地址。  
*ptr=&b; //*ptr 是個指標,&b 的結果也是個指標,且這兩個指標的型別和所指向的型別是一樣的,所以用&b 來給*ptr 賦值就是毫無問題的了。  
**ptr=34; //*ptr 的結果是ptr 所指向的東西,在這裡是一個指標,對這個指標再做一次*運算,結果是一個int 型別的變數。 
  • 賦值:指標變數 = 地址

     棧地址賦值:
     	int num = 0;
     	int* p = NULL;
     	p = &num;
     堆地址賦值:
     	int* p = NULL;
     	p = malloc(4);
    
  • 解引用(根據地址訪問記憶體):*指標變數名 <=> 變數

     1、根據變數中儲存的記憶體編號去訪問記憶體中的資料,訪問多少個位元組要根據指標變數的型別。
     2、如果指標變數中儲存的地址出錯,此時可能發生段錯誤(這是賦值產生的錯誤)。
    

8. 指標和const配合

const int* p; 保護指標指向的資料,不能通過指標解引用修改記憶體的值。
int const *p; 同上
int * const p; 保護指標變數,指標變數初始化之後不能再顯式的賦值。
const int *const p; 既不能修改指標的值,也不能修改記憶體的值。
int const * const p; 同上。

9. 什麼是二級指標,什麼情況下使用

二級指標就是指向指標的指標,就是地址的地址,打個比方,你有一個箱子,裡面有你要的東西,你的鑰匙能開啟箱子,鑰匙就是一級指標,你要用鑰匙開啟別的箱子去拿到開你要的東西的箱子的鑰,那你手上的鑰匙就是二級指標,如此類推…
在這裡插入圖片描述

那什麼時候需要用到二級在呢,一般來說,當你需要共享一級指標變數或者函式時你就需要用到二級指標來進行操作,或者你需要傳遞一級指標陣列或者其他型別的資料結構時就需要用到二級指標來進行操作。總的來說和一級指標用法類似。

10. 函式指標

可以把一個指標宣告成為一個指向函式的指標。
int fun1(char *,int);
int (*pfun1)(char *,int);
pfun1=fun1;
int a=(*pfun1)(“abcdefg”,7); //通過函式指標呼叫函式。
可以把指標作為函式的形參。在函式呼叫語句中,可以用指標表示式來作為實參。

    int fun(char *);  
    inta;  
    char str[]="abcdefghijklmn";  
    a=fun(str);  
    int fun(char *s)  
    {  
        int num=0;  
        for(int i=0;;)  
        {  
            num+=*s;s++;  
        }  
        return num;  
    }  

這個例子中的函式fun 統計一個字串中各個字元的ASCII 碼值之和。前面說了,陣列的名字也是一個指標。在函式呼叫中,當把str作為實參傳遞給形參s 後,實際是把str 的值傳遞給了s,s 所指向的地址就和str 所指向的地址一致,但是str 和s 各自佔用各自的儲存空間。在函式體內對s 進行自加1 運算,並不意味著同時對str 進行了自加1 運算。

11. 陣列指標

int (*p)[3]; //首先從P 處開始,先與*結合,說明P 是一個指標然後再與[]結合(與"()"這步可以忽略,只是為了改變優先順序),說明指標所指向的內容是一個數組,然後再與int 結合,說明數組裡的元素是整型的.所以P 是一個指向由整型資料組成的陣列的指標  

陣列指標,指的是陣列名的指標,即陣列首元素地址的指標。即是指向陣列的指標。

int array[10]={0,1,2,3,4,5,6,7,8,9},value;  
value=array[0]; //也可寫成:value=*array;  
value=array[3]; //也可寫成:value=*(array+3);  
value=array[4]; //也可寫成:value=*(array+4);

上例中,一般而言陣列名array 代表陣列本身,型別是int[10],但如果把array 看做指標的話,它指向陣列的第0 個單元,型別是int* 所指向的型別是陣列單元的型別即int。因此array 等於0 就一點也不奇怪了。同理,array+3 是一個指向陣列第3 個單元的指標,所以(array+3)等於3。其它依此類推。

12. 指標陣列

優先順序:()>[]>*

int *p[3]; //首先從P 處開始,先與[]結合,因為其優先順序比*高,所以P 是一個數組,然後再與*結合,說明數組裡的元素是指標型別,然後再與int 結合,說明指標所指向的內容的型別是整型的,所以P 是一個由返回整型資料的指標所組成的陣列  

陣列元素全為指標的陣列稱為指標陣列。

13. 結構體指標

1. 結構體指標就是指向結構體變數的指標;
2. 如果一個指標變數中儲存了結構體變數的首地址,那麼這個指標變數就指向該結構體變數.
3. 如果一個指標變數中儲存了結構體變數的首地址,那麼這個指標變數就指向該結構體變數.
4. 通過結構體指標即可訪問該結構體變數,這與陣列指標和函式指標的情況是相同的 結構指標變數說明的一般形式為:

  • struct 結構體名 *結構體指標變數名
  • struct student *p = &Boy; //假設事先定義了 struct student Boy;
    struct MyStruct  
    {  
        int a;  
        int b;  
        int c;  
    };  
    struct MyStruct ss={20,30,40};  
    //聲明瞭結構物件ss,並把ss 的成員初始化為20,30 和40。  
    struct MyStruct *ptr=&ss;  
    //聲明瞭一個指向結構物件ss 的指標。它的型別是MyStruct *,它指向的型別是MyStruct。  
    int *pstr=(int*)&ss;  
    //聲明瞭一個指向結構物件ss 的指標。但是pstr 和它被指向的型別ptr 是不同的。  

ptr->a; //指向運算子,或者可以這們(*ptr).a,建議使用前者
ptr->b;
ptr->c;
*pstr; //訪問了ss 的成員a。
*(pstr+1); //訪問了ss 的成員b。
*(pstr+2) //訪問了ss 的成員c。

14. 結構體成員指標

當定義一個結構體,結構體成員中有指標的時候,那個成員就叫做結構體成員指標。
含有指標成員的結構體初始化的時候,必須給指標成員給一個明確的地址。注意:給指標成員初始化的時候,要麼給其一個地址,比如是陣列陣列地址的時候,就可以通過指標來運算元組,也可以一個變數的地址;還可以給其新建一段記憶體,就是malloc一段記憶體,記憶體的地址由系統決定。

15. 指標和堆記憶體配合

什麼是堆記憶體呢
堆:資料無序的順序儲存在記憶體中,它的申請和釋放受程式設計師的控制。
堆記憶體的大小理論上等於實體記憶體的大小。
堆記憶體只能與指標配合使用(無法取名字)。
堆記憶體的釋放是受控制的,一般程式中長期使用的資料都會從資料庫、檔案中讀取到堆記憶體中。
堆記憶體歸程式設計師管理,如果程式設計師的水平有限可能會出現記憶體洩漏、碎片等問題。
堆記憶體使用麻煩,需要藉助標準庫函式(標頭檔案),申請(計算大小)、使用(與指標配合)、釋放(不能重複放、指標要置空)。

指標需要和堆記憶體配合使用,如果指標沒有申請記憶體,那它只能繼續指向而不能進行輸入,關於指標我們需要知道:

  1. 指標變數也是一種變數,佔有記憶體空間,用來儲存記憶體地址
  2. 指標與記憶體緊密相連,沒有記憶體指標沒有意義
  3. 對指標的操作實際上是對記憶體間接操作
  4. 指標變數和它指向的記憶體塊是兩個不同的概念

當使用malloc首次申請記憶體時,malloc會向作業系統提出申請,作業系統會一次向malloc分配33頁(4096byte)記憶體給它管理,malloc會再分配若干位元組給程式,接下來再分配記憶體時,malloc會從剩餘的記憶體中分配給程式。
當獲取到malloc分配的記憶體的地址時,理論上來說是可以越界的,直到超過33頁。
malloc在管理記憶體時記錄一些維護資訊,就在每塊申請到的記憶體的後面的8個位元組(兩個指標,一個指向前一塊記憶體,一個指向接下來要分配的記憶體),這些維護停下被破壞後不會立即出錯,但是會影響後面的申請和釋放。