C語言指針學習

一、電腦存儲

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

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

二、指針的表示

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

電腦幾位作業系統指的是一次性能處理幾位的數據,由上圖可知,64位作業系統,一個指針佔用8個位元組的空間。

三、指針操作

 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語言中還有很多這種傳遞地址的函數,如:,函數,看它下面的解釋,也是用的const類型。

七、指針的應用——函數多返回值

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

 

 

 …

 

 …

 

 

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

首先劃定了MAX的空間4個位元組,然後調用子函數的話,定義了max的空間8個位元組,把&MAX傳入,那麼max中存的就是MAX的首地址。接著在子函數中給*max寫入數據,相當於在MAX空間寫數據,將MAX的值更改,實現值的返回。當子函數返回之後,max被銷毀。由於參數可以隨便多,所以返回值可以隨便多。同樣,在標準C語言庫中,我們也可以找到類似的函數:,結合下面的解釋可以知道,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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Tags: