概述 - C语言老了
目前而言(2017年5月12日) C语言中有 32 + 5 + 7 = 44 个关键字. 具体如下 O(∩_∩)O哈哈~
-> 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 -> signed char uint8_t -> unsigned char
扯淡一点, 程序开发最常遇到的就是自解释问题. 鸡生蛋, 蛋生鸡. 后面再分析 signed 和 unsigned
演示:
#include <stdio.h> char c; c = getchar(); rewind(stdin); printf("c = %d, c = %c. ", c);
2) short
解释:
声明变量的时候用! short 占2字节, 为无符号的. 默认自带signed. 范围[-2^15, 2^15 - 1] 2^15 = 32800.
推荐使用 int16_t or uint16_t 类型.
演示:
short port = 8080; printf("port = %d. ", port);
3) int
解释:
声明变量的时候用! int 声明的变量, 占4字节, 有符号. 范围 [-2^31, 2^31-1].
推荐用 int32_t 和 uint32_t类型开发. 方便移植
演示:
int hoge = 24; printf("hoge = %d. ", hoge);
4) unsigned
解释:
变量类型修饰符! 被修饰的变量就是无符号的.范围 >= 0. unsigned 只能修饰整型的变量.
当然当你用这个修饰变量的时候. 再使用 - 和 -- 运算的时候一定要小心
演示:
unsigned int i = 0; // 正确 unsigned short s = 0; // 正确 unisgned float f = 0.11f; // 错误
5) long
解释:
声明变量的时候用!长整型 x86上四字节, x64上8字节. 一定不比int字节数少. C99之后出现long long类型8字节.
演示:
long l = 4; long long ll = l; printf("l = %ld, ll = %lld. ", l, ll);
6) float
解释:
声明变量的时候用! 四字节. 精度是6-7位左右. 详细精度可以看 float与double的范围和精度
演示:
float f = -0.12f; // 四字节 long float lf = 0; // 八字节 等同于 double, 不推荐这么写
7) double
解释:
声明变量的时候用!八字节,精度在15-16位左右.有的时候压缩内存用float代替.
演示:
double d = 2e13; // 8字节 long double ld = -0.99; // x86也是8字节, 不推荐这么用 long long double lld = 99; // 写法错误, 不支持
8) struct
解释:
定义结构体, 这个关键字用法广泛, 是大头. c 的重要思路就是面向过程编程. 撑起面向过程的大头就是结构体.
struct 就是定义结构的东西, 可以看看下面演示
演示:
// 普通结构体定义 struct node { int id; struct node * next; }; struct node node = { 1, NULL }; // 匿名结构定义 struct { int id; char * name; } per = { 2, "王志" };
9) union
解释:
定义公用体, 用法很花哨. 常在特殊库函数封装中用到.技巧性强
演示:
// 普通定义 union type { char c; int i; float f; };
union type t = { .f = 3.33f }; // 匿名定义 union { ... } t = { .... }; // 类型匿名定义 struct cjson { struct cjson * next; // 采用链表结构处理, 放弃二叉树结构, 优化内存 struct cjson * child; // type == ( _CJSON_ARRAY or _CJSON_OBJECT ) 那么 child 就不为空 unsigned char type; // 数据类型和方式定义, 一个美好的意愿 char * key; // json内容那块的 key名称 union { char * vs; // type == _CJSON_STRING, 是一个字符串 double vd; // type == _CJSON_NUMBER, 是一个num值, ((int)c->vd) 转成int 或 bool }; };
再来一种 union用法, 利用内存对齐.
//12.0 判断是大端序还是小端序,大端序返回true inline bool sh_isbig(void) { static union { unsigned short _s; unsigned char _c; } _u = { 1 }; return _u._c == 0; }
还有很久以前利用union 实现内存字节对齐, 太多了. 每个关键字用法, 确实很多, 很意外.
10) void
解释:
这个是空关键字. 用法很多. 也是我最喜欢的关键字. 用在函数声明中, 类型定义中.
演示:
// 函数声明 extern void foo(); // 函数参数约束 extern void foo(void); // ()中加了void表示函数是无参的, 否则是任意的 // 万能类型定义, 指针随便转 void * arg = NULL;
11) enum
解释:
枚举类型, C中枚举类型很简陋. 其实就相当于一种变相的INT宏常量. 估计这也许也是 INT宏常量和枚举并存的原因.
演示:
// // flag_e - 全局操作基本行为返回的枚举, 用于判断返回值状态的状态码 // >= 0 标识 Success状态, < 0 标识 Error状态 // typedef enum { 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, 所以多数别人就省略了.
演示:
signed int piyo = 0x1314520; signed char * str = u8"你好吗";
当然了, 平时不需要刻意加. 会让人嫌麻烦. O(∩_∩)O哈哈~
13) const
解释:
const修饰的变量表示是个不可修改的量. 和常量有点区别. 可以简单认为 const type val 是个只读的.
演示:
// 声明不可修改的量 const int age = 24; // 修饰指针 const int * pi = NULL; // *pi 不能修改指向变量 int * const pt = NULL; // pt 不能指向新的指针 const int * const pc = NULL; // *pc 和 pc 都不能动
其实在c中基本没有什么改变不了的. 全是内存来回搞, 软件不行硬件~~
14) volatile
解释:
声明变量修饰符, 可变的. 当变量前面有这个修饰符. 编译器不再从寄存器中取值, 直接内存读取写入. 保证实时性.
常用在多线程代码中.
演示:
// 具体轮询器 struct srl { mq_t mq; // 消息队列 pthread_t th; // 具体奔跑的线程 die_f run; // 每个消息都会调用 run(pop()) volatile bool loop; // true表示还在继续 };
以后使用loop的时候, 其它线程修改, 当前线程也能正确获取它的值.
15) typedef
解释:
类型重定义修饰符. 重新定义新的类型.
演示:
// 声明普通类型 typedef void * list_t; // 声明不完全类型, 头文件中不存在struct tree typedef struct tree * tree_t;
16) auto
解释:
变量类型声明符, auto变量存放在动态存储区,随着生命周期{开始 }结束而立即释放.存放在栈上.
默认变量都是auto的. 基本都是不写, 除非装逼!
演示:
{ // 生存期开始 int hoge = 0; auto int piyo = 1; // 生存期结束 }
不要用生命周期结束的变量, 存在各种意外.
17) register
解释:
变量修饰符,只能修饰整形变量.表示希望这个变量存放在CPU的寄存器上.现代编译器在开启优化时候,
能够一定程度上默认启用register寄存器变量.
演示:
#include <limits.h>
register int i = 0;
while (i < INT_MAX) {
++i;
}
由于CPU寄存器是有限的, 有时候你哪怕声明的寄存器变量也可能只是普通变量. printf("&i = %p ", &i) 这种用法是非法.
寄存器变量不能取地址.
18) static
解释:
static 用法很广泛. 修饰变量, 表示变量存在于静态区, 基本就是全局区. 生存周期同系统生存周期.
static修饰的变量作用域只能在当前文件范围内. 可以看成上层语言的private. 除了auto就是static.
static修饰函数表示当前函数是私有的,只能在当前文件中使用. 更加详细的看演示部分.
演示:
// 修饰全局变量, 只对当前文件可见 static int _fd = 0; // 修饰局部变量, 存储在全局区, 具有记忆功能 { static int _cnt = 0; } // 修饰函数, 函数只能在当前文件可见 static void * _run(void * arg) { ...... return arg; } // // C99之后加的static新用法, 编译器优化 // static 只能修饰函数第一维,表示数组最小长度, 方便编译器一下取出所有内存进行优化 // int sum(int a[static 10]) { ... }
19) extern
解释:
extern 关键字表示声明, 变量声明, 函数声明. 奇葩的用法很多.
演示:
// 声明引用全局变量 extern int g_cnt; // 声明引用全局函数 extern int kill(int sig, int val);
当然有时候extern不写, 对于变量不行会出现重定义. 对于函数是可以缺省写法. 再扯一点
// extern 主动声明, 希望外部可以调用 extern int kill(int sig, int val); // extern 缺省,不推荐外部调用 int kill(int sig, int val);
20) break
解释:
结束语句. 主要用于循环的跳转, 只能跳转到当前层级. 也用于switch 语句中, 跳出switch嵌套.
演示:
for(;;) { // 符合条件跳转 if(six == 6) break; } // break 跳出while循环 int i = 0; while(i < 6) { if(i == 3) break; }
break用法主要和循环一块使用, 还有do while. 但只能跳转当前层循环.
21) case
解释:
switch 语句中分支语句. 确定走什么分支.
演示:
// case 普通用法 和 break成对出现 switch ((c = *++ptr)) { case 'b': *nptr++ = ''; break; case 'f': *nptr++ = 'f'; break; case 'n': *nptr++ = ' '; break; case 'r': *nptr++ = ' '; break; case 't': *nptr++ = ' '; break; }
多扯一点, 对于case相当于标记点. switch 中值决定case跳转到哪里.再一直往下执行, 遇到break再结束switch嵌套.
22) continue
解释:
跳过此次循环. 直接进行条件判断操作. for 和 while 有些局别. for 会执行第三个后面的语句.
演示:
// for 循环 continue for(int i = 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(struct skynet_context * context, const char * name) { switch(name[0]) { case ':': return strtoul(name+1,NULL,16); case '.': return skynet_handle_findname(name + 1); default: skynet_error(context, "Don't support query global name %s",name); } return 0; }
24) do
解释:
do 循环. 先执行循环体, 后再执行条件判断.
演示:
register i = 0; do { if(i % 2 == 0) continue; printf("i = %d. ", i); } while(++i < 10);
do while 循环有时候可以减少一次条件判断. 性能更好, 代码更长.
25) else
解释:
else 是 if 的反分支. 具体看演示
演示:
#include <stdbool.h> if(true) { puts("你好吗?"); } else { puts("我们分手吧."); } // 附赠个else 语法 #if defined(__GNUC__) // 定义了 __GNUC__ 环境, 就是gcc环境 #else #error "NOT __GNUC__, NEED GCC!"; #enfif
26) for
解释:
for 循环其实就是while循环的语法糖. 也有独到的地方.
演示:
for(int i = 0; i < 2; ++i) { if(i == 1) continue; if(i == 2) break; } 等价于下面这个 int i = 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) == '