一、計算機儲存
在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語言中還有很多這種傳遞地址的函式,如:
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; }
十、指標在嵌入式中的使用
指標讀取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