C語言指標學習

至聖先師發表於2022-03-30

一、計算機儲存

在C語言中,變數的儲存是先存小端,再存大端,依次往下,int 型別變數佔用四個位元組,short型別變數佔用兩個位元組,char型別變數佔用一個位元組。

需要注意的是:陣列申請的儲存空間,只能是連續的,哪怕是空的,沒有內容,也只能佔用,例如:char c[4] = {0x33,0x34,0x35},實際上只存了三個位元組資料,但是卻申請了四個位元組的空間。

二、指標的表示

在指標的定義中:int *p,*其實是和int 聯絡在一起的,p只是變數名字,但是,如果這樣寫:int *p,c;那麼,c不是指標型別,而是int型別。

計算機幾位作業系統指的是一次效能處理幾位的資料,由上圖可知,64位作業系統,一個指標佔用8個位元組的空間。

三、指標操作

C語言指標學習

p++,由上圖可知,加一個資料寬度,不同型別的變數有不同的資料寬度:

(1)int 型:因為int佔四個位元組,所以資料寬度為四。

(2)short型加2。

(3)char型加1。

減減也是一樣。

注意:指標加加對於只定義了的單獨變數來說,它的下一個儲存空間是未知的,我們並不知道下一個空間裡內容是什麼,強行訪問會出錯,所以加加減減一般都用在定義的陣列中。

四、指標操作

陣列其實就是指標,如上圖所示。 

 

如上圖所示的程式書寫,直接將a賦值給p說明指標和陣列就是一樣的。從後面的輸出也可以看出,輸出結果一樣。char型別一個位元組,加一跳一個位元組,但是變成int型別後,加一跳四個位元組。

五、陣列定義與指標定義

(1)陣列定義:

int  a[] = {0x33,0x34,0x35};陣列定義還是比較簡單的。

(2)指標定義:

先呼叫malloc函式所在的標頭檔案:<stdlib.h>;

然後利用malloc函式申請記憶體:

malloc括號裡的內容意思為申請3個記憶體單元,每個記憶體單元佔用四個位元組。malloc函式的返回值為*型別,即返回給一個指標,所以先定義了int *a,然後把申請到的空間給了a指標。後續給a賦值的操作類似,如上圖。

整體來看,上圖中指標的一系列操作等同於上圖陣列的操作。

六、補充

列印函式以十六進位制顯示:printf("%x\n", 變數);

七、指標的應用——子函式與主函式傳遞引數

首先介紹不使用指標,直接進行引數傳遞,如下圖:

這裡將主函式中的a傳遞給子函式時,相當於將a先複製一遍,再賦值給param,如上圖右邊紅色儲存示意圖所示,這樣做在單個變數這種小資料量時的操作是可以的,但是如果要傳一個陣列呢?這樣做就會導致記憶體的浪費。但是它也是有優點的,如下圖:

在子函式中更改引數,但是不影響主函式的a,因為剛才說過了,它傳參的原理是再複製一遍。綜上,優點:不會破壞原有的引數的值;缺點:佔用記憶體,傳遞大資料量時不宜使用。

接下來介紹指標傳參:

定義一個子函式,子函式需要的引數有指標和陣列元素的個數,在主函式裡,定義了一個陣列,一個變數,用變數接收陣列中最大的那個元素。

如紅色記憶體示意圖所示,a3·陣列就佔用了那6×4個位元組的記憶體,max佔用4個位元組,在子函式中定義了一個指標變數(要傳入的引數,是個區域性變數),這個指標變數array同樣也申請了一塊記憶體,佔用8個位元組,在主函式中將a傳入,那麼array存的就是a的首地址。所以這樣的話只新建了8個位元組的開銷,它訪問的還是主函式陣列那一段儲存空間。它倆共用一套陣列,避免了再次賦值。在子函式這個呼叫程式結束後,array被釋放掉。但是這樣的缺點就是避免不了資料的更改:

在子函式中把array[1]的值改為66,然後列印出來看看:

可以看到,原來陣列中的值已經被修改了。綜上所述,優點:不用複製,節省空間和時間;缺點:資料易被修改。

綜合改進版:加入const:

加了const後,說明傳入的引數是隻讀,不可修改的,若強行修改,如上圖,編譯器會報錯,相當於加入了一種保護機制。

C語言中還有很多這種傳遞地址的函式,如:

strlen函式,看它下面的解釋,也是用的const型別。

七、指標的應用——函式多返回值

在C語言的一般定義的子函式中,只能return一個返回值,如果想要返回多個值就不行了。但是正是利用上述傳入地址引數,共同使用一個陣列的特性,可以實現資料的更改,將這一缺點轉為優點,它的可以更改的屬性,使得在一般意義上,實現了多個值的返回:

 ...

 ...

返回值MAX = 30,count = 3,在意義上實現了值的返回。下面通過儲存示意圖再做解釋:

