学习 C 语言 - 笔记整理 (草稿)

Table of Contents

1 开始前

1.1 我们要做什么?

  • 将 脑子里想的东西 和 解决问题的方法 翻译成 程序
  • 程序 ?: 计算机可以运行的 机器指令 (二进制代码 类似100000111100000100000001)
    • 我们直接写机器指令 ?: bang… 脑袋炸了
    • 使用对应的 汇编指令 (addl $1, %ecx) ?: 好一点 但还是不要
    • 自然语言 ?: 喜欢 爱死了 !: 目前还不可能达到
    • 类似自然语言(高级语言) 限定的少量的词汇和语法 ?: OK
  • 三个问题
    • ? 用什么写程序
      • IDE (集成开发环境)
      • Editor (编辑器)
    • ? 怎么翻译 (高级语言 -> 机器指令)
      • 翻译(编译 或 解释)
        • 编译器
        • 编译流程自动化
    • ? 如何运行
      • 运行
      • 输入 输出
  • 脑子里的东西 和 解决问题的方法 怎么用 编程语言 写出来(表示) ?
    • 把 所想的东西 转换成 计算(算法, 步骤, 菜谱)
    • How?
  • 计算机技术 基础知识 等等 (不在此笔记中啰嗦)
  • 这里不关心 底层(计算机具体实现) 把它抽象掉(仅仅使用一些词语来代表)

1.2 准备

  • 开发环境
    • 编辑器或IDE : Emacs or Xcode
    • 编译器 : gcc or clang
    • 编译流程自动化 : make, Makefile
  • 流程示范: [OS:MacOS]
    • 打开 终端(Terminal)
    • 准备工作

      mkdir test # 建立 项目目录
      cd test # 进入 项目目录
      touch code.c # 新建 源文件
      
    • 用编辑器 打开源文件 写代码

      /* code.c */
      
      #include <stdio.h>
      int main(){
        printf("hello world");
      }
      
    • 编译 和 运行

      cc code.c -o code
      ./code
      
  • 注释

    // 单行 
    /*
      多行
    */
    

2 开始

2.1 第一步

  • ? 我想让程序 输出 一些文字
    • 文字 怎么表达 ?: 用 字符串

      "字符串"
      
    • 输出 ?: 输出函数 这里使用puts函数

      puts("字符串");
      
      • !: puts不是我们定义的 需要导入头文件 (放在开头)

        #include <stdio.h>
        
    • 写在哪 ?: main函数里 main()是程序执行的起点 这是约定

      #include <stdio.h>
      
      int main(){
        puts("字符串");
        
        return 0;
      }    
      

2.2 稍息

基础 就像是一块块不同的积木 我们要做的就是 把它们组合成 我们所希望的样子

一块积木是积木 两块积木组和起来也可被当作一个积木 给每块不同积木 或 组合后的积木 一个名字 就可以被方便的重复使用 我们编程就是 从符合规则的组合中 找到 能够解决待定问题的 一种最优的路径

已经准备好的积木 它们长啥样 怎么使用:

  • 数字 : 整数 浮点数
  • 简单的数学计算过程 : 四则运算符
  • 名字 : 变量 指针
  • 组合
    • 过程(按执行顺序)
      • 顺序 : 一块接一块
      • 分支 : 每一块积木 可以和其它积木进行组合 产生的每一种符合规则的可能 就只一条分支
      • 循环 : 重复积木
      • 跳转 (很少用) : 往回等同于循环 往后就像是选择了不同的分支
    • 聚合 : 数组 结构 共用 枚举
  • 封装过程(把组合起来的积木 给个名字 打包起来) : 函数 参数

