[读] 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
    }
  • 字符数组的初始化与字符串常量code

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

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

    多维数组能够经过如下形式传递给函数:element

    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
相关文章
相关标签/搜索