• 《C和指针》读书笔记



    看过了经典的K&R C,又看了这本Pointers on C,温习了C语言的基本语法。

    在重温过程中,感觉需要重点把握的知识是指针、结构和动态内存分配

    这对今后的算法和操作系统方面的研究学习很有帮助。


    3.2.3 声明指针

    int* b, c, d;

    本以为这条语句把三个变量声明为整型的指针,但事实并非如此。
    星号*只对b有用,其余两个变量只是普通的整型。正确语句:int *b, *c, *d;


    3.3 typedef

    允许为各种数据类型定义新名字。
    #define无法正确地处理指针类型,如下:
         #define d_ptr_to_char char *
         d_ptr_to_char a, b;
    只正确地声明了a,但是b却被声明为一个字符。


    3.4 常量

    int const *pci;     // 一个指向整型常量的指针
    int * const cpi;    // 一个指向整型的常量指针


    5.4.4 优先级和求值的顺序

    两个相邻操作符的执行顺序由它们的优先级决定。
    如果它们的优先级相同,它们的执行顺序由它们的结合性决定。
    除此之外,编译器可以自由决定使用任何顺序对表达式进行求值。


    5.8 问题

    2.下面程序的结果是什么?

    int func(void)
    {
         static int counter = 1;
         return ++counter;
    }

    int main()
    {
         int answer;
         answer = func() - func() * func();
         printf("$d\n", answer);
    }


    6.13 指针运算

    当一个指针和一个整数执行算术运算时,整数在执行加法运算前始终会根据合适的大小进行调整。
    即把整数量乘以指针所指向类型的大小。

    指针 +/- 整数

    指针 - 指针
    只有当两个指针都指向同一个数组中的元素时,才允许从一个指针减去另一个指针。

    < <= > >=
    可以在两个任意的指针间执行相等或不等测试。


    7.2 函数声明

    如果没有关于调用函数的特定信息,编译器便假定在这个函数的调用时参数的类型和数量是正确的。它同时会假定函数将返回一个整型值。如果编译器认定函数返回一个整型值,它将产生整数指令操纵这个值。
         float f;
         f = xyz();
    xyz的返回值会被假定为整型,当成整型返回然后转换成浮点型。


    7.5 递归

    许多教科书都把计算阶乘和斐波那契数列用来说明递归,这是非常不幸的。在第1个例子里,递归并没有提供任何优越之处。在第2个例子中,它的效率之低是非常恐怖。


    8.1 一维数组

    对于int类型的数组,数组名的类型就是“指向int的常量指针”。
         int a[10];
         int *c;
         a = c;     // error

    int ap[10];

    ap:指针变量中的值,即数组的起始地址。
    *ap:ap[0]
    ap[0]
    ap + 6:数组地址加6,即第6个元素的地址。
    *ap + 6:ap[0]+6
    *(ap + 6):ap[6]


    9.2 字符串长度

    size_t strlen(char const *string);

    注意strlen返回一个类型为size_t(无符号整型)的值。在表达式中使用无符号数可能导致不可预料的结果。
         if ( strlen(x) >= strlen(y) ) ...
         if ( strlen(x) - strlen(y) >= 0) ...
    第1条语句能按照你预想的那样工作,第2条语句的结果将永远为真。>=左边的表达式将是无符号数,而
    无符号数绝不可能是负的。
    类似的:
         if ( strlen(x) >= 10) ...
         if ( strlen(x) - 10 >= 0) ...
    原因同上。


    9.3 不受限制的字符串函数

    复制:char *strcpy(char *dst, char const *src);
    拼接:char *strcat(char *dst, char const *src);
    比较:int strcmp(char const *s1, char const *s2);
    注意常见错误:if(strcmp(a, b))。以为如果两个字符串相等,它的结果将是真。恰恰相反。


    9.5 字符串查找基础

    查找一个字符
    char *strchr(char const *str, int ch);
    char *strrchr(char const *str, int ch);

    char string[20] = "Hello there, honey."
    char *ans;
    ans = strchr(string, 'h');     // ans=string+7

    查找任何几个字符
    char *strpbrk(char const *str, char const *group);

    ans = strpbrk(string, "aeiou");     // ans=string+1

    查找一个子串
    char *strstr(char const *s1, char const *s2);
    这个函数在s1中查找整个s2第1次出现的起始位置,并返回一个指向该位置的指针。


    9.9 内存操作

    void *memcpy(void *dst, void const *src, size_t length);
    和strn开头的函数不同,它们在遇到NUL字节时并不会停止操作。

    char temp[SIZE], values[SIZE];
    memcpy(temp, values, SIZE);     // 复制char数组

    int temp[SIZE], values[SIZE];
    memcpy(temp, values, sizeof(values));     // 复制int数组要考虑移植性

    int temp[5], values[SIZE];
    memcpy(temp, values, 5 * sizeof(values[0]));     // 复制int数组的前5个元素


    10.1 结构基础知识

    struct SIMPLE {
         int         a;
         char      b;
         float      c;
    };
    struct SIMPLE x;
    struct SIMPLE y[20], *z;

    也可以将结构创建成一种新的类型。

    typedef struct {
         int      a;
         char    b;
         float    c;
    } Simple;
    Simple x;
    Simple y[20], *z;


    结构的自引用
    struct SELF_REF2 {
         int     a;
         struct SELF_REF2 *b;
         int     c;
    } A;

    如果声明struct SELF_REF2 b;则此结构定义是非法的。因为编译器在结构的长度确定之前就已经知道指针的长度,所以声明成指针才是合法的。

    访问方式:*(A.b).a 或 A->b.a


    10.3 结构的存储分配

    struct ALIGN {
         char a;
         int    b;
         char c;
    };

    编译器按照成员列表的顺序一个接一个地给每个成员分配内存,可以在声明中对结构的成员列表重新排列。

    struct ALIGN2 {
         int    b;
         char a;
         char c;
    };


    10.4 作为函数参数的结构

    void f(struct ALIGN a);     // 调用函数时要拷贝整个结构到栈中
    void f(struct ALIGN *a);    // 只传递4字节的指针


    10.5 位段

    struct CHAR {
         unsigned ch : 7;
         unsigned font : 6;
         unsigned size : 19;
    } ch1;

    size位段过大无法容纳于一个短整型,但其余位段都比一个字符还短。
    位段使程序员能够利用存储ch和font所剩余的位来增加size的位数,避免声明
    一个32位的正数来存储size位段。

    访问磁盘控制器的例子,假设其地址为0xc0200142。

    struct DISK_REGISTER_FORMAT {
         unsigned     command     : 5;
         unsigned     sector          : 5;
         unsigned     track            : 9;
         ...
         unsigned     ready           : 1;
    };

    #define DISK_REGISTER ((struct DISK_REGISTER_FORMAT *) 0xc0200142)

    // 告诉控制器从哪个扇区哪个磁道开始读取
    DISK_REGISTER->sector = new_sector;
    DISK_REGISTER->track   = new_track;
    DISK_REGISTER->command = READ;

    while ( !DISK_REGISTER->ready)
         ;


    10.6 联合

    union {
         float     f;
         int        i;
    } fi;

    fi.f = 3.14159;
    printf("%d\n", fi.i);

    首先把π的浮点表示形式存储于fi,然后把这些相同的位当做一个整型值打印输出。


    11.1 - 3

    malloc:
         void *malloc(size_t size);
         void free(void *pointer);
    这些函数维护一个可用内存池。当一个程序另外需要一些内存时,malloc从
    内存池中提取一块合适的内存。

    calloc:
         void *calloc(size_t num_elements, size_t element_size);
    calloc在返回指向内存的指针之前把它初始化为0。

    realloc:
         void realloc(void *ptr, size_t new_size);
    如果原先的内存块无法改变大小,realloc将分配另一块正确大小的内存,
    并把原先那块内存的内容复制到新的块上。


    11.5 常见的内存错误

    1. 忘记检查所请求的内存是否成功分配。

    int *pi;
    pi = malloc(100);
    if (pi == NULL) {
         printf("Out of memory!\n");
         exit(1);
    }

    2. 操作内存时超出了分配内存的边界。

    3. 传给free函数一个指针,让它释放一块并非动态分配的内存。
    试图释放一块动态分配内存的一部分也有可能出错。

    pi = malloc(10 * sizeof(int));
    free(pi + 5);

    4. 内存泄露



  • 相关阅读:
    zypper命令使用示例
    《大数据之路:阿里巴巴大数据实践》——1-5章
    《织云 Metis 时间序列异常检测全方位解析
    《Replicator Neural Networks》
    《软件应用 | 用 R 语言做因果推断?你少不了这些包》
    《【统计】Causal Inference》
    《Google 开源AI项目15个》
    《TF-Replicator:研究人员的分布式机器学习》
    《TF-Replicator, GPipe, Mesh-Tensorflow 三个库对比》
    虚拟对抗训练:一种新颖的半监督学习正则化方法
  • 原文地址:https://www.cnblogs.com/xiaomaohai/p/6157871.html
Copyright © 2020-2023  润新知