2.3 第二步

  • ? 输入温度(华氏 或 摄氏) -转换- 输出结果
    • 把问题分解为更小的部分 可以理解 甚至是可以直接求解 一个个解决 最后组合在一起
    • 分成三部分: 输入 转换 输出
      • 输入 ?: 从终端 形如: 30C
      • 转换 ?: 公式: C = (5/9)(F-32) 和 F = (9C)/5+32
      • 输出 ?: 到终端 形如: 30C 是 86F
    • 继续 (程序名为: C52F)
      • 输入 ?: 从终端 形如: 30C
        • 从终端 ?: 程序执行时

          C52F 30C        
          
        • 程序内部如何处理 ?: 运行程序提供的参数 将作为参数交给 main函数

          // int argc, char *argv[]  : 参数个数 和 存储参数的数组
          int main(int argc, char *argv[]){
            return 0;
          }
          
        • 处理参数

          #include <stdio.h>
          #include <stdlib.h>
          #include <string.h>
          
          int main(int argc, char *argv[]){
            // 检查参数
            if(argc == 1){
              printf("请重新运行 并提供需要转换的温度(形如 30C 或 89F)\n");
              return 1;
            }
            // 获得参数
            char* tempStr = argv[1];
            // 转换参数 ?: 因为参数是字符串 为了计算 需要转换为 浮点数
            int loct = strlen(tempStr) - 1;
            char type = tempStr[loct];
            tempStr[loct] = '\0';
            double temp = atof(tempStr);
            
            return 0;
            
          }
          
      • 转换 ?:
        • 公式: C = (5/9)*(F-32) 和 F = (9C)*/5+32

          // C = (5/9)*(F-32)
          C = (5.0/9)*(F-32);
          // F = (9*C)/5+32
          F = (9*C)/5+32;
          
        • 分发温度

          switch (type) {
            case 'F': 
              // C =
              break;
            case 'C': 
              // F =
              break;
            default:
              printf("请重新运行 并提供需要转换的温度(形如 30C 或 89F)\n");
              return 1;
            }        
          
        • 结合

          char cvType = ' ';
          switch (type) {
           case 'F':
             cvTemp = (5.0/9)*(temp-32);
             cvType = 'C';
             break;
           case 'C':
             cvTemp = (9*temp)/5+32;
             cvType = 'F';
             break;
           default:
             printf("请重新运行 并提供需要转换的温度(形如 30C 或 89F)\n");
             return 1;
           }
          
      • 输出 ?: 到终端 形如: 30C = 86F

        printf("%s%c = %f%c\n", inTemp, intype, cvTemp, cvType);
        
    • 完成

      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      
      int main(int argc, char *argv[]){
        // 检查参数
        if(argc == 1){
          printf("请重新运行 并提供需要转换的温度(形如 30C 或 89F)\n");
          return 1;
        }
        // 获得参数
        char* tempStr = argv[1];
        // 转换参数 ?: 因为参数是字符串 为了计算 需要转换为 浮点数
        int loct = strlen(tempStr) - 1;
        char type = tempStr[loct];
        tempStr[loct] = '\0';
        double temp = atof(tempStr);
      
        // C = (5/9)(F-32) 和 F = (9C)/5+32
        double cvTemp = 0;
        char cvType = ' ';
        switch (type) {
        case 'F':
          cvTemp = (5.0/9)*(temp-32);
          cvType = 'C';
          break;
        case 'C':
          cvTemp = (9*temp)/5+32;
          cvType = 'F';
          break;
        default:
          printf("请重新运行 并提供需要转换的温度(形如 30C 或 89F)\n");
          return 1;
        }
        printf("%d%c = %f%c\n", (int)temp, type, cvTemp, cvType);
        return 0;
      
      }
      

!!! 干脆做视频 动态的表示 会更好 写出来太麻烦了

3 基础

3.1 类型

  • ? 类型 : 代表 分配的内存空间大小 将实际大小进行抽象
  • 类型
    • 基本
      • 整 : int (short, long) ([un]signed 默认有符号)
      • 字符 : char ([un]signed 依实现)
      • 浮点 : float, double (long double)
      • 空 : void
      • 枚举 : enum
      • 指针
      • 函数
    • 派生
      • 数组
      • 结构
      • 共用
  • Modifiers 修饰符 : unsigned, signed, short, long

3.1.1 表示范围

  • <limit.h>
    • char (编译器为 无|有符号)
      • CHARMIN == 0|SCHARMIN
      • CHARMAX == UCHARMAX|SCHARMAX

3.1.2 定长整型

  • <stdint.h>
  • [u]int(BITS)t : int8t, uint8t, …
  • [U]INT(BITS)MAX, …
  • intleast(BITS)t, …

