C 語言筆記:保留關鍵字
什麼是保留關鍵字?
C 語言的關鍵字是由編譯器保留、具有特殊用途的單字。簡單舉例:if。
它們是語言的語法組成部分,編譯器會將它解讀為語法指令,所以不能作為變數、函數或其他識別符的名稱。
目前依照 C 語言版本不同有不同數量的保留關鍵字, ANSI C 標準中 32 個保留關鍵字、到 C99 有 37 個、C11 有 44 個。
下面依照功能不同列舉,黑色是 ANSI C 標準,藍色為 C99 增加的,紫色則是 C11 增加的,粗體為有額外說明。部分面試常見、我自己有些心得的保留字會有額外的筆記。
流程控制關鍵字
if else switch case default
for while do break continue
goto return
資料型態關鍵字
void char short int long
float double signed unsigned
_Bool _Complex _Imaginary
struct union enum
unsigned:常見陷阱
有號數(int)與 unsigned 比較或運算時,有號數會先轉成 unsigned
int x = -1; unsigned int y = 1; (x + y > 0) ? puts(">0"):puts("<=0");
這邊 x 的值會被強制轉成 1,所以結果會輸出「>0」unsigned 變數小於 0 的判斷永遠為假,因為它不能為負數
for (unsigned int i = 10; i >= 0; i--)
這會變成無限迴圈overfloat 時,會循環
unsigned int a = 0; a--;
a 會變成最大值(例如 4294967295)
union
不同的資料成員共用相同的記憶體位置,會用於對資料空間有限制的系統(如:嵌入式系統、通訊協定的封包、驅動程式)
mutithread 、需要追蹤變數的環境不適合使用
直接用範例解釋會比較清楚:
union Data { int i; float f; char str[20]; }; int main() { union Data data; data.i = 10; printf("data.i: %d\n", data.i); data.f = 3.14; printf("data.f: %.2f\n", data.f); // 設定 f 後,i 的值會被覆蓋(因為共用記憶體) printf("data.i (after setting f): %d\n", data.i); return 0; }
編譯器會根據最大成員大小分配空間,以上面的例子來說,會分配 char str[20]; 的大小
在嵌入式系統上的使用例子:
union { unsigned short TACTL; struct { unsigned short TAIFG : 1; unsigned short TAIE : 1; unsigned short TACLR : 1; unsigned short : 1; unsigned short TAMC : 2; unsigned short TAID : 2; unsigned short TASSEL : 2; unsigned short : 6; } TACTL_bit; } TimerA;
當我們需要一口氣抓全部控制訊息時,直接抓 TimerA.TACTL 即可,但當我們需要個別 bit 進行控制時,可針對 TimerA.TACTL_bit 裡的內容進行操控
enum
定義一組具名整數常數的資料型態
以下範例能清楚解釋怎麼定義,如果沒有賦予第一個成員值,便會依序設為 0、1、2……
enum Color { RED, // 預設為 0 GREEN, // 預設為 1 BLUE // 預設為 2 }; enum Status { OK = 200, NOT_FOUND = 404, INTERNAL_ERROR = 500 };
如果上面的 RED 設定為 5,GREEN 和 BLUE 則會依序為 5、6和 #define 的區別: #define 只是簡單的文字替換,沒有型別資訊;enum 有明確的範圍與可讀性,可在 debugger 中顯示名稱而不是純數字
struct
- 將多個不同型態的變數組合成一個新的資料型態
儲存類別關鍵字:決定變數生命週期與可見範圍
auto static extern register
_Thread_local
static 的作用
修飾區域變數(延長生命週期)
在 function 宣告的變數通常在離開 function時就會銷毀,但加上 static 後,只要程式執行期間該變數就會一直存在。#include <stdio.h> void counter() { static int count = 0; // 僅初始化一次 count++; printf("count = %d\n", count); } int main() { counter(); // count = 1 counter(); // count = 2 counter(); // count = 3 }修飾全域變數與 function(限制作用範圍)
限制全域變數與 function 只能在該檔案範圍中使用。
修飾詞關鍵字:描述變數或函式的額外性質
const volatile inline restrict
_Alignas _Alignof
const
增加「read-only」的屬性,修飾的對象可能是變數、資料或指標本身。
簡易舉例
const int *p:資料不變(指標可變)
int *const p:指標不變(資料可變)
const int *const p:指標與資料都不變
volatile
- 這個變數的值可能會被外部因素改變,所以不要進行編譯器最佳化,每次讀取時一定要重新從記憶體抓值。
- 常用於嵌入式、中斷服務程式 (ISR)、硬體寄存器、與多執行緒環境
- 如果 const 和 volatile 一起使用會如何?
不矛盾,可以一起用。這種情況常出現在硬體暫存器、multithreading 和 interrupt 中。範例如下:const volatile int flag = 0; // flag 只能被外部修改,主程式只能讀 void loop() { while (flag == 0) {} // 等待 flag 被外部事件改變 // 當 flag 被設為非 0 時,進行某些處理 printf("Flag is set! Continue...\n"); }
假設某個全域變數是由中斷處理程序(ISR)或其他執行緒改變,而主程式只能「監控」這個變數。
inline
- 作用:建議編譯器將該函式的程式碼「直接插入(展開)」到呼叫的地方,以減少函式呼叫開銷(呼叫/返回),提高效率。
- 適合用於函式不大、但常被呼叫的類型
- 和 #define 的比較
在運算處理容易出錯、以及傳運算式部分,用例子比較一下功能 inline#define本質 函式 預處理器指令(文字替換) 參數處理 有型別檢查、可傳運算式 無型別檢查,僅字串替換 可除錯 可設定中斷點、可堆疊追蹤 不可除錯(展開後無來源對應) 運算處理 每參數僅計算一次 容易出錯 可讀性 函式語法,清楚易讀 可讀性差,尤其巨集複雜時 建議用在 小函式或性能敏感的邏輯 常數定義 inline int square(int x) { return x * x; } square(i++);#define SQUARE(x) ((x)*(x)) SQUARE(i++); // 展開為 ((i++)*(i++)) → 錯誤,i++ 被執行兩次
其他關鍵字
sizeof typedef _Atomic
_Generic _Noreturn _Static_assert
typedef
設定現有型別的別名,在編譯過程中會進行型別檢查
和 #define 的比較
#define dPS struct s * dPS a, b; // a 是 struct s*,b 是 struct s (非指標) typedef struct s * tPS; tPS a, b; // a 和 b 都是 struct s*
留言
張貼留言