[读] C和指针 (Ch11 ~ Ch14)

Chapter 11

  • mallocfree维护一个内存池数组

    • malloc老是分配一整块内存。根据编译器的实现,实际分配的内存也有可能比请求的稍大一些
    • 可用内存池没法知足请求时,malloc先向OS申请新的内存,仍是不够时返回NULL。所以malloc返回的指针必须先检查
    • free的参数必须为经过malloc等函数申请到的内存指针或NULL,它也老是释放整个分配到的内存
  • calloc:返回指针前将整块内存都初始化为0
  • reallocbash

    • 用于(在尾部)扩大或缩小一块已经分配到的内存,能够被用于有效地释放一块内存的尾部空间
    • 原先的内存没法修改大小时realloc可能返回一块新内存的指针(原先的内容会被复制过去),所以realloc后必须更新指针
    • 第一个参数为NULL时,与malloc行为相同

Chapter 12

  • 能够额外使用一个根结点来避免插入发生在链表头部以致于须要修改根结点的值。对于双链表,额外的根结点还能够同时维护链表的头部和尾部
  • 不过当一个节点自己的体积过大(好比存有大块数据)时,这种方式会形成浪费

Chapter 13

  • ⚠️函数指针(这里过重要了吧!!)函数

    此处重点在于函数调用符(即函数名后的括号)的优先级比间接访问要高,所以工具

    int *foo(); // foo是一个返回int*指针的函数

    foo优先与函数调用符结合,成为一个函数,而后对该函数的值(即返回值)进行间接访问获得int,可据此推论函数返回值为int*测试

    int (*foo)(); // foo是一个返回int的函数的指针

    foo被迫先与间接访问符结合,也就是说*foo做为总体与函数调用符结合,即*foo为函数,则foo为函数指针命令行

    int (*foo[])(); // foo是一个函数指针数组

    foo先与[]结合,说明foo是一个数组名,接着foo[]*结合,说明foo中的元素为某种指针,最后*foo[]与函数调用符()结合,并参照类型声明为int,说明元素为返回int值的函数指针(晕了。。)debug

  • 函数名在表达式中的角色与数组有些相似,也是以相似指针常量的形式出现的,所以指针

    void f(int arg); // f为接收1个int参数,无返回值的函数
    
    void (*fPtr)(int arg) = f; // 函数名能够做为指针被直接复制给函数指针
    void (*fPtr)(int arg) = &f; // 也能够先取址再赋值给函数指针

    事实上,&只是显式地执行了编译器将隐式执行的工做code

  • 转移表(jump table)常经过函数指针数组实现,此时若发生数组越界等问题debug将会很是艰难。。
  • 命令行参数递归

    • argc:参数个数(即argv数组的长度)
    • argv:字符串指针数组(命令行中空格分隔的每一截都为一个参数),一般经过检查字符串开头是否为-来识别选项参数
  • 字符串常量自己其实就是一个常量指针

    char *strPtr = "abcdefg";
    
    "abcdefg"[2] == 'c'; // true
    "abcdefg" + 2 == strPtr + 2; // true,所以它也能够做为一个字符串指针在printf里直接用%s输出

Chapter 14

  • 预约义符号(不是预处理指令!)

    • __FILE__%d源代码名(test.c
    • __LINE__%d文件当前行号(25
    • __DATE__%s文件被编译的日期(Jan 31 1997
    • __TIME__%s文件被编译的时间(13:07:02
    • __STDC__%d编译器是否遵循ANSI C标准(1 / 0
  • ⚠️带参数宏

    #define macro(arg1, arg2) stuff // 参数列表的左括号必须紧邻宏名,不然就会被认为是stuff的一部分

    (之前对宏有误解,觉得是单纯的把一段文本替换成另外一段文本,而实际上宏是先被按照符号解析再转换为文本进行替换的)

  • 宏很容易产生歧义,要格外注意括号和分号的使用
  • 宏参数和#define定义能够包含其余#define定义的符号,但宏不能够递归(大概是由于没有调用栈这种东西)
  • 利用printf会将直接相邻的字符串链接起来的特性,能够实现简单的输出包装:

    #define PRINT(FORMAT, VALUE) printf("This value is "FORMAT".\n", VALUE) // 注意没有分号!
    
    PRINT("%d", x + 4); // 则FORMAT为"%d",VALUE为x + 4
    // 实际被转换为:
    printf("This value is ""%d"".\n", x + 4); // 三个字符串被自动链接
  • ⚠️符号链接符##:产生的新符号必须合法,不然就是undefined行为

    #define ADD_TO_SUM(sum_number, value) sum##sum_number += value // 注意没有分号!
    
    ADD_TO_SUM(5, 23); // 5将做为sum_number的值被链接到sum,产生sum5这个符号,最后整个宏被替换为sum5 += 23;
    // 宏被引用时上下文中必须存在名为sum5的变量
  • ⚠️宏接收参数不指定类型,所以甚至能接受类型自己为参数

    (这就是为何sizeof不是一个函数吧,由于类型自己不能做为参数传递,以及可变参数列表也是用宏来实现)

  • 带反作用的宏参数可能致使严重错误,好比:

    • ++ / --:改变参数自己
    • getchar():消耗I/O buffer
  • 由于宏是被直接替换的,因此反复使用同一个宏也老是生成新的代码。而函数则每次在调用时使用同一份代码
  • 命令行定义:

    • -D{name} / -D{name}=stuff === #define name stuff (未指定时stuff默认为1)
    • -U{name} === #undef name
  • 条件编译:#if / #endif / #elif / #else

    • 条件表达式将由预处理器进行求值,所以它们的值必须是在编译时就能肯定的常量
    • 测试符号是否已被定义:

      • #ifdef symbol === #if defined(symbol)
      • #ifndef symbol === #if !defined(symbol)
    • ⚠️能够结合命令行定义来完成条件编译

      • 源代码test.c

        #ifndef CHOICE // 以避免与命令行定义冲突
            #define CHOICE 5
        #endif
        
        int main() {
            printf("Choice: %d\n", CHOICE);
            return 0;
        }

        直接编译执行:

        > gcc test.c -o test
        > ./test
        > Choice: 5

        编译时使用命令行定义:

        > gcc -DCHOICE=2 test.c -o test # 提早定义CHOICE为5
        > ./test
        > Choice: 2
    • 能够在#endif后用注释标注它结束的是哪个条件以提升程序可读性
    • ⚠️使用条件编译来避免因为头文件被重复包含致使的符号冲突

      #ifndef _HEADERNAME_H
      #define _HEADERNAME_H 1 // 若是没有1则_HEADERNAME_H被定义为空字符串,但仍然被定义,不会影响判断结果
      #endif
      • 尽管有办法防止冲突,多重包含仍然会拖慢编译速度,应被尽力避免
      • 但也不该该为了不多重包含而依靠头文件本身嵌套包含,这样会导在使用make等工具时很难判断文件间的依赖关系
    • #progma指令是不可移植的,不一样平台的预处理器可能会彻底忽略它,或以不一样的方式执行它
相关文章
相关标签/搜索