3.1.3 类型别名

  • Typedef

    typedef unsigned char byte;
    
    int* a[10];
    //
    typedef int* t; // t 代表 int *类型
    t a[10];
    
    int (*a)[10];
    typedef int t[10]; // t 代表 10个int组成的数组类型
    t *a;
    
    typedef struct {
      int x;
      int y;
    }point;
    point pa, pb;
    
    // 函数
    typedef void (*func)(int);
    void some(func f);
    
    // 不同之处
    #define iptr int*
    iptr a, b; // int* a, b; a和b不同类型
    typedef int* iptr;
    iptr a, b; // a和b 同型
    

3.2 变量

  • 变量 : 内存地址 编译时将变量名替换为地址
  • Identifier 标识符 : 名字
    • 数字:0~9 大小写:A~Za~z 下划线:_ (非数字开头)
  • 作用域 生命周期
    • 作用域 : 全局 文件内静态 局部({}块内)
      • 文件内静态 : 全局变量 前加上static 则作用域只在当前文件内
    • 存储期 : 静态 自动 动态
      • 静态 : 全局变量 指定static的变量 从程序开始到结束
      • 自动 : 其它变量 生存在{}块内 (栈实现)
      • 动态 : 从 malloc()分配 到 free()为止
      • 存储类型修饰符
        • auto (过时)
        • static
        • register
        • extern : 使用 其它文件中定义的 变量或函数
  • 全局 和 静态 变量 自动初始化 为 0或NULL
  • Qualifiers 限定符
    • const : 常量
      • 指针
        • 指向常量的 : const int *a; == int const *a; : 指向const int型的指针 *a不可 a可 : 指向的值不可变
        • 指向非常量的常量 : int * const a; : 指向int型的const指针 *a可变 a不可 : 指向的值可变
        • 指向常量的常量 : int const * const a; : 都不允许改写
    • volatile : 不稳定 变量值可能被其它例程改变
    • register : 强制变量存放到寄存器
    • restrict : 声明时 告诉编译器 指针没有别名

        void add(int size, double* restrict arr1, const double* restrict arr2)  {}
        double v1[] = {1.0, 2.0};
        double v2[] = {1.0, 2.0};
        add(2, v1, v2);
      
        double* v3 = v1;
        add(2, v1, v3); // Error
        
      

3.3

  • 整数

    123
    -789
    
    // 进制表示法
    31 // 十进制
    025 // 八进制 0开头
    0x19 // 十六进制 0x开头
    
  • 浮点数(小数)

    123.3
    .3, 3. // 省略部分
    1.2e2, 1.2e-2 // 科学计数法
    1.32 // 默认 double类型
    1.32f // 指明是 float类型 
    

3.4 真值

  • 非0即真

3.5 字符

  • 字符 : 'a'
  • 内部表示 : 整数
    • ASCII编码 ?
      • 编码 ?
  • 特殊字符(转义)

    \a alert
    \b backspace
    \t horizontal tab
    \n new line
    \v vertical tab
    \f form feed
    \r carriage return
    \[n] 八进制
    \x[n] 十六进制
  • printf("%d\n", 'n');       //-> 110
    printf("%d\n", '\n');      //-> 10
    printf("%c\n", '\156'); //-> n
    
    printf("%d %d %d", '0', '\0', 0 ); //-> 48 0 0
    

3.6 数组

  • 声明和初始化

    /// 一维
    int c[3] = {1, 2, 3};
    int c[] = {1, 2, 3}; // 可省略 元素个数  
    int c[3] = {1}; // 整数自动补0 字符补\0
    
    /// 二维
    int a[2][2] = {1,2,3,4};
    int a[][2] = {1,2,3,4}; // 可省略第一维元素个数
    int a[][2] = { { 1, 2 },
                   { 3, 4 },
                   { 5, 6 } };
    int a[2][2] = {{1} {2}}; // 部分
    
    /// 更高维度 类似 : 数组的 数组的 数组 ...
    
    
  • 注意

    // 声明后 大小 不可变  没有边界检查
    
    // 声明和初始化 不能分开, 不能通过 赋值 进行初始化
    int c[3];
    c = {1,2,3}; // Error
    
    // Memberwise Initialization (C99)
    int a[3][2] = { [0][1] = 9, [2][1] = 8 };  
    

3.7 字符串

  • 字符串 : '\0' 结尾的字符序列
  • 如何表示 ?: 字符 数组|指针 或 字面量(读入自动转换)

    char str[] = {'a', 'b', 'c', '\0'};
    char str[] = "abc";
    char s[] = "a\0bc"; //-> a
    
    char* str = "abc";
    
    // 空字符串
    char str[] = ""  
    char str[] = {'\0'} //== {0}
    

3.8 指针

  • 声明 定义 使用
  • 计算 : ptr+N == 当前指针的数据类型的长度 乘 N

3.9 数组 与 指针

  • 数组名(数组变量) : 数组的起始地址 (使用&运算符 结果是其本身 &s == s)
    • 不能指向其他地方 即不接受赋值操作
      • 不分配任何空间 编译器在出现它的地方把它替换成数组的起始地址
    • 不同维
      • int a[10]
        • a : int类型的指针
        • &a : 10个int数组的指针
      • int arr[rows][cols]
        • arr : cols个 int数组的 指针
        • &arr : rows*cols个 int数组的 指针
  • 退化(信息的丢失) : 数组赋给指针变量 指针变量只会包含数组的起始地址
  • 数组 还是 指针 ?: 函数参数的形式声明 则为指针
  • [] 语法糖 : p[i] -> *(p+i)
    • p[i] == i[p] (可以 但不要这么做)

Example

#include <stdio.h>

int main(){
  int a[3] = {0, 1, 2};
  printf("%p, %p\n", a, &a); 
  printf("%p, %p\n", a+1, (&a)+1);
  // | 0x7fff4fc1090c     | 0x7fff4fc1090c      |
  // | 0x7fff4fc10910 | 4 | 0x7fff4fc10918 | 12 |

  int (*p)[3] = a;
  printf("%d, %d\n", p[0][0], p[0][1]); // == *((*p)+1)) == (*p)[1] 
  p = &a;
  printf("%d, %d\n", p[0][0], p[0][1]);
  // 0 1
  
  int a2[2][3] = {{1, 3, 2}, {5, 6, 7}};
  printf("%p, %p\n", a2, &a2); 
  printf("%p, %p\n", a2+1, (&a2)+1);
  // | 0x7fff5a5c68f0               | 0x7fff5a5c68f0                 |
  // | 0x7fff5a5c68fc | 3x4(int)=12 | 0x7fff5a5c6908 | 2x3x4(int)=24 |
  printf("%d, %d\n", 0xfc-0xf0, 0x908-0x8f0);
  
  p = a2;
  printf("%d, %d\n", p[0][0], p[0][2]); // 1 2
  printf("%d, %d\n", p[1][0], p[1][2]); // 5 7

  p = &a2; 
  printf("%d, %d\n", p[0][0], p[0][2]); // 1 2
  printf("%d, %d\n", p[1][0], p[1][2]); // 5 7

  return 0;
}

3.10 运算符

  • , : 逗链的值 为 最后表达式的值
    • a=(1+2, 3+3, 3+8); // a=11
  • Assignment 赋值
    • = (Assign equal)
    • 复合赋值 : +=(Assign equal) 等等
  • 二元操作符 注意: a<b<c 与 a<b && b<c 不同

3.10.1 Arithmetic 算术

  • + Add
  • - Subtract
  • * Multiply
  • / Divide
  • % Modulus
    • 正 % 正 -> 正 (其它取决于 编译器)
  • ++ Increment
  • – Decrement

3.10.2 Bitwise 位运算

  • 逻辑
    • & (and 与)
    • | (or 或)
    • ^ (xor 异或)
    • ~ (one's complement 取反)
  • 位移
    • << (shift left 左移)
    • >> (shift right 右移)
      • 算术右移: 补1
      • 逻辑右移: 补0

3.10.3 Logical 逻辑

  • && (And)
  • || (Or)
  • ! (Not)
  • ?: (Ternary)

3.10.4 Relational 关系 (比较)

  • == (Equal)
  • != (Not equal)
  • > (Greater than)
  • < (Less than)
  • >= (Greater than equal)
  • <= (Less than equal)

3.10.5 Data 数据

  • sizeof()
  • [] : array subscript
  • & : address
  • * : value of 解引用 (for pointer)
  • -> : Structure dereference (for pointer)
  • . : Structure reference
  1. sizeof
    • sizeof : 在存储器中占 多少字节 (编译器 根据 类型的大小 或 变量声明的类型的大小 来返回相应的值)
      • 只是 向编译器询问 所以只在编译器知道对象大小的情况下使用
    • 参数: 类型名|变量名 Ex.(sizeof(char))
    • 返回值: sizet (unsigned int)
    • Ex.

      sizeof(int) // 4 
      sizeof(3) // 4 (3 -> int)
      sizeof('a') // 4 int
      
      /// String
      sizeof("Turtles!") // 9 == 8个字符 + '\0'
      
      /// Array
      // sizeof(数组名) -> 数组的大小 包括'\0'
      char i[] = {1,2,3}; sizeof(i);    // 3
      char s[5] = "How?"; sizeof(s);    // 5 
      // sizeof(指针名) -> 4|8 (bit:32|64)
      char *t = s; sizeof(t);  
      
      char ssa[][4] = {"abc","def","ghi"};
      sizeof(ssa); // 12
      sizeof(ssa[0]); // 4
      sizeof(ssa)/sizeof(ssa[0]); // length == 3
      
      char **ss = {"abc","def","ghi"};
      sizeof(ss);     // 8
      sizeof(*ss);     // 8
      
      

3.11 控制流程

  • 顺序 分支 循环(递归)
  • 条件分支 : if switch
    • switch
      • 条件: 整型表达式(可包含: 运算符 函数调用)
      • case : 整型常量(常量表达式 常量运算)
      • break
  • 循环 : while do-while for
  • 跳转 : goto (label)

3.12 函数

  • 把 部分程序 封装起来 并 给一个名字 方便重复使用
  • 定义
  • 调用
    • 在调用之前 先定义 或 显示声明
    • 参数的 计算顺序 不确定 (依赖:编译器)
  • 返回值

3.12.1 函数指针

Example

int (*name)(int, int) = someFunc;
name(1, 2);

// name == &name

typedef int (*f_name) (int, int);
f_name name = someFunc;

// 函数指针数组
int (*names[12])(int, int) = {NULL};
//or:
typedef int (*name) (int, int);
name names[12] = {NULL};

3.13 结构 共用 枚举

3.13.1 Enum 枚举

  • 定义 声明 使用

    enum animal { Dog, Cat, Monkey, Invalid }; // Dog == 0
    enum animal { Dog=3, Cat, Monkey, Invalid }; // Dog == 3, Cat == 4
    
    
    emu animal selected;
    
    switch (selected = select()){
      case Dog : dog(); break;
      case Cat : cat(); break;
      case Monkey : monkey(); break;
      default error();
    }
    

3.13.2 Struct 结构

  • 值类型 : 赋值时拷贝
  • 声明 定义 初始化 使用

    struct point {
      int x;
      int y;
    };
    struct point pa, pb;
    //
    struct point {
      int x;
      int y;
    }pa,pb;
    //  
    typedef struct {
      int x;
      int y;
    }point;
    point pa, pb;
    
    // 初始化
    struct point pa = {3, 5};
    struct point pa = {.x=3, .y=5}; // [C99]
    // 使用
    pa.x = 3;
    // 结构指针
    struct point *pc = &pa;
    (*pc).x
    pc->x
    
    
  1. 结构数组
    struct complex_struct {
        double x, y;
    } a[4];
    
  2. bitfield 位段(位域)
    struct Name {
      unsigned vname1:bit1;
      unsigned vname2:bit2;
      // ...
    };
    
    //
    struct record{
      char* name;
      int refcount : 4;
      unsigned dirty : 1;
    };
    
  3. 包含自指向的指针
    // Errro
    typedef struct {
      char* item;
      Node next;
    }* Node;
    
    // ->
    typedef struct node {
      char* item;
      struct node* next;
    }* Node;
    // or
    struct node;
    typedef struct node* Node; 
    struct node {
      char* item;
      Nodenext;
    };
    // or
    struct node {
      char* item;
      struct node* next;
    };
    typedef struct node* Node;
    
  4. 域 在 结构中的字节偏移量
    • <stddef.h> : offsetof() : offsetof(structs, f)
    • Imp

      // (&0)->f - 0
      #define offsetof(type, f) ((size_t)             \
        ((char*) &((type*)0)->f - (char*)(type*)0))
      

3.13.3 Union 共用

  • 声明 定义 初始化 使用

    union Data {
      int i;
      char c;
    };
    union Data some = {88}; // 初始化 需要与第一个类型一致
    printf("Int:%d", some.i);
    printf("Char:%c", some.c);
    
    

3.14 动态内存分配

  • 在堆上分配空间
  • 函数
    • 分配: malloc, calloc, realloc,
      • 一段空间 : void *malloc(sizet size);
      • 一段空间 并 清零 : void *calloc(unsigned n, unsigned size); (空间长度: n*size)
      • 重新分配 (变大或变小) : void *realloc(void *ptr, sizet size); (将ptr指向的空间大小 改为size)
    • 释放: free
      • void free(void *ptr);
    • 填充: memset
      • memset(ptr, n, size);
  • 示例

    int *p = (int*)malloc(sizeof(int));
    free(p);
    
    int *array = (int*)calloc(3, sizeof(int));
    free(array);
    
    double *d = (double*)malloc(sizeof(double));
    int *i = (int*)realloc(d, sizeof(int));
    free(d);
    free(i);
    
    

4 高级

4.1 IO

4.1.1 标准输入输出

  • 字符 : getchar, putchar
  • 字符串 : gets, puts
  • 格式化 : scanf, printf, sscanf, sprintf

4.1.2 文件

  • <stdio.h>
  • 表示 : FILE型
    • stdin, stdout, stderr 都是指向 FILE型 的指针
  • 开关: fopen, fclose
    • 方式 : r, w, a, rb, wb, ab, r+, w+, a+, rb+, wb+, ab+
      • r|rb (read) : 读 文件需存在
      • w|wb (write) : 写 文件需不存在
      • a|ab (append) : 追加 文件需不存在
      • r+|rb+ : 读 写从头
      • w+|wb+ : 读 写覆盖整个文件
      • a+|ab+ : 读 追加写
    • Ex.

      FILE *fp;
      // 打开 : fopen(fname, mode)
      fp = fopen("abc", "r");
      
      if(fp == null)
        error();
      else
        // 关闭
        fclose(fp);
      
  • 读写 : fscanf, fprintf; fgetc, fputc, fgets, fread, fwrite
    • Ex.

      /// 字符读写
      // 单个字符
      fgetc(fp);
      fputc('a', fp);
      // 格式化 读写
      fscanf(fp, "%d", &x);
      fprintf(fp, "%d", x);
      
      /// 二进制读写
      int x = 3;
      fwrite(&x, sizeof(int), 1, fp);
      fread(&x, sizeof(int), 1, fp);
      int a[10];
      fwrite(a, sizeof(int), 10, fp);
      fread(a, sizeof(int), 10, fp);
      
  • 定位 : fseek, ftell, rewind
    • fseek(文件指针, 移动的字节数(字面量时需加后缀"L"), 起始点(首:SEEK-SET:0, 当前:SEEK-CUR:1, 尾:SEEK-END:2))
    • ftell : 当前位置(位移量)
    • rewind : 文件的位置指针 重回文件开头

4.2 Process

  • fork, waitpid, exec
  • signal

4.3 String

  • <string.h>
  • 长度 : strlen
  • 比较 : strcmp, strncmp, memcmp (返回值: +:s1>s2 -:s1<s2, 0:s1==s2, )
  • 复制 : strcpy(D,S), strncpy, memcpy, memmove, strdup
  • 连接 : strcat(D,S), strncat
  • 搜索 : strchr, strrchr, strstr, index, rindex
  • 填充 : memset
  • 分割 : strtok, strtokr
  • 转换
    • 大小写 : strupr strlwr
    • ->(int|long, double) : atoi, atof

