开篇废话:
本文意在回顾 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的关键字,后续完善.....
后记:
《意颓废》
意颓废,人难寐。
夜夜长街空买醉。
路茫茫,断离肠。
梦牵魂索,独守西窗,伤!伤!伤 。
花儿碎,风流泪。
曲悲弦断连心肺。
恋成殇,心透凉。
一朝白头,只为情狂,怆!怆!怆。