首先劃定了MAX的空間4個位元組,然後呼叫子函式的話,定義了max的空間8個位元組,把&MAX傳入,那麼max中存的就是MAX的首地址。接著在子函式中給*max寫入資料,相當於在MAX空間寫資料,將MAX的值更改,實現值的返回。當子函式返回之後,max被銷燬。由於引數可以隨便多,所以返回值可以隨便多。同樣,在標準C語言庫中,我們也可以找到類似的函式:strcpy

結合下面的解釋可以知道,const是傳入的不可以更改資料,而那個char *則是可以修改,也就是我們想要的返回值。

strcpy函式使用示例:注意strcpy的函式解釋中雖然要求傳的引數是const,但是正如上面自定義的FindMaxAndCount函式,在主函式呼叫中傳遞進引數,並不需要再加一遍const,直接把地址傳進去就好了,這裡也是一樣,我們直接把地址傳進去,不需要再額外的加const這個關鍵詞。strcpy這個函式就實現了字串陣列的複製。需要補充的是,列印函式printf正如第一個C語言程式一樣,列印hellow world!時,我們直接這樣寫:printf("Hellow world!"),不需要像%d、%x、%c這樣的列印方式的關鍵詞,所以這裡也是一樣,直接列印就好。

八、指標的應用——函式返回值為指標

定義一個函式,該函式的返回值為指標型別,通過返回的地址,拿到陣列的地址,間接訪問陣列。如下所示,在列印時,可以寫*pt,也可以寫pt[]加下角標的形式,這些都是等價的。

#include <stdio.h>
#include <stdlib.h>

/*********************/
int Time[] = {23,59,55};

int *GetTime(void)//int *看成一個整體,意思是該函式的返回值為int *型別。
{
    return Time;
}
/*********************/
int main()
{
    int *pt;
    pt = GetTime();
    printf("pt[0]=%d\n",*pt);
    printf("pt[0]=%d\n",pt[0]);
    printf("pt[1]=%d\n",pt[1]);
    printf("pt[2]=%d\n",pt[2]);
    return 0;
}

但是不能夠把區域性變數返回,因為區域性變數在執行完此子函式後就會被銷燬,如下是錯誤的。

/*********************/

int *GetTime(void)//int *看成一個整體,意思是該函式的返回值為int *型別。
{
    int Time[] = {23,59,55};
    return Time;
}
/*********************/

九、指標的實際操作——檔案的應用

fopen函式,第一個引數為const char *,按照我們之前學的,就是用指標傳遞一個大容量的陣列,在這裡根據Filename可知,是檔案;第二個也是const char *,是mode,可查閱該函式的定義得知mode可以是什麼值。返回值為FILE *,FILE是個結構體,FILE *就是檔案的控制程式碼,拿到這個FILE *我們就持有了檔案,我們才可以對它進行操作。

注意:FILE *與char *、int *對比可知,它是一個新的資料型別,就是FILE。

既然fopen返回值為FILE *,那麼我們就用FILE *去接它:

當然名字是隨便起的,這裡起名為f。

在最後還需要一個fclose函式把檔案關掉,如下:

int main(void)
{
    FILE *f=fopen("E:\\text.txt","w");
    
    
    fclose(f);
    return 0;
}

然後我們就可以在fopen和fclose之間去給該檔案寫東西(因為fopen中此次選擇的是w:只寫):

int main(void)
{
    FILE *f=fopen("E:\\text.txt","w");

    fputc('A',f);

    fputs("Hellow World!",f);

    fclose(f);
    return 0;
}

根據執行結果可知,檔案寫成功。

接下來,改成以只讀的形式開啟:

int main(void)
{
    char a;
    FILE *f=fopen("E:\\text.txt","r");
    a=fgetc(f);
    fclose(f);
    printf("%c\n", a);
    return 0;
}

只讀成功。讀出字元陣列:

int main(void)
{
    char a;
    char s[15];
    FILE *f=fopen("E:\\text.txt","r");
    a=fgetc(f);
    fgets(s,15,f);//把f中15個讀出寫到s中
    fclose(f);
    printf("%c\n", a);
    printf(s);
    return 0;
}

C語言檔案操作完全攻略 (biancheng.net)

十、指標在嵌入式中的使用

指標讀取ID號:

#include "reg52.h"

void main()
{
  unsigned char *p;
    
    LCD_Init();
    p=(unsigned char *)0xF1;//強制型別轉換,0xF1為ID號儲存地址
    
    LCD_ShowHexNum(2,1,*p,2);
    LCD_ShowHexNum(2,3,*(p+1),2);
    LCD_ShowHexNum(2,5,*(p+2),2);
    LCD_ShowHexNum(2,7,*(p+3),2);
    LCD_ShowHexNum(2,9,*(p+4),2);
    LCD_ShowHexNum(2,11,*(p+5),2);
    LCD_ShowHexNum(2,13,*(p+6),2);

    while(1);
}

也可以這麼寫:

LCD_ShowHexNum(2,1,*((unsigned char *)0xF1),2);//也可以這麼寫,先強制轉換,再取內容

