目前而言(2017年5月18日) C語言中有 32 + 5 + 7 = 44 個關鍵字. 具體如下
-> C89關鍵字
char
|
short
|
int
|
unsigned
|
long
|
float
|
double
|
struct
|
union
|
void
|
enum
|
signed
|
const
|
volatile
|
typedef
|
auto
|
register
|
static
|
extern
|
break
|
case
|
continue
|
default
|
do
|
else
|
for
|
goto
|
if
|
return
|
switch
|
while
|
sizeof
|
-> C99新增關鍵字
_Bool
|
_Complex
|
_Imaginary
|
inline
|
restrict
|
-> C11新增關鍵字
_Alignas
|
_Alignof
|
_Atomic
|
_Generic
|
_Noreturn
|
_Static_assert
|
_Thread_local
|
下面容我細細分析起具體用法.(存在平臺差異, 有問題特別歡迎評論補充, 這就相當于一個關鍵字字典)
C89 32個關鍵字
1) char
解釋:
聲明變量的時候用! char占1字節, 8bit. 多數系統(vs or gcc)上是有符號的(arm 上無符號), 范圍是[-128, 127].
在工程項目開發中推薦用
#include <stdint.h>int8_t-> signedcharuint8_t-> unsignedchar
扯淡一點, 程序開發最長遇到的就是自解釋問題. 雞生蛋, 蛋生雞. 后面再分析 signed 和 unsigned
演示:
#include <stdio.h>charc;
c=getchar();
rewind(stdin);
printf("c = %d, c = %c.\n", c);
2) short
解釋:
聲明變量的時候用! short 占2字節, 為無符號的. 默認自帶signed. 范圍[-2^15, 2^15 - 1] 2^15 = 32800.
推薦使用 int16_t or uint16_t 類型.
演示:
shortport =8080;
printf("port = %d.\n", port);
3) int
解釋:
聲明變量的時候用! int 聲明的變量, 占4字節, 有符號. 范圍 [-2^31, 2^31-1].
推薦用 int32_t 和 uint32_t類型開發. 方便移植
演示:
inthoge =24;
printf("hoge = %d.\n", hoge);
4) unsigned
解釋:
變量類型修飾符! 被修飾的變量就是無符號的.范圍 >= 0. unsigned 只能修飾整型的變量.
當然當你用這個修飾變量的時候. 再使用 - 和 -- 運算的時候一定要小心
演示:
unsignedinti =0;//正確unsignedshorts =0;//正確unisgnedfloatf =0.11f;//錯誤
5) long
解釋:
聲明變量的時候用!長整型 x86上四字節, x64上8字節. 一定不比int字節數少. C99之后出現long long類型8字節.
演示:
longl =4;longlongll =l;
printf("l = %ld, ll = %lld.\n", l, ll);
6) float
解釋:
聲明變量的時候用! 四字節. 精度是6-7位左右. 詳細精度可以看 float與double的范圍和精度
演示:
floatf = -0.12f;//四字節longfloatlf =0;//八字節 等同于 double, 不推薦這么寫
7) double
解釋:
聲明變量的時候用!八字節,精度在15-16位左右.有的時候壓縮內存用float代替.
演示:
doubled = 2e13;//8字節longdoubleld = -0.99;//x86也是8字節, 不推薦這么用longlongdoublelld =99;//寫法錯誤, 不支持
8) struct
解釋:
定義結構體, 這個關鍵字用法廣泛, 是大頭. c 的重要思路就是面向過程編程. 撐起面向過程的大頭就是結構體.
struct 就是定義結構的東西, 可以看看下面演示
演示:
//普通結構體定義structnode {intid;structnode *next;
};structnode node = {1, NULL };//匿名結構定義struct{intid;char*name;
} per= {2,"王志"};
9) union
解釋:
定義公用體, 用法很花哨. 常在特殊庫函數封裝中用到.技巧性強
演示:
//普通定義union type {charc;inti;floatf;
};
union type t= { .f =3.33f};//匿名定義union { ... } t ={ .... };//類型匿名定義structcjson {structcjson * next;//采用鏈表結構處理, 放棄二叉樹結構, 優化內存structcjson * child;//type == ( _CJSON_ARRAY or _CJSON_OBJECT ) 那么 child 就不為空unsignedchartype;//數據類型和方式定義, 一個美好的意愿char* key;//json內容那塊的 key名稱union {char* vs;//type == _CJSON_STRING, 是一個字符串doublevd;//type == _CJSON_NUMBER, 是一個num值, ((int)c->vd) 轉成int 或 bool};
};
再來一種 union用法, 利用內存對齊.
//12.0 判斷是大端序還是小端序,大端序返回trueinlineboolsh_isbig(void) {staticunion {
unsignedshort_s;
unsignedchar_c;
} _u= {1};return_u._c ==0;
}
還有很久以前利用union 實現內存字節對齊, 太多了. 每個關鍵字用法, 確實很多, 很意外.
10) void
解釋:
這個是空關鍵字. 用法很多. 也是我最喜歡的關鍵字. 用在函數聲明中, 類型定義中.
演示:
//函數聲明externvoidfoo();//函數參數約束externvoidfoo(void);//()中加了void表示函數是無參的, 否則是任意的//萬能類型定義, 指針隨便轉void* arg = NULL;
11) enum
解釋:
枚舉類型, C中枚舉類型很簡陋. 其實就相當于一種變相的INT宏常量. 估計這也許也是 INT宏常量和枚舉并存的原因.
演示:
////flag_e - 全局操作基本行為返回的枚舉, 用于判斷返回值狀態的狀態碼//>= 0 標識 Success狀態, < 0 標識 Error狀態//typedefenum{
Success_Exist= +2,//希望存在,設置之前已經存在了.Success_Close = +1,//文件描述符讀取關閉, 讀取完畢也會返回這個Success_Base = +0,//結果正確的返回宏Error_Base= -1,//錯誤基類型, 所有錯誤都可用它, 在不清楚的情況下Error_Param = -2,//調用的參數錯誤Error_Alloc = -3,//內存分配錯誤Error_Fd = -4,//文件打開失敗} flag_e;
枚舉變量完全可以等同于 int 變量使用, 枚舉值等同于宏INT常量使用. 枚舉的默認值是以1位單位從上向下遞增.
12) signed
解釋:
變量聲明類型修飾符. 有符號型, 對比 unsigned 無符號型. 變量聲明默認基本都是 signed, 所以多數別人就省略了.
演示:
signedintpiyo =0x1314520;
signedchar* str = u8"你好嗎";
當然了, 平時不需要刻意加. 會讓人嫌麻煩. O(∩_∩)O哈哈~
13) const
解釋:
const修飾的變量表示是個不可修改的量. 和常量有點區別. 可以簡單認為 const type val 是個只讀的.
演示:
//聲明不可修改的量constintage =24;//修飾指針constint* pi = NULL;//*pi 不能修改指向變量int*constpt = NULL;//pt 不能指向新的指針constint*constpc = NULL;//*pc 和 pc 都不能動
其實在c中基本沒有什么改變不了的. 全是內存來回搞, 軟件不行硬件~~
14) volatile
解釋:
聲明變量修飾符, 可變的. 當變量前面有這個修飾符. 編譯器不再從寄存器中取值, 直接內存讀取寫入. 保證實時性.
常用在多線程代碼中.
演示:
//具體輪詢器structsrl {
mq_t mq;//消息隊列pthread_t th;//具體奔跑的線程die_f run;//每個消息都會調用 run(pop())volatileboolloop;//true表示還在繼續};
以后使用loop的時候, 其它線程修改, 當前線程也能正確獲取它的值.
15) typedef
解釋:
類型重定義修飾符. 重新定義新的類型.
演示:
//聲明普通類型typedefvoid*list_t;//聲明不完全類型, 頭文件中不存在struct treetypedefstructtree * tree_t;
16) auto
解釋:
變量類型聲明符, auto變量存放在動態存儲區,隨著生命周期{開始 }結束而立即釋放.存放在棧上.
默認變量都是auto的. 基本都是不寫, 除非裝逼!
演示:
{//生存期開始inthoge =0;
autointpiyo =1;//生存期結束}
不要用生命周期結束的變量, 存在各種意外.
17) register
解釋:
變量修飾符,只能修飾整形變量.表示希望這個變量存放在CPU的寄存器上.現代編譯器在開啟優化時候,
能夠一定程度上默認啟用register寄存器變量.
演示:
#include <limits.h>register int i = 0;
while (i < INT_MAX) {
++i;
}
由于CPU寄存器是有限的, 有時候你哪怕聲明的寄存器變量也可能只是普通變量. printf("&i = %p\n", &i) 這種用法是非法.
寄存器變量不能取地址.
18) static
解釋:
static 用法很廣泛. 修飾變量, 表示變量存在于靜態區, 基本就是全局區. 生存周期同系統生存周期.
static修飾的變量作用域只能在當前文件范圍內. 可以看成上層語言的private. 除了auto就是static.
static修飾函數表示當前函數是私有的,只能在當前文件中使用. 更加詳細的看演示部分.
演示:
//修飾全局變量, 只對當前文件可見staticint_fd =0;//修飾局部變量, 存儲在全局區, 具有記憶功能{staticint_cnt =0;
}//修飾函數, 函數只能在當前文件可見staticvoid* _run(void*arg) {
......returnarg;
}////C99之后加的static新用法, 編譯器優化//static 只能修飾函數第一維,表示數組最小長度, 方便編譯器一下取出所有內存進行優化//intsum(inta[static10]) { ... }
19) extern
解釋:
extern 關鍵字表示聲明, 變量聲明, 函數聲明. 奇葩的用法很多.
演示:
//聲明引用全局變量externintg_cnt;//聲明引用全局函數externintkill(intsig,intval);
當然有時候extern不寫, 對于變量不行會出現重定義. 對于函數是可以缺省寫法. 再扯一點
//extern 主動聲明, 希望外部可以調用externintkill(intsig,intval);//extern 缺省,不推薦外部調用intkill(intsig,intval);
20) break
解釋:
結束語句. 主要用于循環的跳轉, 只能跳轉到當前層級. 也用于switch 語句中, 跳出switch嵌套.
演示:
for(;;) {//符合條件跳轉if(six ==6)break;
}//break 跳出while循環inti =0;while(i <6) {if(i ==3)break;
}
break用法主要和循環一塊使用, 還有do while. 但只能跳轉當前層循環.
21) case
解釋:
switch 語句中分支語句. 確定走什么分支.
演示:
//case 普通用法 和 break成對出現switch((c = *++ptr)) {case'b': *nptr++ ='\b';break;case'f': *nptr++ ='\f';break;case'n': *nptr++ ='\n';break;case'r': *nptr++ ='\r';break;case't': *nptr++ ='\t';break;
}
多扯一點, 對于case相當于標記點. switch 中值決定case跳轉到哪里.再一直往下執行, 遇到break再結束switch嵌套.
22) continue
解釋:
跳過此次循環. 直接進行條件判斷操作. for 和 while 有些局別. for 會執行第三個后面的語句.
演示:
//for 循環 continuefor(inti =0; i <20; ++i) {if(i %2==0)continue;//上面continue 調到 ++i -> i < 20 代碼塊}
23) default
解釋:
switch 分支的默認分支, 假如case都沒有進入那就進入default分支. default 可以省略break. c 語法中可行.
演示:
uint32_t
skynet_queryname(structskynet_context * context,constchar*name) {switch(name[0]) {case':':returnstrtoul(name+1,NULL,16);case'.':returnskynet_handle_findname(name +1);default:
skynet_error(context,"Don't support query global name %s",name);
}return0;
}
24) do
解釋:
do 循環. 先執行循環體, 后再執行條件判斷.
演示:
register i =0;do{if(i %2==0)continue;
printf("i = %d.\n", i);
}while(++i <10);
do while 循環有時候可以減少一次條件判斷. 性能更好, 代碼更長.
25) else
解釋:
else 是 if 的反分支. 具體看演示
演示:
#include <stdbool.h>if(true) {
puts("你好嗎?");
}else{
puts("我們分手吧.");
}//附贈個else 語法#ifdefined(__GNUC__)//定義了 __GNUC__ 環境, 就是gcc環境#else#error"NOT __GNUC__, NEED GCC!";#enfif
26) for
解釋:
for 循環其實就是while循環的語法糖. 也有獨到的地方.
演示:
for(inti =0; i <2; ++i) {if(i ==1)continue;if(i ==2)break;
}
等價于下面這個inti =0;while(i <2) {if(i ==1) {++i;continue;
}if(i ==2)break;++i;
}//for 最好的寫法, 在于死循環寫法for(;;) {//xxxx}
for(;;) { } 比 while(true) { } 寫法好, 有一種不走條件判斷的意圖, 雖然匯編代碼是一樣的.
27) goto
解釋:
goto 是我第二喜歡的關鍵字. 可以在當前函數內跳轉. goto 可以替代所有循環.
演示:
__loop://xxx 死循環用法goto__loop;
__exitloop:
還有就是在工程開發中, goto 常用于復制的業務邏輯.
if((n = *tar) =='\0')//判斷下一個字符goto__err_ext;if(cl % rl){//檢測 , 號是個數是否正常__err_ext:
SL_WARNING("now csv file is illegal! c = %d, n = %d, cl = %d, rl = %d.", c, n, cl, rl);returnfalse;
}
28) if
解釋:
if 分支語句. 用法太多了. 程序語句中分支就是智能.
演示:
if(false) {
puts("我想做個好人!");
}
29) return
解釋:
程序返回語句太多了. 用于函數返回中. 返回void 直接 return;
演示:
#include <stdlib.h>intmain(intargc,char*argv[]) {returnEXIT_SUCCESS;
}
30) switch
解釋:
條件分支語句. 很復雜的if else if 時候可以switch.
演示:
#include <unistd.h>do{intrt = write(fd, buf,sizeofbuf)if(rt <0) {switch(errno) {caseEINTERcontinue;default:
perror("write error");
}
}
}while(rt >0);
31) while
解釋:
循環語句, 有do while 和 while 語句兩種.
演示:
#define_INT_CNT (10)inti = -1;while(++i <_INT_CNT) {//......}
32) sizeof
解釋:
這個關鍵字也稱為 sizeof 運算符. 計算變量或類型的字節大小. 這個關鍵字特別好用!
演示:
sizeof(main) ->x86 上四字節// 獲取數組長度,只能是數組類型或""字符串常量,后者包含'\0'
#define LEN(arr) (sizeof(arr) / sizeof(*(arr)))
到這里C89保留的關鍵字基本解釋完畢.
C99 5個新增關鍵字
33) _Bool
解釋:
bool類型變量, 等價于 unsigned char . 只有0和1.
演示:
#include <stdbool.h>boolflag =true;//或者直接用_Bool flag = !0;
34) _Complex
解釋:
對于C99 標準定義, 存在 float _Complex, double _Complex, long double _Complex 復數類型. 下面先演示gcc 中關于復數的用法.
演示:
#include <math.h>#include<stdio.h>#include<complex.h>////測試 c99 complex 復數//intmain(intargc,char*argv[]) {floatcomplex f = -1.0f+1.0if;
printf("The complex number is: %f + %fi\n",crealf(f), cimagf(f));doublecomplex d = csqrt(4.0+4.0i);
printf("d = %lf + %lfi\n", creal(d), cimag(d));return0;
}
其實在復數類型中, gcc標準實現
#definecomplex _Complex
而在VS 中實現具體為
#ifndef _C_COMPLEX_T#define_C_COMPLEX_Ttypedefstruct_C_double_complex
{double_Val[2];
} _C_double_complex;
typedefstruct_C_float_complex
{float_Val[2];
} _C_float_complex;
typedefstruct_C_ldouble_complex
{longdouble_Val[2];
} _C_ldouble_complex;#endiftypedef _C_double_complex _Dcomplex;
typedef _C_float_complex _Fcomplex;
typedef _C_ldouble_complex _Lcomplex;
總的而言, 學習C 最好的平臺就是 *nix 平臺上使用 Best new GCC. 當然除了科學計算會用到復數, 其它很少.
這里VS 和 GCC實現不一樣. 用起來需要注意.
35) _Imaginary
解釋:
虛數類型. _Complex 復數類型的虛部. 例如 10.0i, 10.8if 等等. 這個關鍵字在VS 上沒有實現. 其實我也覺得沒有必要.
和_Complex有重疊.
演示:
這個關鍵字無法在代碼中表示. 系統保留, 我們不能使用.
36) inline
解釋:
內聯函數,從C++中引入的概念. 就是將小函數直接嵌入到代碼中. C的代碼損耗在于函數的進出棧. 要是可以推薦用內聯函數
替代宏. 宏能不用就不用. 函數什么的時候不要加inline 需要加extern, 定義的時候需要加inline.
演示:
/** 對json字符串解析返回解析后的結果
* jstr : 待解析的字符串*/externcjson_t cjson_newtstr(tstr_t str);
inline cjson_t
cjson_newtstr(tstr_t str) {
str->len = _cjson_mini(str->str);return_cjson_parse(str->str);
}//還有就是和static 一起使用staticinlineint_sconf_acmp(tstr_t tstr,structsconf *rnode) {returnstrcmp(tstr->str, rnode->key);
}
37) restrict
解釋:
這是很裝逼的關鍵字用于編譯器優化. 關鍵字restrict只用于限定指針;該關鍵字用于告知編譯器,
所有修改該指針所指向內容的操作全部都是基于(base on)該指針的,即不存在其它進行修改操作的途徑;
這樣的后果是幫助編譯器進行更好的代碼優化,生成更有效率的匯編代碼。
演示:
externvoid*mempcpy (void*__restrict __dest,constvoid*__restrict __src, size_t __n)
__THROW __nonnull ((1,2));
上面是摘自GCC 的 string.h中. 其實正式用法
//簡單演示用法, GCC 和 VS 都是 __restrict 推薦加在 * 后面staticvoid_strlove(char*__restrict dest) {*dest ='\0';
}
Pelles C 編譯器可以完整支持 restrict.
C11 7個新增關鍵字
38) _Alignas
解釋:
內存對齊的操作符. 需要和_Alignof配合使用, 指定結構的對齊方式.
演示:
#ifndef __cplusplus#definealignas _Alignas#definealignof _Alignof#define__alignas_is_defined 1#define__alignof_is_defined 1#endif
例如一種用法
#include <stdio.h>#include<stdalign.h>structper {intage;doublesecl;charsex;
};intmain(intargc,char*argv[]) {charc[100];
alignas(structper)structper * per = (structper *)&c;
printf("per = %p, c = %p.\n", per, c);return0;
}
將c 數組以 struct per 對齊方式對齊返回回去.
39) _Alignof
解釋:
得到類型和變量的對齊方式.
演示:
printf("alignof(struct per) = %zd.\n", alignof(structper));
40) _Atomic
解釋:
原子操作, 原子鎖. gcc 很早就支持. 詳細用法可以參照 CAS https://sanwen8.cn/p/18dZQie.html
講的可以.
演示:
#include <stdio.h>#include<stdatomic.h>intmain(intargc,char*argv[]) {
_Atomicinthoge = ATOMIC_VAR_INIT(100);intpiyo = atomic_load(&hoge);
printf("piyo = %d.\n", piyo);
piyo+=2;
atomic_store(&hoge, piyo);
printf("hoge = %d.\n", hoge);return0;
}
具體的執行結果, 你也懂就那樣. 原子操作, 對于寫出高效代碼很重要.
41) _Generic
解釋:
這個比較叼, C的泛函機制. 高級函數宏. 下面來個老套路用法
演示:
#include <math.h>#include<stdio.h>#include<stdlib.h>#defineABS(x) \_Generic((x),int:abs,float:fabsf,double:fabs)(x)////測試 C11 語法//intmain(intargc,char*argv[]) {inta =1, b =2, c =3;
_Generic(a+0.1f,int:b,float:c,default:a)++;
printf("a = %d, b = %d, c = %d\n", a, b, c);
printf("int abs: %d\n", ABS(-12));
printf("float abs: %f\n", ABS(-12.04f));
printf("double abs: %f\n", ABS(-13.09876));returnEXIT_SUCCESS;
}
宏泛型真的很給力. 宏又能玩上天了.
42) _Noreturn
解釋:
修飾函數,絕對不會有返回值. _Noreturn 聲明的函數不會返回. 引入此新的函數修飾符有兩個目的:
-
消除編譯器對沒有return的函數的警告.
-
允許某種只針對不返回函數的優化.
演示:
_Noreturnvoidsuicide(void) {
abort();//Actually, abort is _Noreturn as well}
43) _Static_assert
解釋:
編譯器期間斷言, 當 #if #error 搞完畢(預編譯)之后, 編譯器斷言. assert是運行時斷言.用的時候看具體的需求.
演示:
_Static_assert(__STDC_VERSION__ >=201112L,"C11 support required");//Guess I don't really need _Static_assert to tell me this :-(
44) _Thread_local
解釋:
到這里快扯完了, 其實C11標準是個很好的嘗試. 為C引入了線程和原子操作. 各種安全特性補充. 可以說C強大了.
但是還遠遠不夠, 因為越來越丑了. C11為C引入了線程 在 頭文件<threads.h>中定義.但也允許編譯可以不實現.
_Thread_local是新的存儲類修飾符, 限定了變量不能在多線程之間共享。
演示:
_Thread_localstaticinti;//Thread local isn't local!
語義上就是線程的私有變量.