用我所學去講C語言指針


文章更新,更加詳細的介紹請看這篇:https://www.cnblogs.com/lulipro/p/7460206.html

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

下面開始。

 

一、指針的定義 

        指針是內存單元的編號。內存單元是以字節為單位的。所以指針就是字節的編號。    

比如我們的個人電腦,內存一般4GB吧,那么一共就有 :   4*1024*1024*1024 = 4294967296 字節,也就是4294967296個編號。一個字節擁有一個編號,

范圍從 0  ~  4294967296-1 。

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

 

但是呢,一般我們是用16進制來表示的這些編號,但效果都一樣。

 

二、變量與內存的關系

       現在我們來看C 變量在內存中的樣子。我們使用自動變量(局部變量)來講解。

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

      

說明:

      內存可以存儲數據,所以我們把每個字節當做是一個“箱子”。數據存入內存就好比在箱子里面放數據

      但是C語言的不同數據類型占用的字節數是不都一樣的,所以,每種數據類型占的”箱子”的個數不都一樣。

      比如char型,只要一個字節就夠了,所以一個字符只需一個“箱子”。

      而int型需要4(一般是4個字節)個“箱子”才放得下。

      double型則需要8個“箱子”。

 

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

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

1、內存的數據

     我們的變量賦值為5,所以內存的數據就是    0000 0000    0000 0000     0000 0000    0000 0101   (大端模式)

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

  

2、*內存的名字  (對於我們的程序使用的內存來說,並不是每一個內存塊都有名字)

     名字就是變量名a

3、內存的地址

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

4、內存的寬度

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

     這里提一下,為后面講指針的寬度做鋪墊   :)

 

三、定義和使用指針變量

       什么是指針變量。我們知道int類型變量用來存儲整形值,12、1003、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;
    
}

 

四、指針的屬性和使用

       我認為指針有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個字節。

 

 

 

六、為什么要使用指針       

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

但並不是這樣,下面是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是隨函數調用新生成的變量,是副本,而不是源對象。

但是傳遞指針就不一樣了,因為內存數據的指針是獨一無二的。一個人有唯一的身份證一樣。

 

 

 

七、野指針和NULL指針

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

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

 

 

最后:由於內容比較多,可能有許多地方表達不當,或有疏漏,錯誤,希望大家及時指出,謝謝 : )

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM