1. c文件執行過程
C語言程序的執行過程可以分為四個基本步驟:預處理、編譯、匯編和鏈接。下面是這些步驟的簡要概述:
預處理:在這個步驟中,預處理器將源代碼中以 # 開頭的指令進行處理,例如 #include 和 #define。預處理器還可以執行條件編譯,將一些代碼包含或排除在編譯過程中。
編譯:在這個步驟中,編譯器將經過預處理的源代碼轉換成匯編語言。編譯器會進行詞法分析、語法分析和語義分析,生成相應的中間代碼。
匯編:在這個步驟中,匯編器將中間代碼轉換成可執行的機器碼。匯編器會將每條匯編指令翻譯成一條或多條機器指令,并生成目標文件。
鏈接:在這個步驟中,鏈接器將目標文件和庫文件組合在一起,生成可執行文件。鏈接器會解決符號引用問題,將代碼和數據段放置在內存中的正確位置,并生成可執行文件的元數據(如入口地址和符號表)。
一旦完成了這些步驟,可執行文件就可以被操作系統加載和執行了。操作系統會將程序加載到內存中,并將控制權轉移到程序的入口點。程序開始執行,按照代碼的邏輯執行指令,直到程序結束或者遇到異常錯誤。
2.Void*
類型為 void * 的指針代表對象的地址,而不是類型。例如,內存分配函數 void *malloc( size_t size ); 返回指向 void 的指針,可以轉換為任何數據類型。
3.C 中的變量聲明
變量聲明向編譯器保證變量以指定的類型和名稱存在,這樣編譯器在不需要知道變量完整細節的情況下也能繼續進一步的編譯。變量聲明只在編譯時有它的意義,在程序連接時編譯器需要實際的變量聲明。
變量的聲明有兩種情況:
? 1、一種是需要建立存儲空間的。例如:int a 在聲明的時候就已經建立了存儲空間。
? 2、另一種是不需要建立存儲空間的,通過使用extern關鍵字聲明變量名而不定義它。 例如:extern int a 其中變量 a 可以在別的文件中定義的。
? 除非有extern關鍵字,否則都是變量的定義。
extern int i; //聲明,不是定義
int i; //聲明,也是定義
不帶初始化的定義:帶有靜態存儲持續時間的變量會被隱式初始化為 NULL(所有字節的值都是 0),其他所有變量的初始值是未定義的,最好初始化。
4.C 中的左值(Lvalues)和右值(Rvalues)
C 中有兩種類型的表達式:
- 左值(lvalue):指向內存位置的表達式被稱為左值(lvalue)表達式。左值可以出現在賦值號的左邊或右邊。
- 右值(rvalue):術語右值(rvalue)指的是存儲在內存中某些地址的數值。右值是不能對其進行賦值的表達式,也就是說,右值可以出現在賦值號的右邊,但不能出現在賦值號的左邊。
4.常量
4.1 整數常量
- 整數常量可以是十進制、八進制或十六進制的常量。前綴指定基數:0x 或 0X 表示十六進制,0 表示八進制,不帶前綴則默認表示十進制。
- 整數常量也可以帶一個后綴,后綴是 U 和 L 的組合,U 表示無符號整數(unsigned),L 表示長整數(long)。后綴可以是大寫,也可以是小寫,U 和 L 的順序任意。
- 下面列舉幾個整數常量的實例:
- 212 /* 合法的 */
- 215u /* 合法的 */
- 0xFeeL /* 合法的 */
- 078 /* 非法的:8 不是八進制的數字 */
- 032UU /* 非法的:不能重復后綴 */
4.2浮點數常量
可以帶有一個后綴表示數據類型,例如:
11. float myFloat = 3.14f;
double myDouble = 3.14159;
5. C存儲類
5.1 auto
auto 存儲類是所有局部變量默認的存儲類。
定義在函數中的變量默認為 auto 存儲類,這意味著它們在函數開始時被創建,在函數結束時被銷毀。
int mount;
auto int month;
上面的實例定義了兩個帶有相同存儲類的變量,auto 只能用在函數內,即 auto 只能修飾局部變量。
5.2 register 存儲類
register 存儲類用于定義存儲在寄存器中而不是 RAM 中的局部變量。這意味著變量的最大尺寸等于寄存器的大小(通常是一個字),且不能對它應用一元的 ‘&’ 運算符(因為它沒有內存位置)。
register 存儲類定義存儲在寄存器,所以變量的訪問速度更快,但是它不能直接取地址,因為它不是存儲在 RAM 中的。在需要頻繁訪問的變量上使用 register 存儲類可以提高程序的運行速度。
register int miles;
寄存器只用于需要快速訪問的變量,比如計數器。還應注意的是,定義 ‘register’ 并不意味著變量將被存儲在寄存器中,它意味著變量可能存儲在寄存器中,這取決于硬件和實現的限制。
5.3 static 存儲類
static 存儲類指示編譯器在程序的生命周期內保持局部變量的存在,,而不需要在每次它進入和離開作用域時進行創建和銷毀。若在函數內部定義,外部不可訪問
靜態變量在程序中只被初始化一次,即使函數被調用多次,該變量的值也不會重置。
void func1(void)
{
/* ‘thingy’ 是 ‘func1’ 的局部變量 - 只初始化一次
- 每次調用函數 ‘func1’ ‘thingy’ 值不會被重置。
*/
static int thingy=5;
thingy++;
printf(" thingy 為 %d , count 為 %d\n", thingy, count);
}
輸出:thingy 6 7 8 9
5.4 extern 存儲類
extern 存儲類用于定義在其他文件中聲明的全局變量或函數。當使用 extern 關鍵字時,不會為變量分配任何存儲空間,而只是指示編譯器該變量在其他文件中定義。
6.運算符
a++: 先賦值后運算 ++a:先運算后賦值
7.判斷語句switch 與無限循環
1.switch:
當遇到 break 語句時,switch 終止,控制流將跳轉到 switch 語句后的下一行。
不是每一個 case 都需要包含 break。如果 case 語句不包含 break,控制流將會 繼續 后續的 case,直到遇到 break 為止。‘
2.無限循環
C 程序員偏向于使用 for( ; ; ) 結構來表示一個無限循環。
8.枚舉
義一個枚舉類型,需要使用 enum 關鍵字,后面跟著枚舉類型的名稱,以及用大括號 {} 括起來的一組枚舉常量。每個枚舉常量可以用一個標識符來表示,也可以為它們指定一個整數值,如果沒有指定,那么默認從 0 開始遞增。
8.1枚舉變量的定義
- enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day; - enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
例子:
#include <stdio.h>enum DAY
{MON=1, TUE, WED, THU, FRI, SAT, SUN
};int main()
{enum DAY day;day = WED;printf("%d",day);return 0;
}
輸出3 里面的元素如果不賦值初始化,默認從前面一個遞增
9. 函數指針與回調函數
1.函數指針
#include <stdio.h>int max(int x, int y)
{return x > y ? x : y;
}int main(void)
{/* p 是函數指針 */int (* p)(int, int) = & max; // &可以省略int a, b, c, d;printf("請輸入三個數字:");scanf("%d %d %d", & a, & b, & c);/* 與直接調用函數等價,d = max(max(a, b), c) */d = p(p(a, b), c); printf("最大的數字是: %d\n", d);return 0;
}
2.回調函數
函數指針作為某個函數的參數
函數指針變量可以作為某個函數的參數來使用的,回調函數就是一個通過函數指針調用的函數。
簡單講:回調函數是由別人的函數執行時調用你實現的函數。
實例:實例中 populate_array() 函數定義了三個參數,其中第三個參數是函數的指針,通過該函數來設置數組的值。
實例中我們定義了回調函數 getNextRandomValue(),它返回一個隨機值,它作為一個函數指針傳遞給 populate_array() 函數。
populate_array() 將調用 10 次回調函數,并將回調函數的返回值賦值給數組
#include <stdlib.h>
#include <stdio.h>void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
{for (size_t i=0; i<arraySize; i++)array[i] = getNextValue();
}// 獲取隨機值
int getNextRandomValue(void)
{return rand();
}int main(void)
{int myarray[10];/* getNextRandomValue 不能加括號,否則無法編譯,因為加上括號之后相當于傳入此參數時傳入了 int , 而不是函數指針*/populate_array(myarray, 10, getNextRandomValue);for(int i = 0; i < 10; i++) {printf("%d ", myarray[i]);}printf("\n");return 0;
}
10.字符串相關函數
11. 結構體與結構體指針
12.union共用體
同一時間只能使用一個變量
- 定義共用體
為了定義共用體,您必須使用 union 語句,方式與定義結構類似。union 語句定義了一個新的數據類型,帶有多個成員。union 語句的格式如下
- 訪問
為了訪問共用體的成員,我們使用成員訪問運算符(.)。成員訪問運算符是共用體變量名稱和我們要訪問的共用體成員之間的一個句號。您可以使用 union 關鍵字來定義共用體類型的變量。下面的實例演示了共用體的用法:
13.位域
C語言中的位域是一種用來定義數據結構成員(即結構體成員)的特殊方式,其中這些成員被定義為占用指定數量的二進制位,而不是完整的字節。
- 位域的定義和位域變量的說明
在下面例子中,定義Age結構體中的age變量值只占用三位,也就是數值不能大于7
- 位域使用
下面示例中,定義了位域結構圖變量和位域結構體指針
14.typedef關鍵字
C 語言提供了 typedef 關鍵字,您可以使用它來為類型取一個新的名字
- 別名
typedef unsigned char BYTE;
在這個類型定義之后,標識符 BYTE 可作為類型 unsigned char 的縮寫,例如:
BYTE b1, b2;
按照慣例,定義時會大寫字母,以便提醒用戶類型名稱是一個象征性的縮寫。 - typedef定義結構體
- typedef vs #define
#define 是 C 指令,用于為各種數據類型定義別名,與 typedef 類似,但是它們有以下幾點不同:
- typedef 僅限于為類型定義符號名稱,#define 不僅可以為類型定義別名,也能為數值定義別名,比如您可以定義 1 為 ONE。
- typedef 是由編譯器執行解釋的,#define 語句是由預編譯器進行處理的。
15 C 預處理器
C 預處理器不是編譯器的組成部分,但是它是編譯過程中一個單獨的步驟。簡言之==,C 預處理器只不過是一個文本替換工具而已,它們會指示編譯器在實際編譯之前完成所需的預處理==。我們將把 C 預處理器(C Preprocessor)簡寫為 CPP。
所有的預處理器命令都是以井號(#)開頭。它必須是第一個非空字符,為了增強可讀性,預處理器指令應從第一列開始。下面列出了所有重要的預處理器指令:
#include <stdio.h>
#include “myheader.h”
這些指令告訴 CPP 從系統庫中獲取 stdio.h,并添加文本到當前的源文件中。下一行告訴 CPP 從本地目錄中獲取 myheader.h,并添加內容到當前的源文件中。
- 宏延續運算符(\)
一個宏通常寫在一個單行上。但是如果宏太長,一個單行容納不下,則使用宏延續運算符(\)。例如:
16. C錯誤處理
C 語言不提供對錯誤處理的直接支持,但是作為一種系統編程語言,它以返回值的形式允許您訪問底層數據。在發生錯誤時,大多數的 C 或 UNIX 函數調用返回 1 或 NULL,同時會設置一個錯誤代碼 errno,該錯誤代碼是全局變量,表示在函數調用期間發生了錯誤。您可以在 errno.h 頭文件中找到各種各樣的錯誤代碼。
- 程序退出狀態
通常情況下,程序成功執行完一個操作正常退出的時候會帶有值 EXIT_SUCCESS。在這里,EXIT_SUCCESS 是宏,它被定義為 0。
如果程序中存在一種錯誤情況,當您退出程序時,會帶有狀態值 EXIT_FAILURE,被定義為 -1。
17 C可變參數
后面的參數的個數和類型都不固定
18 內存管理
C 語言為內存的分配和管理提供了幾個函數。這些函數可以在 <stdlib.h> 頭文件中找到。
- C語言中常用的內存管理函數和運算符
-
malloc() 函數:用于動態分配內存。它接受一個參數,即需要分配的內存大小(以字節為單位),并返回一個指向分配內存的指針。
-
free() 函數:用于釋放先前分配的內存。它接受一個指向要釋放內存的指針作為參數,并將該內存標記為未使用狀態。
-
calloc() 函數:用于動態分配內存,并將其初始化為零。它接受兩個參數,即需要分配的內存塊數和每個內存塊的大小(以字節為單位),并返回一個指向分配內存的指針。
-
realloc() 函數:用于重新分配內存。它接受兩個參數,即一個先前分配的指針和一個新的內存大小,然后嘗試重新調整先前分配的內存塊的大小。如果調整成功,它將返回一個指向重新分配內存的指針,否則返回一個空指針。
-
sizeof 運算符:用于獲取數據類型或變量的大小(以字節為單位)。
-
指針運算符:用于獲取指針所指向的內存地址或變量的值。
-
& 運算符:用于獲取變量的內存地址。
-
*運算符:用于獲取指針所指向的變量的值。
-
-> 運算符:用于指針訪問結構體成員,語法為 pointer->member,等價于 (*pointer).member。
-
memcpy() 函數:用于從源內存區域復制數據到目標內存區域。它接受三個參數,即目標內存區域的指針、源內存區域的指針和要復制的數據大小(以字節為單位)。
-
memmove() 函數:類似于 memcpy() 函數,但它可以處理重疊的內存區域。它接受三個參數,即目標內存區域的指針、源內存區域的指針和要復制的數據大小(以字節為單位)。
19. C命令行參數
執行程序時,可以從命令行傳值給 C 程序。這些值被稱為命令行參數,它們對程序很重要,特別是當您想從外部控制程序,而不是在代碼內對這些值進行硬編碼時,就顯得尤為重要了。
命令行參數是使用 main() 函數參數來處理的,其中,argc 是指傳入參數的個數,argv[] 是一個指針數組,指向傳遞給程序的每個參數。
- 執行的程序名為數組第一個值 argv[0], argc是int類型的,它表示的是命令行參數的個數。不許要用戶傳遞,它會根據用戶從命令行輸入的參數個數,自動確定。argv是char**類型的,它的作用是存儲用戶從命令行傳遞進來的參數。它的第一個成員是用戶運行的程序名字。
- 多個命令行參數之間用空格分隔,但是如果參數本身帶有空格,那么傳遞參數的時候應把參數放置在雙引號 “” 或單引號 ‘’ 內部。
./test hello world hello world
5個參數,test為執行程序名,后面以空格劃分./test "hello world" "hello world"
3個參數