4.4 预处理

  • 预定义名
    • _LINE__ : 当前编译行的行号
    • _FILE__ : 文件名
    • _DATA__ : 程序创建日期
    • _TIME__ : 程序创建时间
    • _STDC__ : 当前编译器 是否符合标准C 是:1
  • 头文件 : #include : 会读取头文件中内容 插入主文件
    • <> : 在存放 C库函数头文件 的目录 中找
    • "" : 优先在当前目录找 (或指定路径)
    • 创建 : #define
    • 撤销#define的定义 : #undef
    • 编译前被替换掉 运行时无法改变
    • 对象式宏(变量式)

      #define PI 3.14    
      
    • 函数式宏

      // 最外层括号 用于 保证优先级
      #define ADD_ONE(x) ((x) + 1)  // ((3) + 1) <- ADD_ONE(3)
      
      #define putsa(str) ( putchar('\a'), puts(str); )
      if (na)
        putsa("a");
      // -> ( putchar('\a'), puts("a") );
      
      //
      #define device_init_wakeup(dev,val)             \
        do {                                          \
          device_can_wakeup(dev) = !!(val);           \
          device_set_wakeup_enable(dev,val);          \
        } while(0)
      
      // 带可变参数 : ... 和 #__VA_ARGS__ (? ##__VA_ARGS__)
      #define showlist(...) printf(#__VA_ARGS__)
      
      • 规则
        • 1 外部使用括号 保证优先级
        • 2 内部 所有参数出现的地方 使用括号
        • 3 出现多次 要考虑 可能被多次求值 是否有副作用
    • 运算符 : #, ## , defined()

      // #运算符 : 用于创建字符串 (只用于 函数式宏)
      // #运算符后面应该跟一个形参 中间可有空格或Tab    
      #define STR(s) #s
      STR(hello   world) //  "hello   world"
      STR(123\\12) // "123\12"
      
      // ##运算符 : 把前后两个预处理Token 连接成一个
      #define CONCAT(a, b) a##b
      CONCAT(con, cat)
      
      // 展开成两个#号
      #define HASH_HASH # ## #
      
  • 条件编译 : #if, #else, #elif, #ifdef, #ifndef, #endif

    #ifdef SPANISH
    char *greeting = "Hola";
    #else
    char *greeting = "Hello";
    #endif
    // SPANISH宏定义与否会改变这段代码的编译方式
    
    // defined() : 预处理操作符
    #if !defined(AIR)
    #endif
    
    #define RED 1
    #define BLUE 2
    
    #if COLOR == RED
    //...
    #elif COLOR == BLUE
    //...
    #endif
    
  • 改变行号 : #line

    // #line 行号["文件名"]
    #line 30"text.c"
    printf("%d", __LINE__); // 30
    
  • 设定编译器状态 : #progma 参数
    • message参数 : 编译信息输出窗口中输出的相信
    • codeseg : 设置程序中 函数代码 存放的代码段
    • once : 保证头文件被编译一次
  • 编译器报错退出 : #error : #error UNKNOWN TARGET MACHINE
    • 编译器遇到这个预处理指示就报错退出 错误信息就是UNKNOWN TARGET MACHINE
  • #warning

5 概念

  • 头文件
    • 导入
  • 字面量
  • 作用域 生命周期
  • 声明 定义
  • 关键字 保留字
  • 变量

5.1 声明

  • 理解
    • 从 名字 开始读 然后 根据优先级顺序读
    • 优先级 从 高到低
      • 括号内
      • 后缀操作符
        • () : 函数
        • [] : 数组
      • 前缀操作符 : * : 表示 指向 某某 的指针
    • const, volatile 后跟
      • 类型说明符 则作用于 类型说明符
      • 其它情况 作用于 左边紧邻的指针星号
  • Ex.
    • char * const *(*next)()
      • 名字 -> 变量名 next
      • 括号内 -> next 是 一个指针
      • 后缀 -> next 是 一个 函数 指针
      • 前缀 -> next 是 一个函数指针 返回 一个指针
      • 剩下 -> next 是 一个函数指针 返回一个指针(指向 一个 类型为char 的常量指针)
    • int (*hoge)[3] : hoge是指针 指向int数组(3个元素)