
筆記會不定期補充更新數據類型基本類型數據整型整型 -- int 4字節短整型 -- short int 2字節長整型 -- long int 8字節浮點數【實數】單精度浮點數 -- float 4字節雙精度浮點數 -- double 8字節字符char 1字節復合類型數據結構體、 枚舉、共用體變量# includestdio.h int main(void) { int i; i 3; printf(i %d\n,i);//3最終是存放在內存中程序終止后3所占的空間被釋放 return 0; }讓內存條中的一塊空閑單元與所定義變量產生關聯。變量的本質就是內存中的一段存儲空間程序終止后內存空間被釋放。變量必須初始化就是賦值的原因替代內存中原來的垃圾值。單個字符變量要用單引號括起來字符串要用雙引號括起來。字符串不能賦給單個字符變量char。單引號也不能括字符串。printf用法將變量的內容輸出到顯示器上。1.printf字符串;2.printf (輸出控制符輸出參數);3.printf輸出控制符1 輸出控制符2 ......,輸出參數1,輸出參數2.........);4.printf(輸出控制符 非輸出控制符,輸出參數);輸出控制符%d %ld %c %f %lf %x %s輸出控制符可以清楚的表明所輸出的內容所表達的具體含義。scanf用法通過鍵盤將數據輸入到變量中。1.scanf(輸入控制符,輸入變量);將輸入的數據放到輸入變量地址下存放。2.含有非輸出控制符例如逗號、字母等等。非輸入控制符必須原樣輸入。#includestdio.h int main(void) { int i; scanf(m%d,i); //m是非輸出控制符 printf(i %d\n,i); return 0; } /* 輸入123 輸出垃圾值 輸入m123 輸出123 輸入m123n 輸出123 先輸入m時計算機認為合法輸入123也合法輸入n不合法。 */3.應該編寫代碼對用戶的非法輸入做適當處理while ((ch getchar()) ! \n)continue;這段代碼的功能是從標準輸入讀取字符并且忽略這些字符直到遇到換行符為止。流程控制什么是流程控制 程序代碼的執行順序。順序、選擇、循環。順序1. if只能控制一條語句的執行else也只能控制一條語句。所以在執行多條語句時要加{ } 。if...else...為順序執行語句若出現多個條件同時成立則執行最靠前的一條。2. 一個分號代表一條語句的結束。循環for循環# includestdio.h int main(void) { int i; int sum sum i; for(i1;i4;i) //執行完條件判斷語句后執行條件執行語句然后才會執行i sum sum i; printf(sum %d\n,sum); //在for循環結束后才會執行printf()指令 return 0; }1.for循環同樣只能控制一條語句控制多條語句加括號。在for循環結束后才會執行printf()指令。2. 執行完條件判斷語句后執行條件執行語句然后才會執行i。3.強制類型轉換格式(數據類型)(表達式)功能把表達式的值強制轉化為前面所執行的數據類型。4.浮點數存儲帶來的問題float和double都不能保證可以精確的存儲一個小數。舉例有一個浮點型變量x如何判斷x的值是否為零if(x 0) //不一定能判斷正確判斷條件可以寫成if|x-0.000001| 0.000001是else不是while循環1.格式while表達式執行語句2.while同樣只能控制一條語句的執行。若執行語句含有多條語句要注意語句的排列順序其是順序執行的。do.....while:格式do{...........}while(表達式);1.do......while主要用于人機交互,至少執行一遍while和for可能一遍都不執行。選擇switch格式#includestdio.h int main(void) { int val; printf(請輸入樓層); scanf(%d,val); switch (val) { case 1: printf(1層開); break; case 2: printf(1層開); break; default: printf(還沒有); break; } return 0; }1. 在switch中case、default是程序的入口然后順序執行break是程序的出口 。2.所有case后面的常量表達式只能是枚舉常量、數值常量、字符常量、常變量或宏名中的一種注意普通變量和枚舉變量是不能作為case后面的常量表達式使用的。3.switch后邊括號里的表達式可以使任何類型。4.break的用法用在循環中則是用來終止循環。用于switch則是用來終止switch終止距離最近的switch。不能直接用于if除非if屬于循環內部的一個子句。在循環中if后面的break是用來終止循環的不是用來終止if的。在多層循環中break只能終止距離他最近的循環。5.continue的用法#includestdio.h int main(void) { int i; char ch; scanf(%d,i); printf(i %d\n,i); while ((chgetchar()) ! \n) continue; //只有檢測到輸入為換行符時才執行continue之后的指令 int j; //功能可以檢查非法輸入 scanf(%d,j); //例輸入1 da dasd\n 輸出1 da dasd printf(j %d\n,j); //只有在輸入\n后才會輸出正確的輸入字符 return 0; }主要用在循環語句中當執行到continue時跳過在本次循環中continue后面的程序直接去判斷下一次循環條件是否滿足。數組數組的定義int i[5] {1,2,3,4,5}; //定義長度為5的數組完全初始化 int i[5] {1,2,3,}; //未完全初始化未初始化的位為0int i[5] ; //未初始化所有位是垃圾值int i[5] {0}; //清零數組的作用為n個變量連續分配存儲空間所有變量數據類型必須相同。為了解決大量同類型數據的存儲和使用問題 為了模擬現實世界。一維數組一維數組的名稱是變量名其保存數組第一位數據i[0]的地址。只有在定義數組時才能整體賦值其他情況下整體賦值都是錯誤的。二維數組 定義int a[2][3]; //二行三列a[0][0]、a[0][1]....... 二維數組將其內容輸出通過兩個for循環。#includestdio.h int main(void) { int a[3][4] { //初始化數組 {1,2,3,4}, {5,6,7,8}, {9,10,11,12} }; int i,j; /* int a[3][4] {1,2,3,4,5,6,7,8,9,10,11,12}; //初始化數組 */ //輸出數組內容 for(i0;i3;i) { for(j0;j4;j) printf(%-5d\n,a[i][j]); //5表示每個字符占五個位置-號表示左對齊 printf(\n); } return 0; }多維數組多維數組不存在多維數組因為內存是線性一維的n維數組可以當作每個元素是n-1維數組的一維數組。注把數組a中的值全部賦給數組b正確寫法;for(i0;i5;i)b[i] a[i];補充:sizeof(arr)arr雖然數組名保存的是數組首元素的地址但上述兩種操作判斷的是整個數組的大小、取的是整個數組的地址而不是首元素的大小、首元素的地址對于數組前對于數組類型的定義定義的是數組中每個元素的類型同樣定義了每個元素的大小int (*p)[10] arrp是數組指針也叫 “指向數組的指針”p保存的是數組的地址寫[10]是為了明確指針指向的是有10個元素的數組由于arr是數組地址如果寫 int *p arr 會報 類型不匹配函數指針數組/* Add,Mul,Sub,Div是四個函數 */int (*pf)(int,int) Add; //pf是函數指針int (*arr[4])(int,int) {Add,Mul,Sub,Div}; //arr是函數指針數組函數指針數組的使用ret arr[input](x,y); //對于功能繁多的情況使用case語句繁雜其不易維護就可以使用函數指針數組掉用不同的功能柔性數組在結構體的最后一個成員時未明確大小的數組struct S { int n; int arr[]; } int main() { /* 柔性數組的使用 */ struct S* ps (struct S*)malloc(sizeof(struct S) 40); //結構體的大小只有4字節 ps-n 100; int i 0; for(i 0; i 10; i) { ps-arr[i] i; } for(i 0; i 10; i) { printf(%d,ps-arr[i]); } struct S* ptr (struct S)relloc(ps,sizeof(struct S) 80); if(ptr ! NULL) ( ps ptr; //此時ps與ptr都指向relloc分配的空間地址 ptr NULL; //賦值為空防止誤操作ptr使得ps變成野指針 ) //釋放 free(ps); ps NULL; return 0; }函數函數定義void max(int i, int j)}函數的執行體 {void表示函數沒有返回值max是函數名稱括號()內是形參列表 。int f(void) //void表示函數f不能接收數據int是函數返回值的類型。 { return 10; //向主調函數返回10 } void g(void) //函數名前面的void表示函數沒有返回值 { //return 10; //如果寫了返回值就會報錯 }注在使用函數時會為形參變量分配空間當函數執行完后該空間會被釋放在下一次使用該函數時會為形參變量分配新的存儲空間且分配的不一定是同一個空間。但程序是從main函數進入的也是從main函數退出的。函數返回值的類型也稱為函數的類型因為如果函數名前的返回值類型與函數執行體中的return 表達式中表達式的類型不同的話則最終函數返回值的類型以函數名前的返回值類型為準。實參與形參形參是函數定義時的參數用于接收實參的值在函數內部使用。實參是函數調用時傳遞的實際值用于初始化形參需要確切的數據。函數的形參函數的形參在函數執行結束后就被銷毀了return使用格式return 表達式;作用函數中的return是用來終止被調函數并向主調函數返回表達式的值如果表達式為空則只終止函數不向被調函數返回任何值。與break是用來終止循環不同。需要在main函數中調用的函數要寫在main函數的前面順序執行。main函數下可以調用其他函數但其他函數不能調用main函數可在main函數前添加函數聲明就能將函數寫在main函數的后面。函數聲明格式void f(void); //函數聲明是一個語句要加分號函數的功能設計要單一才能提高復用率而不是設計的十分復雜。函數中傳遞一維數組參數需要傳遞數組名稱和元素個數。但字符數組不需要傳元素個數。//數組傳參 //函數傳遞數組求和 #include stdio.h int array_sum(int * data,int n); int main(int argc,char *argv[]) { int a[] {5,9,10,3,10}; int sum 0; sum array_sum(a,sizeof(a)/sizeof(int));//傳遞數組就必須要傳起始地址a和元素個數n reyurn 0; } int array_sum(int * data,int n)//int * data a;就是一個指針接收了數組名 { int ret 0; int i; for(i0;in;i) ret data[i]; return ret; }//字符數組傳參 //函數字符數組傳參不需要傳元素個數因為字符數組有\0判斷結束 includestido.h int main(int argc,char *argv[]) { char s[] h a sdg k; puts(s); del_space(s); puts(s); return 0; } void del_space(char * s1) { char * s2; s2 s1; //一個指針用來處理一個指針用來賦值 while (*s1){ if(*s1 ){ s1; } else{ *s2 *s1; s1; s2; } *s2 \0;//補充\0標志字符串結束 } }指針函數指針函數是指一個函數的返回值為地址量的函數。結構數據類型*函數名稱(參數說明){語句序列;}錯誤示例#includestdio.h #includestring.h char * mystring(){ char str[20]; strcpy(str,hello); return str; //返回值是指針 } int main(void) { printf(%s\n,mystring()); //打印函數返回值 return 0; }由于函數執行完成后對應內存已經釋放所以編譯錯誤。只有當返回值為全局變量的地址、static變量地址、字符串常量的地址或使用malloc函數申請存儲空間。遞歸遞歸函數指一個函數體中直接或間接調用了該函數本身。//階乘計算 #includestdio.h int fac(int n); int main(int argc,char *argv[]) { int n; printf(input); scanf(%d,n); return 0; } int fac(int n) { if(n 0 || n 1) return 1; return n * fac(n-1);//在函數中調用了該函數 }棧一個端口既是出口也是入口。補充對于函數參數的設計函數參數的設計取決于要傳入的是什么就是說形參與實參的類型要匹配假設要傳入一個一級指針那就需要將參數定義為一級指針類型函數指針指向函數的指針數組指針指向數組的指針指針數組數組成員是指針的數組回調函數通過函數指針在適當是調用他所指向的函數變量函數內定義的變量是局部變量所有函數外定義的變量是全局變量。在一個函數內部如果定義的局部變量的名字和全局變量名一樣時局部變量會屏蔽掉全局變量。static變量1. 存儲位置固定靜態變量存放在程序的數據段已初始化的靜態變量或者 BSS 段未初始化的靜態變量并非棧或堆。這使得它的內存地址在程序運行期間保持不變。2. 延長生命周期靜態變量的生命周期和程序的運行周期一樣長。就算定義它的函數執行完畢其值也不會被銷毀下次調用該函數時靜態變量會使用上次保存的值。3. 僅初始化一次靜態變量在程序加載時就完成初始化而非每次函數調用時都初始化。如果沒有顯式初始化它會被自動初始化為 0對于數值類型或者 NULL對于指針類型。指針1指針的含義#include stdio.h int main(void) { int *p; int i; p i; /* p保存了i的地址因此p指向i p不是ii也不是p更準確的說修改p的值不會影響i修改i的值也不會影響p */ return 0; }指針的本質是操作受限的非負整數不能進行加、減、乘、除等操作。p是變量的名字int *表示p變量存放的是int類型變量的地址。也就是說只有整形變量的地址才能存入變量p。p是變量名p變量的數據類型是int*類型而*p是int類型也就是指針所指向的內容的數據類型是整型。*p 就是以p的內容為地址的變量*p和取地址是逆運算。指針就是地址地址就是指針指針變量是存放地址的變量。如果一個指針變量指向了某個普通變量則*指針變量就完全等同于普通變量。地址就是內存單元的編號。p指的是所指變量的地址*p指的是該地址下的內容。匯編中的的概念給地址加括號表示該地址的內容。例DPTR 1000H,DPTR 1230H 變量p存放的是地址*p存放的是該地址下的數據。在 C 語言里聲明指針時不會自動為其分配內存地址。要是直接使用未初始化的指針它的值是隨機的這就會讓指針指向內存中一個不確定的位置。在其他函數中定義的二級指針能跨函數修改變量的值是因為二級指針存放的是地址而不是變量內容 。/* 數字交換程序 */ includestdio.h void exchange(int *p,int * q) { int t; //注意t必須定義為int類型 t *p; *p *q; *q t; } int main(void) { int a 3; int b 4; exchange(a,b); //exchang*p*q是錯誤的exchangeab也是錯誤的 printf(a %d, b %d/n,a,b); reeturn 0; }void類型指針未定義指針類型在使用時需要強制類型轉換。const修飾指針情況一const 數據類型*指針變量名稱[指針運算表達式];常量化指針目標是限制通過指針改變其目標的數值但指針變量存儲的地址值可以修改。情況二數據類型*const指針變量[指針運算表達式];使得指針變量存儲的地址值不能修改。但是可以通過*指針變量名稱可以修改指針所指向的變量的數值。#includestdio.h int main(int argc,const char argv[]) { int m 10; const int * p; //情況一 int * const q m; //情況二 const int * const r m; p m; //right //(*p); //error //q m; //error *q 20; //right r; //error (*r); //error return 0; }*號的用法乘法、定義指針、指針運算符a *p*p是以p的內容為地址的變量。通過指針在被調函數中修改主調函數中的值例 *形參名 ........2指針常見的錯誤# include stdio.h int main(void) { int i 5; int *p; int *q; p i; *q p; //錯誤1類型不匹配 錯誤2沒有對q初始化 *q *p; //錯誤q未初始化 p q; //q是垃圾值q賦給pp也變成了垃圾值 /* 在 C 語言里聲明指針時不會自動為其分配內存地址。要是直接使用未初始化的指 針它的值是隨機的這就會讓指針指向內存中一個不確定的位置。 本程序無法讀取*q的內容得到原因指針q沒有被初始化它指向的是一個隨機的內 存地址直接對其進行解引用會導致未定義行為。 */ printf(%d\n,*q); return 0; }3指針和數組指針和一維數組一維數組名是一個指針常量不能做賦值操作存放的是第一個元素的地址。確定一個一維數組只需要數組第一個元素的地址和數組長度。#include stdio.h void f(int * pArr, int len) { pArr[3] 88; //改變的是數組第四個元素的值 } int main(void) { int a[6] {1,2,3,4,5,6,}; printf(%d\n,a[3]) f(a,6) //將數組a的首地址傳遞給函數f確定一個數 //組或對數組進行操作只需要數組的首地址和數組長度 printf(%d/n,a[3]) return 0; } 輸出結果 --------------------------------- 4 88 ---------------------------------下標和指針的關系如果p是一個指針變量則 p[i] 永遠等價于 *p i。*p i的含義取出pi地址中的內容。指針變量運算指針變量不能相加、相乘、相除。只能相減 如果兩個指針變量指向的是同一塊連續空間中不同的存儲單元則這兩個指針變量才能相減。 例如一個數組中不同元素的地址。一個變量的地址使用該變量首字節的地址表示。無論他指向的變量占幾個字節每個指針變量只占用四個字節。因為無論指針變量保存的地址有多大都需要32位地址線來表示(324*8)。對于硬件地址每一個字節一個編號。例如double類型的變量i占8個字節那么i保存的是第一個字節的地址。一個變量的地址使用該變量首字節的地址表示4動態內存分配靜態數組的缺點1.數組的長度必須提前制定且定義的長度只能是常數。int a[5]; //okint len 5; int a[len]; //error2.傳統形式定義的數組該數組的內存程序員無法手動釋放。函數中定義的數組只有在函數運行完之后系統分配個數組的內存空間才會被釋放。3.數組的長度不能在函數運行的過程中動態的擴充或縮小。4.A函數定義的數組在A函數運行期間可以被其他函數使用但A函數運行結束之后由于數組A的內存已經釋放了所有A函數中的數組就不能被其他函數使用了。為什么需要動態分配內存原因就是解決了上面的四個缺陷。//動態分配函數malloc的用法1 #include stdio.h #include malloc.h int main(void) { int i 5; //i是靜態分配的 int * p (int*)malloc(4); //a行需對函數返回值強制類型轉換才能確定有幾個變量由于 //int類型變量占4個字節所有該行動態分配了1個變量 /* 1.要使用malloc這個函數要添加對應頭文件 2.malloc函數只有一個形參并且形參是整數 3.4表示請求系統為本程序分配4個字節 4.malloc函數只能返回第一個字節的地址 5.a行分配了8個字節p變量占4個字節p所指向的內存也占4個字節 6.p本身所占的內存是靜態分配的p所指向的內存是動態分配的 */ free(p);//由程序員手動釋放掉p所指向的動態內存 //而p所占用的內存只能在所在函數運行終止時由系統釋放 return 0; }/* malloc函數用法2:在一個函數中動態分配的內存在另一個函數中進行處理 */ # include stdio.h # include malloc.h void f(int * q) { *q 200; } int main(void) { int * p (int*)malloc(sizeof(int)); *p 10; printf(%d\n,*p); f(p); printf(%d,*p); return 0; }動態數組的構造#include stdio.h #include malloc.h int main(void) { int len; int *pArr; int i; //動態構造一維數組 printf(請輸入你要存放的元素的個數:) scanf%dlen pArr int*malloc4 * len //數組的每個元素都是int類型。 //也可以寫成pArr[i]的形式使用 //對一維數組進行操作 for(i0;ilen;i) scanf(%d\n,pArr[i]); //對一維數組進行輸出 printf(一維數組輸出的內容時\n) for(i0;ilen;i) printf(%d\n,pArr[i]) free(pArr);//釋放掉動態分配的數組 return 0; }靜態內存與動態內存的區別補充malloc和freevoid* malloc(size_t size);1.如果開辟成功則返回一個指向開辟空間的指針2.如果開辟失敗則返回一個NULL指針因此返回值一定要檢查; if(p NULL){}3.返回值的類型是void*所以malloc函數并不知道開辟空間的類型具體在使用的時候使用者自己決定強制類型轉換4.如果size為0malloc的行為是標準的是未定義的取決于編譯器內存開辟int* p malloc(40); //在堆區開辟40字節大小的內存內存釋放free(p);p NULL; //釋放內存后p仍然保存了原地址為防止內存泄露為p賦值NULLcalloc函數void* calloc(size_t num, size_t size);1.num是創建元素個數 size每個元素的大小2.相比于malloccalloc會將開辟的每個字節都初始化為0relloc函數void* relloc(void* ptr, size_t size);1.ptr是要調整的內存地址size是調整后的新大小2.返回值為調整之后的內存的起始地址3.調整之后的內存空間可能是在原來內存空間的基礎上也可能是開辟了一塊新的空間擴充空間的兩種情況1原空間后有足夠大的空間滿足擴充條件則由原空間加擴充的部分構成2原空間后空間不足就會開辟一塊新的空間能夠容納擴充后的大小動態內存分配中的常見錯誤1.對于創建的動態內存要檢驗其返回值是否為空再對其進行解引用操作2.不能對動態分配的空間越界訪問3.對非動態開辟的空間使用free釋放4.由于程序將堆區地址跑丟了使用free釋放一塊動態開辟內存的一部分可以定義中間變量保存動態內存的起始地址在釋放時傳中間變量給free()或者寫成 p[i]*(pi) p是動態內存分配函數的返回值5.對同一塊動態內存多次free因為在第一次free后函數的返回值依然記著所釋放內存的地址所以要在ferr后對返回值賦值NULL6.動態開辟的內存空間忘記釋放內存泄露開辟了空間但是不使用導致該進程中該地址空間不能被使用只有進程結束后才會被系統回收相當于內存泄露了比如1在開辟內存命令和釋放內存命令之間存在return命令可能出現沒有機會執行free命令的情況2內存開辟在一個函數中進行但釋放命令交給了其他函數忘記寫釋放命令/* 程序崩潰 */ void GetMemory(char* p) { p (char*)malloc(100); //存在內存泄露p是形參函數結束后就被銷毀了所以p指向的動態分配空間在該函數執行完成后就找不到了 } void Test(void) { char* str NULL; GetMemory(str); strcpy(str, hello world); //str依然是NULL解引用時崩潰 printf(str); //且該程序沒有free過程也存在問題 } int main() { Test(); return 0; }(5)跨函數使用內存問題靜態內存不能跨函數使用//靜態變量不能跨函數使用 #include stdio.h void f(int ** q) { int i 5; *q i; } int main(void) { int *p; f(p); printf(%d\n,*p);//該行語法沒問題但是邏輯有問題p可以保存i的地址但不能訪問i的空間 //因為在f函數執行完后i的空間就被釋放了不能讀也不能寫 return 0; }動態內存可以跨函數使用/* 動態內存分配 靜態內存在棧里面分配動態內存在堆里面分配 */ #include stdio.h #include malloc.h void f(int * q) { *q (int *)malloc(sizeof(int)); **q 5; //等價于pint*mallocsizeofint } int main(void) { int * p; f(p); printf(%d\n,*p);//由于在f函數中沒有手動釋放內存命令 //所有該內存在執行完f函數后任然存在所有該行語句正確 return 0; }定義的靜態變量在其內存被釋放之后就不能再在其他函數中被調用了。不直接寫數字是為了在不同設備上定義正確的字節數。函數sizeof返回變量類型的字節數。動態分配是在堆中分配地址。而靜態分配是在棧中分配地址運行結束后就會出棧就釋放了所有就不能跨函數調用。malloc函數就是通過定義二級指針的方式實現跨函數修改變量的目的。二級指針的核心作用是允許函數修改一級指針的值。如果只是用一級指針指向另一個一級指針類型不匹配而且無法實現修改實參指針這樣的功能。…………………………………………………………………………………………第二段代碼里不是“普通變量能在main里賦值”——普通變量 p 、 q 只是“持地址的工具人”真正被跨函數賦值、修改的是 malloc 申請的堆內存它不受函數生命周期限制只要有有效地址就能操作。普通變量棧區本身依然不能跨函數賦值比如 f 里改 q 的值 main 里的 p 不會變。指針指向的內存“無效”要么被釋放、要么根本沒分配過此時通過指針操作內存讀/寫就是非法的野指針操作。核心答案一句話說透內存是否釋放和“哪個函數分配”無關只看“內存存放在棧區還是堆區”——棧區內存隨函數結束自動釋放堆區內存必須手動free才釋放。一、先明確兩個關鍵前提嵌入式開發必記1. C語言中局部變量的“存放區域”由 定義方式 決定不是“哪個函數定義”決定- 普通局部變量比如 int i 5; 默認存 棧區函數專屬內存輕量、自動管理。- malloc 申請的內存比如 int *p (int*)malloc(...) 存 堆區全局共享內存重量級、手動管理。2. 函數調用的本質只是“執行代碼邏輯”不會改變內存的“區域屬性”——棧區內存不會因為在其他函數中被訪問就變成堆區堆區內存也不會因為函數結束就自動變成棧區。二、兩段代碼的內存差異拆解對應你的疑問我們分別看“f函數中的i”和“malloc申請的內存”的生命周期1. 第一段代碼f函數里的 int i 5; - i 的定義 f 函數內的 普通局部變量 → 存棧區。- 內存生命周期- 進入 f 函數時系統在棧區給 i 分配空間存5- 退出 f 函數時棧區的核心規則是“函數結束自動回收內部所有局部變量的內存”——不管 main 函數的指針 p 是否還存著 i 的地址 i 的棧內存已經被系統標記為“可復用”后續訪問就是非法的“野指針操作”。- 關鍵 i 是棧區變量所以 f 函數結束就釋放和“有沒有被其他函數的指針指向”無關。2. 第二段代碼main里 malloc 的內存- 內存的定義 main 函數中用 malloc 申請 → 存堆區。- 內存生命周期- malloc 執行時系統在堆區分配一塊 int 大小的空間返回地址給 p - 調用 f(p) 時只是把“堆區地址”傳給 f 的 q q 和 p 指向 同一塊堆內存—— f 函數里的 *q 200 本質是修改“堆區內存的值”不是修改“內存的存放區域”- 退出 f 函數時 f 的局部變量 q 棧區指針會被回收但它指向的 堆內存沒動堆區內存的回收只認 free 不認函數結束- 回到 main 后 p 依然指向這塊堆內存所以能正常訪問修改后的值200直到手動 free(p) 才釋放。- 關鍵 malloc 的內存是堆區變量所以 f 函數結束不會釋放和“在main里分配、f里訪問”無關。三、一句話總結核心區別簡單說棧區內存“跟函數走”函數沒了內存就沒堆區內存“跟程序員走”你不釋放它就一直在——這也是嵌入式開發中處理緩沖區、數據結構時優先用 malloc 需注意內存泄漏的原因。…………………………………………………………………………………………也就是說無論是在哪里定義的什么類型的變量操作邏輯都是針對內存空間來說的只需要看內存空間的地址和地址下所存儲的內容。變量只是內存空間的“別名”或“標識”所有對變量的操作本質都是通過“變量關聯的內存地址”去讀寫這塊地址里的內容變量是給人看的“標識”內存地址和內容才是給計算機執行的“本質”——所有代碼邏輯最終都會落地到“哪個地址、存什么內容”的操作上。6函數指針定義數據類型*函數指針名稱[大小](說明列表)其中大小是指函數指針數組元素的個數#includestdio.h //生成函數 int add(int a,int b) { return ab; } int main(int argc, char *argv[]) { int m 10,n 20; int (*p)(int,int); //函數聲明 p[0] add; //指針p指向函數add printf(%d\n,(*p)(m,n)); //函數調用 //p[1] sub; return 0; }補充定義指針的類型是指指針所指向的內容的類型比如char *p指的是p指向的內容是char類型函數指針指向函數的指針數組指針指向數組的指針指針數組數組成員是指針的數組函數指針在代碼重復的部分其中只有一個函數不一樣就可以采取封裝一個函數這個函數的形參是函數指針類型指向冗余代碼中的不同函數無具體類型的指針void* pa a;/* void*表示無具體類型的指針可以接受任意類型的地址由于其是無具體類型 的指針所有不能解引用通過指針變量訪問其指向的內存地址中存儲的值操作也不能-整數需要強制類型轉換后在操作 */指針的初始化對于未初始化的指針定義該指針可能指向任何位置可能是程序的其他數據、代碼區甚至是受保護的內存此時解引用會導致程序崩潰結構體定義賦值結構體#include stdio.h struct student //這只是定義了一個數據類型并沒有定義變量 { int age; float score; char name; } //結構體定義賦值 int main(void) { //方式一 struct student st {15, 84, JIM}; //st是變量名定義的同時賦初值。 struct student st2; st2.age 15; //賦值 st2.score 84; st2.name JIM; //方式二 struct student *pst st; //將普通變量st的地址保存到pst pst-age 15; //賦值 return 0; }pst - age 88.8f; //方式二/指針變量名- 成員名 在計算機內部會被轉化成(*pst).age也等價于st.age。88.8默認為double類型在末尾加f可轉換成float類型且浮點數不能準確存儲。通過函數讀結構體進行輸入和輸出int main(void) { struct Student st; //st為struct Student類型變量名用來保存該結構體的數據 InputStudent(st); //對結構體變量輸入必須發送st的地址 OutputStudent(st); //對結構體變量輸出可以發送st的地址也可以發送st的內容 // OutputStudent(st); return 0; } //輸出函數參數名為ss void OutputStudent(struct Student ss) { printf(%d %c %s\n , ss.age, ss.sex, ss.name); } //輸入函數參數名為pstu void InputStudent(struct Student * pstu) //pstu只占四個字節,*前面的是指針的 //類型用來確定該指針占多少字節 { (*pstu).age 10; //等于st.age strcpy(pstu-name,張三); pstu-sex F; }雖然發送地址可能會造成內容誤改寫但是如果發送的是內容占用的內存太大而如果發送地址占用內存少執行速度快。所以推薦發送地址。結構體變量的運算:結構體變量不能相加不能相減也不能相乘除只能相互賦值。補充結構體對齊:對齊規則1.第一個結構體成員在與結構體變量偏移為0的地址處2.其他成員要對齊到該成員數據類型大小的整數倍地址處對齊數 編譯器默認對齊數與該成員數據類型大小相比中的較小值3.結構體的總大小為最大對齊數的整數倍4.如果嵌套了結構體嵌套的結構體對齊到自己最大對齊數的整數倍外層結構體的大小依然是最大對齊數的整數倍含嵌套結構體的對齊數原因1.方便不同硬件平臺訪問數據讀/寫2.提高訪問數據的速度用空間換時間對齊只需要訪問一次但是不對齊可能出現一次訪問沒有訪問到全部的數據對策讓占用小空間的成員經量集中在一塊結構體傳參要傳結構體地址減小在傳值時壓棧的開銷導致的性能下降枚舉把一個事物的所有取值的列舉出來。//這只是定義了一個數據類型而不是一個變量,數據類型的名字是enum WeekDay enum WeekDay { monday,tuesday,wednesday,........,sunday }; int main(void) { enum WeekDay day wednesday; //day是變量等號后面只能寫類型 //中的元素不能直接寫數字 printf(%d\n,day); //輸出結果為2 return 0; } //本函數的目的只是期望接受0-6之間的數字將形參定義為枚舉類型 void f(enum weekday i) { switch (i) { case 0:printf(monday\n); break; case 1:printf(tuesday\n); break; case 2:printf(wednesday\n); break; case 3:printf(thursday\n); break; .... } }定義為枚舉類型可以保證傳輸的都是有效信息使代碼更安全。聯合體成員共用同一段內存空間那么聯合體的大小至少是最大成員的大小定義union un{char b,int c};//該聯合體中成員b、c的存儲起始地址是相同的所以也會出現數據覆蓋的情況補碼計算機內的數據以補碼的形式存儲。二進制轉十進制先確定依據最高位確定正負若為負數將所有位取反再加1最后添加負號。十進制轉二進制正數的補碼:為其二進制本身空余的位數補0。負數的補碼:其絕對值 的二進制取反加1空余的位數全部補1。要補夠空余位數因為系統默認補0。例-3的補碼是0xFFFFFFFDint類型變量所存儲的最大正數用十六進制表示0x7FFF_FFFFint類型變量所存儲的絕對值最大負數用十六進制表示0x8000_0000缺少符號位發生溢出ch中只保存了1個字節的數據 char ch 0x80;//字符型變量占一個字節 ch 129; //129占4個字節 printf(%d\n,ch); //輸出結果為-127 0000_0000 0 0000_0001 1 ......... ... 0111_1111 127 1000_0000 -128 1000_0001 -127 ......... ... 1111_1111 -1鏈表鏈表的建立用結構體和函數寫鏈表先構造一個頭結點再依次寫所需的節點個數通過malloc函數構造節點再將所構造的節點插入到頭結點的后邊返回了頭結點的地址 。一個節點包括數據域和指針域兩部分。#includestdio.h #includemalloc.h #inncludestdlib.h //定義了一個鏈表節點的數據類型 struct Node { int data;//數據域 struct Node * pNext;//指針域遞歸 }; //函數聲明 struct Node * create_list(void); void traverse_list(struct Node *); int main(void) { struct Node * pHead NULL;//創建了一個struct Node類型的指針用來存放頭節點的地址 pHead createlist();//CreateList()功能創建一個非循環單鏈表并將頭節點地址作為函數返回值 traverse_list(pHead);//遍歷鏈表元素 return 0; } //非循環單鏈表函數 struct Node * creat_list(void) { int len;//用來存放有效節點個數 int i; int val;//用來臨時存放用戶輸入的節點的值 //分配一個不存放有效數據的頭節點 struct Node * pHead (struct Node *)malloc(sizeof(struct Node)); if(NULL pHead) { printf(分配失敗程序終止\n); exit(-1); } struct Node * pTail pHead; pTail-pHead NULL; printf(請輸入您要生成鏈表節點的個數len ); scanf(%d,len); for(i0;ilen;i) { printf(請輸入第%d個節點的值i1); scanf(%d,val); struct Node * pNew (struct Node *)malloc(sizeof(struct Node)); if(pNew NULL) { printf(分配失敗程序終止\n); exit(-1); } pNew-data val; pTail-pNext pNew; pNew-pNext NULL; pTail pNew; } return pHead;//返回值為頭節點的地址 //遍歷函數 void traverse_list(struct Node * pHead) { struct Node * p pHead-pNext; while(NULL ! p) //鏈表不為空則執行循環 { printf(d\n,p-data); p p-pNext; } return; } }首節點存放第一個有效數據的節點。尾節點存放最后一個有效數據的節點。頭結點頭結點和首節點的數據類型一模一樣頭結點是首節點前面那個節點頭結點并不存放有效數據。設置頭結點的目的是為了方便對鏈表進行操作。頭指針存放頭結點地址的指針變量。確定一個鏈表只需要一個參數頭指針。算法通俗定義解題的方法和步驟。狹義定義對存儲數據的操作。對不同存儲結構要完成一個功能所執行的操作是不一樣的。例如輸出數組中所有的元素的操作與輸出鏈表中所有元素的操作肯定是不一樣的。這說明算法是依附于存儲結構的不同的存儲結構所執行的算法是不一樣的。廣義定義也叫泛型。無論數據是如何存儲的對該數據的操作都是一樣的。數組優點存取速度快缺點需要一個連續的很大的內存空間插入和刪除元素的效率很低。鏈表查找某個元素的效率低而鏈表不需要連續的空間來存儲數據每一個元素的空間存儲數據和下一個元素的地址。靠指針找到下一個元素。NULL的含義1.數值0。2.字符串結束標記符。3.空指針NULL,表示的是編號位零的地址。 至于其到底是什么含義取決于解碼方式。計算機規定了以零為編號的存儲單元內容不可讀也不可寫。free (p);p NULL;*p 某值;對于被釋放的變量p要沒有給p寫入NULL就可能出現任然可以對*p賦值的現象。但如果寫如NULL計算機不允許對編號為零的存儲單元進行讀寫操作就避免了前面問題的發生。宏定義含參宏定義#define ADD(a,b) ((a)(b)) int result ADD(3,4);1.括號的使用要給每個參數和整個表達式都加上括號以此避免宏展開后出現運算優先級方面的問題。2.參數為表達式時可能出現副作用當參數是帶有副作用的表達式像自增、自減運算時宏展開后可能會產生多次求值的情況。#define MAX(a,b) ((a) (b) ? (a) : (b)) int x 5; int y MAX(x,10)// 宏展開后是 ((x) (10) ? (x) : (10))x 會被遞增兩次3.字符串化操作符#借助#操作符可以把參數轉換為字符串字面量。#define PRINT(x) printf(#x %d,x) PRINT(10); // 會被展開成 printf(10 %d\n, 10); 也就是 printf(10 %d\n, 10);4.標記粘貼操作符##使用##操作符能夠把兩個參數連接成一個標識符。#define CONCAT(a, b) a##b int xy 10; int result CONCAT(x, y); // 會被展開成 int result xy;補充assert的作用assert斷言是編程中用于調試階段驗證關鍵條件 / 假設的工具核心作用是「自我檢查」—— 確保程序運行到某一位置時預期的條件必須成立若不成立程序會主動觸發錯誤斷言失敗幫助開發者快速定位問題而非等到錯誤擴散后才暴露。const的作用語義是只讀基礎類型的值直接存在內存棧中const 會鎖定這個值的內存地址無法通過變量重新賦值修改會直接報錯