在程式儲存器最後7個位元組單元也存放著ID號,我們試一下在程式儲存器讀取出ID號:由於unsigned char *是訪問內部RAM的,想要訪問程式空間得加一個code:

void main()
{
  unsigned char code*p;
    
    LCD_Init();
    p=(unsigned char code *)0x1FF9;//強制型別轉換,0xF1為ID號儲存地址
    
    LCD_ShowHexNum(2,1,*p,2);//也可以這麼寫,先強制轉換,再取內容
    LCD_ShowHexNum(2,3,*(p+1),2);
    LCD_ShowHexNum(2,5,*(p+2),2);
    LCD_ShowHexNum(2,7,*(p+3),2);
    LCD_ShowHexNum(2,9,*(p+4),2);
    LCD_ShowHexNum(2,11,*(p+5),2);
    LCD_ShowHexNum(2,13,*(p+6),2);

    while(1);
}

十.一、將複雜格式資料轉換為位元組

先用軟體模擬無線模組,構建虛擬無線模組,遵循一個位元組一個位元組的傳送原則:

#include <stdio.h>

/*****************************/
unsigned char AirData[20];

void SendData(const unsigned char *data,unsigned char count)
{
    unsigned char i;
    for(i=0;i<count;i++)
    {
        AirData[i]=data[i];
    }
}
/*****************************/

/*****************************/
void ReceiveData(unsigned char *data,unsigned char count)
{
    unsigned char i;
    for(i=0;i<count;i++)
    {
        data[i]=AirData[i];
    }
}
/*****************************/
int main(void)
{
/******************************/
    unsigned char i;
    unsigned char DataSend[]={0x12,0x34,0x56,0x78};
    SendData(DataSend,4);

    printf("\nAirData=");
    for(i=0;i<20;i++)
    {
        printf("%x ",AirData[i]);
    }
/******************************/
    unsigned char DataReceive[4];
    ReceiveData(DataReceive,4);
    printf("\nAirData=");
    for(i=0;i<4;i++)
    {
        printf("%x ",DataReceive[i]);
    }
/******************************/
    return 0;
}

接下來解決float引數的傳送問題:float本身是四個位元組,所以傳送只要把四個位元組都傳送過去就?了,因為四個位元組,它在儲存float時是編碼後的資料儲存的,所以我們可以把它看成unsigned char型別的四個單元的陣列。

定義一個unsigned char*的變數,讓它等於float的首地址,把四個資料傳送過去,解碼的時候再定義一個float*,讓它等於這個陣列的首地址,那它就會把這四個數當成float進行解碼,解碼出來就是原資料:

#include <stdio.h>

/*****************************/
unsigned char AirData[4];

void SendData(const unsigned char *data,unsigned char count)
{
    unsigned char i;
    for(i=0;i<count;i++)
    {
        AirData[i]=data[i];
    }
}
/*****************************/

/*****************************/
void ReceiveData(unsigned char *data,unsigned char count)
{
    unsigned char i;
    for(i=0;i<count;i++)
    {
        data[i]=AirData[i];
    }
}
/*****************************/
int main(void)
{
/******************************/
    unsigned char i;
    unsigned char DataSend[]={0x12,0x34,0x56,0x78};
    float num=12.345;

    unsigned char *p;

    p=(unsigned char*)&num;

    SendData(p,4);

    printf("AirData=");
    for(i=0;i<4;i++)
    {
        printf("%x ",AirData[i]);
    }
/******************************/
    unsigned char DataReceive[4];
    ReceiveData(DataReceive,4);
    printf("\nAirData=");
    for(i=0;i<4;i++)
    {
        printf("%x ",DataReceive[i]);
    }
/******************************/
    return 0;
}

編碼資料:

接收解碼:

#include <stdio.h>

/*****************************/
unsigned char AirData[4];

void SendData(const unsigned char *data,unsigned char count)
{
    unsigned char i;
    for(i=0;i<count;i++)
    {
        AirData[i]=data[i];
    }
}
/*****************************/

/*****************************/
void ReceiveData(unsigned char *data,unsigned char count)
{
    unsigned char i;
    for(i=0;i<count;i++)
    {
        data[i]=AirData[i];
    }
}
/*****************************/
int main(void)
{
/******************************/
    unsigned char i;
    unsigned char DataSend[]={0x12,0x34,0x56,0x78};
    float num=12.345;

    unsigned char *p;

    p=(unsigned char*)&num;

    SendData(p,4);

    printf("AirData=");
    for(i=0;i<4;i++)
    {
        printf("%x ",AirData[i]);
    }
/******************************/
    unsigned char DataReceive[4];

    float *fp;//接收解碼

    ReceiveData(DataReceive,4);

    fp=(float *)DataReceive;
    printf("\nAirData=");

    printf("\nnum=%f",*fp);

/******************************/
    return 0;
}

完結...... 

 

2022-03-30

20:20:02

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

相關文章