目前而言(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 #/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!
语义上就是线程的私有变量.