[读] C和指针 (Ch8 ~ Ch10)

Chapter 8

  • ⚠️数组名和指针常量相似,但并不是指针!(也因为是指针常量,所以数组名也是un-assignable的)

    • 编译器用数组名来记住数组的属性,只有当数组名在表达式中被使用时,编译器才为它产生一个指针常量
    • 只有两种情况下数组名不用指针常量来表示:

      • 用于sizeof:对数组名使用sizeof将返回整个数组的长度(以字节为单位)
      • 用于&取址:得到指向数组的指针(而不是指向第一个元素的指针的指针)
    • 下标引用可以作用于任何指针,而不仅仅是数组名,因此C编译器不检查下标的范围,下标也可以为负
    • 通过指针来访问数组中的元素可能比用下标的效率更高,因为通过下标访问数组总是要做以下运算:

      arr + (index * sizeof(elementOfArr)) // 此处的乘法无法避免

      而某些情况(主要是循环体)下,对该偏移值的计算将被编译器优化为纯加法(一个在现代基本没什么用的知识。。):

      for (int* arrPtr = arr; arrPtr < arr + 10; arrPtr ++) { ... } // arrPtr++ 所加上的值只需要计算第一次
    • 一个注意:

      指针不仅在做加法时需要考虑元素大小,两指针相减的结果也会被通过除法转换为元素个数(单纯比较是否相等则不需要)

  • ⚠️函数接收多维数组做参数时,必须指定除第一维外其他所有维度的大小,否则寻址时函数无法计算偏移量

    void func(int x, int y, int arr[][]) { // 未指定第二维大小
        printf("%d", arr[x][y]); // 访问arr的第(x * sizeOfRow + y)个元素,但函数不知道sizeOfRow
    }
  • 字符数组的初始化与字符串常量

    char msg[] = "Hello";
    char *msg = "Hello"; // 字符串常量是read only的
  • ⚠️声明一个指向数组的指针:

    int arr[3][10];
    int (*arrPtr)[10] = arr; // arrPtr是一个指向拥有10个int元素的数组的指针,arrPtr + 1将指向下一行的10个int的开始

    多维数组可以通过以下形式传递给函数:

    void func(int (*mat)[10]);
    void func(int mat[][10]);

    当多维数组各维度的大小并不总固定时(我的最爱~),可以通过以下方式将它以一维数组的形式传递给函数:

    int *arrPtr = &arr[0][0];
    int *arrPtr = arr[0];

    但是以下这种声明可能会导致严重的问题:

    int arr[3][10];
    int (*arrPtr)[] = arr; // 此时指针arrPtr不知道数组arr的size信息,它将会把0当作arr的二维宽度(而不是3)
  • 下标引用[]的优先级高于间接访问*

    int *p[10]; // p被声明为拥有10个int*元素的数组

Chapter 9

  • strlen的返回值为size_t,是一个unsigned值(永远大于等于0)
  • 标准库中的字符串函数不负责安全性检查,程序员必须保证字符串以NULL结尾(在必要的情况下)且大小合适
  • strcpystrcat等函数会将目标字符串的地址作为返回值,这个特性令它们可以被嵌套使用
  • 即使是对转换大小写这种简单的操作,使用库函数也有利于提高程序在使用不同字符集的平台间的移植性
  • strerror函数接收一个错误代码,并返回一个指向描述该错误的字符串的指针

Chapter 10

  • ⚠️struct(突然发现我根本不知道struct的标准语法。。)

    struct Tag { // struct关键字和花括号之间的部分为该struct的标签,为可选部分,这样声明的结构体每次使用时都要加上struct关键字
        int val;
        char name[10];
    } tag1, *tag2; // 此处可以顺便声明该类型的变量,但此后声明新变量必须使用struct Tag tag;,因为Tag实际上不是一种类型
    
    typedef struct { // 将这种struct定义为一种类型,因此以后使用时不用加struct关键字
        int val;
        char name[10];
    } Tag; // 以后可以直接使用Tag tag;来声明新变量

    所以struct {};才是一个整体,它可以被加上标签作为记号,也可以被typedef为一种新的结构体

    (我竟然一直以为是在主函数外声明新变量就要在类型前加struct关键字。。所以我这么久都写的啥==)

  • 点操作符.的优先级高于间接访问*,所以C提供了->来更为方便地通过结构体指针访问它的成员(原来如此!!)

    (*ptr).val = 0;
    ptr->val = 0; // 显然下面这种写法更为直观

    以及优先级上->高于.高于*

    *p->a.b; // 将会从p指向的结构体中取出a结构体的b成员并对它进行间接访问,即*((p->a).b)
  • 不完整声明:使两个(或更多)结构体可以互相包含对方的指针

    struct B; // 提前声明B的存在
    
    struct A {
        struct B *bPtr;
        ...
    };
    struct B {
        struct A *aPtr;
        ...
    };
  • 从C2011开始typedef struct foo foo;是合法的了,但是这种写法在之前的编译器上(可能!)会出现兼容性问题
  • ⚠️C中的空结构体为undefined行为(但C++允许结构体为空),以及结构体实际上允许嵌套声明,因此可能无意间声明空结构体:

    struct A {
        struct B { // 此处实际只是struct B的定义,而非变量声明。因此没有其他成员的struct A实际为空结构体
            int id;
            ...
        };
    };
  • ⚠️对结构体使用sizeof将返回它实际占用的字节数,包括那些为了边界对其而跳过的字节。

    因此要确定一个成员对于结构体起始位置的实际偏移量,应使用offset(type, member)宏(位于stddef.h),type为结构类型,member为该成员的名字。这个宏将返回一个size_t

  • 把整个结构体作为函数的参数or返回值都会导致整个结构体被复制,使用指针效率更高
  • 位段(bit filed):(本质上是不可移植的。。hmmm)

    • 位段成员必须为intunsigned int类型
    • 成员后跟一个冒号和一个用于指定该位段所占用的bit数的整数
    struct CHAR {
        unsigned ch :   7;
        unsigned font : 16;
        unsigned size : 19;
    };
    struct CHAR c1;
  • ⚠️联合(union):(通过访问不同的联合成员,内存中的同一块内容可以被解释为不同的东西)

    • struct相似,union也允许嵌套声明

      struct POINTER { // 实际包含两个变量,type和ptr
          enum {INT, FLOAT, STRING} type;
          union {
              int        *i;
              float    *f;
              char    *s;
          } ptr; // 匿名union
      };
    • 联合中所有成员都拥有相同的起始地址,整个联合的大小取决于最大的成员
    • 为了避免个别超长成员造成空间浪费,通常只储存指向不同成员的指针,使用中再为该成员动态分配内存
    • 初始化:初始值必须为第一个成员的类型,且必须用花括号包围

      union {
          int a;
          float b;
      } x = { 5 }; // 其他类型的初始值(如果可能的话)将会被转换为一个int赋值给x.a

你可能感兴趣的:(c和指针,读书笔记,指针,多维数组,结构体)