回首C语言关键字(~回首向来萧瑟处~)

开篇废话:

本文意在回顾 C 语言中的关键字,整理文件发现当时做的这些笔记还是蛮用心的,有临摹

前辈的足迹也有自己的理解和体会。时至今日2018已经跨过一半,对不起过去半年,今天

拿这篇关键字开篇,开启自己的程序猿心路,主要记录一下自己遇到的问题和学习的经历,

方便自己。如果能对别也有用那就更开心了,由于自己还很菜,理解和体会都很有限,如

果你打开发现了错误还请不吝赐教,随便评论,不要客气,我都会很感激的。首篇废话就

这么多吧,我可能废话比较多,哈哈,批评我吧~

C 关键字

/**
* 到目前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,多数系统上是有符号的(arm 上无符号)
范围 [-128, 127] 在工程项目中开发推荐用
int8_t -> singned char
uint8_t -> unsigned char
*/
char c = 'p'; /*
2) short 解释:
声明变量的时候用,short占2个字节,为无符号的,默认自带signed
范围[-2^15, 2^15-1]2^15 = 32800 推荐使用 int16_t or uint16_t 类型
*/
short port = 8080; /*
3) int
解释:
声明变量的时候用,int声明的变量占4个字节,有符号,范围[-2^31,2^31-1]2^31 推荐使用:int32_t or uint32_t 类型开发,方便移植
*/
int i = 0; /*
4) unsigned
解释:
变量类型修饰符,被修饰的变量就是无符号的,范围>=0,unsigned 只能修饰整型的变量
当然你用这个修饰变量的时候,再使用++和--运算的时候一定要小心。
*/
unsigned int i = 0; //正确
unsigned short s = 0; //正确
unsigned float f = 0.11f; //错误 /*
5) long
解释:
声明变量的时候使用,长整型x86上4个字节,x64上8个字节,一定不比int字节数少
c99之后出现long long 类型为8个字节
*/
long l = 4;
long long ll = 8; /*
6) float
解释:
声明变量的时候用,4个字节,精度是6-7位,
详细精度可以看:https://blog.csdn.net/dxy612/article/details/5518477
*/
float f = -0.12f; /*
7) double
解释:
声明变量的时候用,8个字节,精度在15-16位左右,有的时候压缩内存用float代替
*/
double d = 2e13; /*
8) struct
解释:
定义结构体,这个关键字用法很广,是大头。C的重要的思路就是面向过程编程,
撑起面向过程的大头就是结构体。
*/
// 普通结构体
struct node {
int id;
struct node *next;
};
struct node n = {1,NULL}; // 匿名结构体
struct {
int id;
char* name;
} per = {2,"Tom"}; /*
9) union
解释:
定义共用体,用法很花哨,常在特殊库函数封装中用到,技巧很强
*/
// 普通定义
union type {
char c;
int i;
float f;
};
union type t = {.f = 3.33f}; // 匿名定义
union {...} t = {...}; //类型匿名定义
struct cjson {
struct cjson *next; //采用链表结构处理,放弃二叉树结构,优化内存 unsigned char type; // 数据类型和方式定义,一个美好的愿望
char *key; // json内容那块的key名称
union {
char *vs; // type == _CJSON_STRING, 是一个字符串
double vd; // type == _CJSON_NUMBER, 是一个num值((int)c->vd)转成int或bool
};
}; /*
什么是大小端? Endian表示数据在存储器中的存放顺序, 大端(Big Endian): 是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的
高地址中,这样的存储模式有点儿类似把数据当做字符串顺序处理:地址由小向大增加,
儿数据从高位往低位。 小端(Little Endian): 是指数据的高字节保存在内存的高地址中,而数据的低字节保存在
内存的低地址中,这种存储模式将地址的高低和数据位权有效的结合起来,高地址部分权值
高,低地址部分权值低,和我们的逻辑方法一致。 这两种模式,泥瓦匠记忆宫殿:“小端低低”。这样就知道小端的模式,反之大端的模式。 https://www.cnblogs.com/Alandre/p/4878841.html
*/ // 在来一种union用法,判断大小端,笔试题中长见
inline bool
sh_isbig(void)
{
static union {
unsigned short s;
unsigned char c;
}U = {1}; // 1 -> 0x0001;
return U.c == 0;
} /*
10) void
解释:
这个关键字用法很多,也是用在函数声明中,或函数参数
*/
// 函数声明
extern void foo(); // 函数参数约束
extern void foo(void); //函数参数为void表示函数是无参数的,否则是任意的 // 万能类型定义,指针随便转
void* vp = NULL; /*
11) enum
解释:
枚举类型,C中枚举类型很简陋,相当于一种变相的INT宏常量,估计也许是INT宏
常量和枚举并存的原因 问题1
有些面试题中会问你enum 和#define 的区别:
1. #define 宏常量是在预编译阶段进行简单的替换;enum常量则是在编译的时候确定其值
2. 一般在调试器里,可以调试枚举常量,但不是不能调试宏常量。
3. 枚举可以一次定义大量相关的常量,而#define 宏一次只能定义一个。 问题2
1. 枚举能做的事,#define 宏能不能做到?如果能,那为什么还需要枚举?
答:能,枚举可以自增1,这样不用每一个值都定义,而宏必须每个值都定义,
而枚举是一个集合,代表一类值,像代码中的颜色归为一类方便使用,而#define不能形成集合 2. sizeof(ColorVal)的值是多少?为什么?
enum Color{
GREEN = 1,
RED,
BLUE,
GREEN_RED = 10,
GREEN_BLUE
}ColorVal; 答:值为4,ColorVal一个枚举变量,而枚举变量代表一个整数。 */
//
// flag_e -全局操作基本行为返回的枚举,用于判断返回值状态的状态码
// >=0 标识Success状态,< 0 标识Error状态
//
// 枚举变量完全可以等同于int变量使用,枚举值等同于宏INT常量使用,枚举的默认值
// 是以1为单位从上向下递增。
//
typedef enum {
Success_Exit =2, //希望存在,设置之前已经存在了
Success_Close =1, //文件描述符读取关闭,读取完毕也返回这个
Success_Base =0, // 结果正确的返回宏 Error_Base =-1, //错误类型,所有错误都用它,在不清楚的情况下
Error_Parm =-2, //调用参数错误
Error_Alloc =-3, //内存分配错误
Error_Fd =-4, //文件打开失败
} flag_e; /*
12) signed
解释:
变量声明类型修饰符,有符号型,对比unsigned无符号型,变量声明默认基本都是
singned ,所有多数别就省略了
*/
signed int piyo = 0x12345678; /*
13) const
解释:
const 修饰的变量表示是一个不可修改的量,和常量有点区别,
*/
// 声明不可修改的量
const int age = 24; // 修饰指针
const int *pa = NULL; //pa指向的值(*pa)是不能修改
int *const pt = NULL; //pt不能指向新的指针,pt指向的值(*pt)可以修改
const int *const pc = NULL; //pc和pc指向的值(*pc)都不能修改 /*
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; // 重定义变量类型
typedef unsigned long int ub4; /* unsigned 4-byte quantities*/
typedef unsigned char ub1; /* unsigned 1-byte quantities*/ // 定义一个函数指针
typedef uint32_t (*crc_func)(uint32_t crc, const void *buf, size_t len);
// 使用
crc_func crc32c; //声明一个crc_func类型的变量,实际就是一个函数指针,指向函数的首地址 /*
16) auto
解释:
变量类型声明符,auto变量存放在动态存储区,随和声明周期{开始},结束而立即释放,存在在栈上 默认变量都是auto的,基本是不写
*/
//演示
{
// 生存期开始
int a = 0;
auto int p = 1;
// 生存期结束
} /*
17) register
解释:
修饰变量,这个关键字请求编译器尽可能的将变量存放在CPU内部寄存器中而不是通过内存寻址访问
以提高效率,注意是尽可能,不是绝对。 实用register修饰的注意点
虽然寄存器的速度非常快 ,但是实用register修饰符也要些限制的,register变量必须是能被cpu寄存器所能接受的类型
意味着register变量必须是一个单个的值,并且其长度应小于或等于整型的长度,而且register变量
可能不存在在内存中,所以不能用取地址运算符"&"来获取register变量的地址。
*/
#include <limits.h> register int i = 0;
while (i < INT_MAX) {
++i;
} /*
18) static
解释:
static用法很广,修饰变量,函数,从字面上看static 很安静,这个关键字在C++
中做了扩展,在C语言中重要就前面提到的两个作用。 1. 修饰变量
变量又分为局部变量和全局变量,但它们都在内存的静态区。 静态全局变量,作用域仅限于变量被定义的文件中,其他文件即使用extern声明也没有办法使用
准确的说:作用域是从定义之处开始,到文件结尾处结束, 静态局部变量:在函数体里面定义的,就只能在这个函数里用了,同一个文档中的其他函数也
用不了,由于被static修饰的变量总是存在内存的静态区,所有即使这个函数运行结束,这个
静态变量的值也不会被销毁,函数下次使用时任然能用这个值。 看下面一段代码: static int j;
void fun1(void) {
static int i = 0;
i++;
} void fun2(void) {
j = 0;
j++;
} int main()
{
int k = 0;
for(k = 0; k < 10; k++) {
fun1();
fun2();
} return 0;
} 此时i和j的值分别是多少?
我们来分析一下哈:
首先毫无疑问,j 是个全局静态变量,调用一次fun2()后,它的值始终没有变,所有调用10次值还是1 i这个变量是一个局部静态变量,值存放在内存的静态区,调用一次fun1()结束后它的值不会被销毁,
函数下次调用的时候任然使用这个值,所有调用10次它的值一次为,1,2,3,4,5,6,7,8,9,10,11 2. 修饰函数
函数前面加static使得函数成为静态函数,但此处"static"的含义不是指存储方式,而是指对函数作用域
仅局限于本文件(所有称内部函数)使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义
的函数是否会与其他文件中的函数同名。 // C99之后加的static新用法,编译器优化
/ static 只能修饰函数第一维,表示数组最小长度,方便编译器一下取出所有内存进行优化
*/
int sum(int a[static 10]) { ... } /*
19) extern
解释:
extern (外面的,外来的)可以置于变量或函数前,以表明变量或函数的定义在别的文件中
下面代码用到的这些变量或函数是外来的,不是本文件中定义的,提示连接器遇到此变量
和函数时在其他模块中解析/绑定此标识符。
*/ // 声明引用全局变量
extern int G_arg; // 声明引用全局函数,(主动声明,希望外部可以调用)
extern int kill(int sig, int val); // 当然有时候extern不写,对于变量不写会出现重定义,对于函数时可以缺省的 /*
20) break
解释:
结束语句,主要用于循环的跳出,只能跳出到当前层级,也用于switch语句中
跳出swithc嵌套
*/
// 演示 for循环
for(;;) {
// 符合条件跳转
if(n == 10)
break; // 跳出for循环
} /*
21) 22) 23) switch & case & default
解释:
21)switch :条件分支语句,很复杂的if else if时候可以用switch
22)case : 语句中分支语句,确定走哪个分支
23)default: switch 分支的默认分支,所有case都没有进入就到default分支
*/
// 演示 switch (errcode) {
case SSL_ERROR_ZERO_RETURN:
/* Possibly a clean shutdown. */
if (SSL_get_shutdown(bev_ssl->ssl) & SSL_RECEIVED_SHUTDOWN)
event = BEV_EVENT_EOF;
else
dirty_shutdown = 1;
break;
case SSL_ERROR_SYSCALL:
/* IO error; possibly a dirty shutdown. */
if ((ret == 0 || ret == -1) && ERR_peek_error() == 0)
dirty_shutdown = 1;
break;
case SSL_ERROR_SSL:
/* Protocol error. */
break;
case SSL_ERROR_WANT_X509_LOOKUP:
/* XXXX handle this. */
break;
case SSL_ERROR_NONE:
case SSL_ERROR_WANT_READ:
case SSL_ERROR_WANT_WRITE:
case SSL_ERROR_WANT_CONNECT:
case SSL_ERROR_WANT_ACCEPT:
default:
/* should be impossible; treat as normal error. */
event_warnx("BUG: Unexpected OpenSSL error code %d", errcode);
break;
} /*
24) continue
解释:
跳过此次(本轮)循环,直接进行条件判断操作,进入下一轮循环,
for和while循环有些区别,for会执行第三个后面的语句
*/
// 演示
for(int i = 0; i < 20; ++i) {
if(i % 2 == 0)
continue; // 满足if跳到 ++i,再到条件 i<20
} /*
25) do ... 26) while 27) for
解释:
C 语言中的三种循环
25) do: do循环,先执行循环体,在执行条件判断,先保证循环体执行一遍在判断条件
在一个菜单的程序设计的时候用do...while 比较好,首先给出选择。 26) while: 循环,先判断while后的条件,只有条件真才执行里面的代码块,
常用的还有就是一种死循环的写法 while(1) 27) for: 循环,for循环很容易的控制循环次数,多用于事先知道循环次数的情况下
*/
// 演示
/* do -while 循环 */
do {
event_loop(EVLOOP_ONCE | EVLOOP_NONBLOCK);
xcount++;
} while (count != fired); /* while 循环 */
while(1) {
if('#' == GetInputChar())
break;
} /* for 循环 */
for (i = 0; i < 25; i++) {
tv = run_once();
if (tv == NULL)
exit(1);
fprintf(stdout, "%ld\n",
tv->tv_sec * 1000000L + tv->tv_usec);
} /*
28) if ... 29) else
解释:
28) if: if分支语句,可以单独使用,可嵌套
29) else: else分支,必须和if分支对应,和if分支条件相反
*/
n = recv(fd, (char*)&ch, sizeof(ch), 0);
if (n >= 0)
count += n;
else
failures++;
if (writes) {
if (widx >= num_pipes)
widx -= num_pipes;
n = send(pipes[2 * widx + 1], "e", 1, 0);
if (n != 1)
failures++;
writes--;
fired++;
} /*
30) goto
解释:
关于goto这个关键字,褒贬很多,原因就在于它太*,可以灵活的跳转,在结构化
编程中它有了很多争议,如果不加以限制,它的*跳转的确会破坏结构化设计的风格
所有使用它一定要慎重,下面我们用一段真实代码展示它的魅力, */
// 演示
if (evbuffer_expand_fast_(buf, to_alloc, 2) < 0) {
goto done;
} for (n = 0; n < n_vec; n++) {
/* XXX each 'add' call here does a bunch of setup that's
* obviated by evbuffer_expand_fast_, and some cleanup that we
* would like to do only once. Instead we should just extract
* the part of the code that's needed. */ if (evbuffer_add(buf, vec[n].iov_base, vec[n].iov_len) < 0) {
goto done;
} res += vec[n].iov_len;
} done:
EVBUFFER_UNLOCK(buf);
return res; /*
31) return
解释:
return 用来终止一个函数并返回其后面跟着的值 使用return 不可返回指向“栈内存”的“指针”,因为该内存在函数体结束后就被自动释放了
*/
// 演示 #include <stdio.h> int main(int arg, char* argv[]) {
...... return EXIT_SUCCESS;
} /*
32) sizeof
解释:
也称为sizeof运算符,计算变量或类型的字节大小,它常被误认为是个函数
面试中也经常有它出现,下面我们看看它的用法
*/
// 演示 x86上
int *p = NULL;
sizeof(p) 的值是多少 ---> 4
sizeof(*p)呢? ---> 4 int a[100];
sizeof(a)的值是多少 ---> 400
sizeof(a[100])的值 ---> 4
sizeof(&a)的值 ---> 4
sizeof(&a[0])的值 ---> 4 int b[100];
void func(int b[100]) {
sizeof(b); // sizeof(b)的值 ---> 4
} // 常用一种写法获取数组长度
#define LEN(arr) (sizeof(arr) / sizeof(*(arr)))
#define LEN(arr) (sizeof(arr) / sizeof(arr[0]))

到此 C89 的32个关键字都就介绍完了,对于 C99 和 C11的关键字,后续完善.....

后记

《意颓废》

意颓废,人难寐。
夜夜长街空买醉。
路茫茫,断离肠。
梦牵魂索,独守西窗,伤!伤!伤 。

花儿碎,风流泪。
曲悲弦断连心肺。
恋成殇,心透凉。
一朝白头,只为情狂,怆!怆!怆。

回首C语言关键字(~回首向来萧瑟处~)

上一篇:OAF中为MessageTextInput添加加事件处理


下一篇:Python: 读文件,写文件