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; }
十、指針在嵌入式中的使用
指針讀取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*)# 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*)# 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