用我所學去講C語言指針

文章更新,更加詳細的介紹請看這篇:http://www.javashuo.com/article/p-bpizmwmt-go.htmlhtml

不少人不敢講C的指針,有些人講不清,有些人怕講錯。初生牛犢不怕虎,就讓我講講。java

下面開始。程序員

 

1、指針的定義 數組

        指針是內存單元的編號。內存單元是以字節爲單位的。因此指針就是字節的編號。    函數

好比咱們的我的電腦,內存通常4GB吧,那麼一共就有 :   4*1024*1024*1024 = 4294967296 字節,也就是4294967296個編號。一個字節擁有一個編號,spa

範圍從 0  ~  4294967296-1 。指針

 畫個圖表示:(注意字節由8位bit組成,爲了直觀我沒畫出來code

 

可是呢,通常咱們是用16進制來表示的這些編號,但效果都同樣。orm

 

2、變量與內存的關係htm

       如今咱們來看C 變量在內存中的樣子。咱們使用自動變量(局部變量)來說解。

       int   a  =  5;    //假設a存儲在編號爲0x02開始的位置

      

說明:

      內存能夠存儲數據,因此咱們把每一個字節當作是一個「箱子」。數據存入內存就比如在箱子裏面放數據

      可是C語言的不一樣數據類型佔用的字節數是不都同樣的,因此,每種數據類型佔的」箱子」的個數不都同樣。

      好比char型,只要一個字節就夠了,因此一個字符只需一個「箱子」。

      而int型須要4(通常是4個字節)個「箱子」才放得下。

      double型則須要8個「箱子」。

 

來分析上圖中用橙色框起來的4個字節的內存塊,這裏就存儲了a這個變量。

咱們從4個方面去討論這個內存塊:

一、內存的數據

     咱們的變量賦值爲5,因此內存的數據就是    0000 0000    0000 0000     0000 0000    0000 0101   (大端模式)

                     每一個字節地址:                        0x02             0x03               0x04             0x05

  

二、*內存的名字  (對於咱們的程序使用的內存來講,並非每個內存塊都有名字)

     名字就是變量名a

三、內存的地址

     變量a佔用了4個字節,那麼,哪個字節的地址,纔是變量a的地址呢?答:第一個低地址字節的地址,也就是0x02

四、內存的寬度

     這裏的a變量佔用了4個字節,這就是他的寬度。

     這裏提一下,爲後面講指針的寬度作鋪墊   :)

 

3、定義和使用指針變量

       什麼是指針變量。咱們知道int類型變量用來存儲整形值,十二、100三、9823......

那麼,一樣的道理,指針變量就是用來保存地址的變量。

定義指針變量:在指向的變量的類型上加個* ,以下:

 

   int* p_int;        //指向int類型變量的指針 
     
    double* p_double;  //指向idouble類型變量的指針 
    
    int(*p_func)(int,int);  //指向返回類型爲int,有2個int形參的函數的指針 
    
    int(*p_arr)[3];        //指向含有3個int元素的數組的指針 
    
    struct Student *p_struct;  //結構體類型的指針 
     
    int** p_pointer;  //指向 一個整形變量指針的指針 

 

既然咱們已經定義指針變量了,那麼接下來就用指針變量來存儲其它變量的地址。

取地址符號 &

#include<stdio.h>

int Add(int a,int b);

struct Student
{
     int age;
     double score;
     char name[31];
   
};


int main(void)
{
    
    int val_int =100 ;
    double val_double  = 12.00;
    
    int arr[3]={1,2,3};
    
    struct Student stu={
        19,
        98.00,
        "Jack"
    };
    
    
    int*p = NULL;
    
    /**********************************************************/
    
    int* p_int = &val_int;        
     
    double* p_double = &val_double;  
    
    int(*p_func)(int,int) = &Add;  //or  =Add 
    
    int(*p_arr)[3]  = &arr;  
    
    struct Student *p_struct = &stu;  
     
    int** p_pointer = &p;  
    
    
    
    return 0;
}



int Add(int a,int b)
{
    
    return a+b;
    
}

 

4、指針的屬性和使用

       我認爲指針有2個屬性: ①指針的值   ②指針的寬度    

       指針的值很好理解,好比第一個例子   int a = 5;   int*p = &a;   那麼p的值就是a的地址0x02

       指針的寬度:由指針的類型決定。

       若是說指針的值標記了某個數據在內存的起始位置,那麼,指針的寬度就決定了從起始地址

       對應的字節開始,日後還有多少個字節,也是屬於這個內存數據的。

       生活情景「老王,去幫我去倉庫拿個貨,個人貨從78號箱開始,而且有2個。」 因而老王取出 78和79號箱子的貨物遞給你

                     假如你對老王說:「老王,幫我去倉庫取個人貨,個人從78號箱開始,一共

                     有幾個箱子,我也記不住了」 「那我怎麼取,取少了你就有損失了,取多了別人也不幹了,你不要難爲我啊」

     

 

      這點個人另外一篇隨筆也講到了  .             

       

#include<stdio.h>
int main(void)
{
    
    int a =256 ;
    
    int* p=&a;
    
    printf("%d\n",   *((unsigned char*)p)    ); //讀取內存數據時,取的寬度的比存時的寬度小,數據缺失 
    
    
    return 0;
}

 

#include<stdio.h>
int main(void)
{
    int arr[2] = {1,2};
    
    arr[2] = 100;     //ops! 訪問越界 
    return 0;
}

 

      前面咱們講了內存塊的4點要素,第4點就是內存塊的寬度,他是和指針的寬度是一一對應的。一個指針變量指向了這個內存塊,

      那麼指針的寬度就是這個內存塊的寬度。這也代表,咱們在使用指針的時候,不要越界,也不要取少,取少了取出的數據會不完整,

      越界就更嚴重了,程序會掛掉的,由於你訪問了不屬於你的內存。

 

 

 

擴展:有木有 沒有寬度的指針呢?  答:這個能夠有   void* p;

void* 類型的指針對應與C#  or java中的Object類型變量,它能夠指向任何類型變量。 不過他只保存了內存的起點,沒有保存寬度信息,若是你想

取出原來的數據,你必須作出正確的強制轉換。

 

   

五:一對相反操做的運算符   *    和   &

 

& 取地址符       &a 就獲取了a的指針,而後咱們就能夠用int*p變量出承接這個地址    p = &a; 咱們就說p指向了a

                  

 

* 解地址符       *操做符有一個很形象的動詞  :解  。p保存了a的起始地址和延續寬度,那麼,*p則是肯定起始地址,量出寬度,

                     獲取這個內存塊。 所以咱們能夠經過a 的地址p去操做(讀/寫)這個內存塊了。
 

p也是一個變量,他本身也有地址,這就長產生了指針的指針。

 注意我所說的指針的寬度並非說指針變量佔用的內存大小,而是經過這個指針指向的內存塊的寬度。指針的佔用的字節數是必定的,通常狀況下,指針變量都佔用4個字節。上面圖中我也畫了4個字節。

 

 

 

6、爲何要使用指針       

你可能會問:我有變量名,爲何還要繞一轉,用指針去使用內存數據?

但並非這樣,下面是2個例子。

   ①經過動態申請的內存是匿名的,沒有名字,只能經過指向這塊內存的指針去使用。  

     malloc()     realloc()       calloc()    這裏不擴展了。

 

   ②一個經典的題目:用函數交換2個變量的值。

 

#include<stdio.h>
void swap_2b(int a,int b);
void swap_hack(int *pa,int *pb);
void swap_normal(int*pa,int*pb);


int main()
{
    int a = 5;
    int b = 3;
    swap_2b(a,b);           //Can`t swap;
    swap_normal(&a,&b);    //OK
    swap_hack(&a,&b);      //OK
    
    
    return 0;
} 

//2b青年寫的函數 
void swap_2b(int a,int b)
{
    int t;
    t=a;
    a=b;
    b=t;

}

//程序員寫的函數 
void swap_normal(int*pa,int*pb)
{
    int t;
    t=*pa;
    *pa=*pb;
    *pb=t;
    
    
}

//黑客寫的函數 
void swap_hack(int *pa,int *pb)
{
    *pa = *pa^*pb;
    *pb = *pa^*pb;
    *pa = *pa^*pb;
    
}

咱們發現只有2b青年沒用指針哈。

緣由很簡單,C語言函數是按值傳遞的,2b青年寫的代碼之因此達不到效果是由於它操做的內存塊錯了。

不管他在函數裏面怎麼換a和b的值,main函數中的a和b都不會變。由於swap函數中的a和b是隨函數調用新生成的變量,是副本,而不是源對象。

可是傳遞指針就不同了,由於內存數據的指針是獨一無二的。一我的有惟一的身份證同樣。

 

 

 

7、野指針和NULL指針

 指針變量在使用前或者使用完,好的習慣是賦值爲NULL ,NULL 是編號爲0的字節的地址。指向NULL表示不指向任何變量。

NULL就像劍鞘,野指針就像暗箭,若是你不像被暗箭所傷,就讓他歸鞘。 

 

 

最後:因爲內容比較多,可能有許多地方表達不當,或有疏漏,錯誤,但願你們及時指出,謝謝 : )

相關文章
相關標籤/搜索