回首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的关键字,后续完善.....

后记

《意颓废》

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

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

原文地址:https://www.cnblogs.com/zhaoosheLBJ/p/9250912.html