[读] C和指针 (Ch4 ~ Ch7)

Chapter 4

  • if嵌套出现时,else属于离它最近的不完整的if
  • switch的最后一个case也加上break是个好习惯,以避免在后面追加新的case时忘记添加break
  • 使用goto通常不是好事,惟一的例外多是须要从多重循环中直接退出时比较方便,可是这也能够经过把整个循环包装成一个函数,而后在须要退出的时候直接return来解决
  • C自己不具有任何I/O函数及异常处理能力,这些都要靠调用库函数来实现

Chapter 5

  • 取模运算符%不能用于浮点数
  • 对负数作整数除法的结果取决于编译器实现,可能向0取整,也可能向负无穷取整
  • 移位⚠️C只规定对于unsigned值使用逻辑移位,对于signed值使用的移位方式则由编译器决定!数组

    • 逻辑移位:不要问就是移!无论左右,空位全补0
    • 算数移位:ide

      • 左移时与逻辑移位效果相同,结果可能与原值不一样正负,但这被视为溢出,不予处理
      • 右移时只能一位一位移,符号位为0则补0,为1则补1,以保证与原值同号函数

        • 正数的算数右移等价于逻辑右移,但负数不等价!
        • 负数不等价
    • 循环移位:汇编及verilog中用的桶移位(bucket shift)是循环移位,只有它确保原值中的0和1个数不变
  • ⚠️C中的赋值符实际上会将左值做为运算结果返回,因此下面这种连续赋值实际上是合法的:测试

    int x = 3;
    int y = x = x + 2; // 合法

    可是实际上不管x是否为intx + 2都会返回一个int结果并赋值给x,而若x实际上是一个char,这样的赋值就会致使结果的高位被截去,于是获得的x的值的完整性是没法保障的,进而y的值也不必定符合指望。lua

  • ⚠️sizeof操做符判断它的操做数的类型长度(sizeof不是函数!!),而并不须要知道它的操做数(也能够是表达式)的实际值指针

    // 括号
    sizeof(x);
    sizeof x; // 二者彻底等价,以字节为单位返回变量x的长度
    // 数组
    sizeof(arr); // 返回数组的字节数(不是数组长度!!)
    // 表达式
    sizeof(a = b + 3); // 判断长度不须要知道表达式的实际值,所以sizeof不会执行接收到的表达式,a也并不会被赋任何值
    // 给sizeof一个有反作用的表达式将会获得以下警告:
    // Expression with side effects has no effect in an unevaluated context
  • ⚠️自增 & 自减code

    ++和--真是C永恒的话题。。
    • 只能用于左值
    • 结果为值的拷贝,并非变量自己。所以表达式中出现自增自减时,实际参与表达式运算的是一个un-assignable的单纯的值
    int a = 10;
    int b = a++;        // 合法:a加一的结果先被做为一个值拷贝后赋值给b,而后a被加一
    int c = (++a)++;    // 非法:++a实际是a值的拷贝,不是一个左值,所以不可自增自减
  • 逗号操做符分隔多个表达式,表达式们从左到右逐个求值,一整条逗号表达式的值等于最后一个小表达式的值。内存

    惟一的用处多是用来简化同时出如今while循环前面和内部的语句(能够用逗号链接起来放进while的条件里,而后把原来的条件做为最后一个表达式)。。由于while每一轮开始前都会执行一遍条件
  • 对于一个任意的整形值,显式地测试它的具体值比把它看成布尔值直接判断真假要更好
  • ⚠️隐式类型转换ci

    C中的整型算数运算至少都会以缺省整型类型的精度来进行,即使全部操做数都是更小的类型(居然是这样!):get

    char a, b, c;
    a = b + c; // 虽然a, b, c全都是较小的char,b和c也会先被提高为int,而后执行加法获得int结果,再将结果截短赋值给a

    可是,若是直接参与运算的值已经达到了不用提高的标准而运算结果将会产生溢出,那么即便左边变量足够接收完整的结果,获得的也是先溢出再被加长的值:

    int a, b;
    long c = a * b; // 就算a和b相乘产生溢出,右边表达式的计算结果也只是溢出后的int,不会由于c够大就提高a和b的长度
  • 对于左右都有表达式的运算符,先计算左边表达式仍是先计算右边表达式是由编译器决定的,所以以下表达式的结果实际没法预测:

    a = ++b + --b; // C只规定自增自减运算要优先加法运算执行,可是先求加法的左边(++b)仍是右边(--b)则由编译器决定

    有更好的例子以下:

    f() + g() - h();
    // 同级运算符从左到右执行的规则只约束例子中的加法比减法先执行,而不约束执行加法时必定要先算f()再算g()

    甚至编译器也能够先把每个小的表达式求出来再作运算:

    f() + f() * f();
    // 先无视运算优先级,直接求出3个f()再进行总体运算也是能够的,此时3次f()的调用顺序彻底取决于编译器实现

Chapter 6

  • 访问一个指针所指向的地址的过程称为间接访问(indirection)或解引用指针(dereferencing the pointer)
  • 注意运算符优先级,尤为是对被解引用的同时自增自减的指针(到底为何要写这种代码!!)
  • 指针与整值的加减法老是以它所指向的类型的大小为单位:对int型指针+1会使它指向下一个int
  • ⚠️指向同数组两个指针相减的结果也是以它所指向的类型大小为单位的,这个结果的类型为ptrdiff_t
  • 指向不一样数组的指针相减是undefined的,由于没有意义
  • ⚠️标准容许将指向数组元素的指针和指向该数组最后一个元素后面那个位置的指针相比较,但不容许将它和指向数组第一个元素以前的内存位置的指针相比较

    这是什么神仙规定。。不过大部分状况下查得不严,仍是能够比较的,只是移植性会稍微降低

Chapter 7

  • 可变参数列表

    • stdarg.h

      • 类型:va_list
      • 宏:va_startva_argva_end
      • #include <stdarg.h>
        int sum(int argNum, ...) { // 对未知个数的参数求和
            va_list args;
            va_start(args, argNum); // 使用va_start(可变参数列表 + 最后一个命名参数)将args指向可变参数列表的第一个元素
        
            int result = 0;
            for (int i = 0; i < argNum; i ++) {
                result += va_arg[args, int]; // 使用va_arg得到可变参数列表中的下一个参数并为它指定类型
            }
            va_end(args); // 使用va_end结束访问可变参数列表
        
            return 0;
        }
      • 这些宏没法知道实际参数的数量和类型,而且可变参数实际上会先进行缺省参数类型提高再传入函数,要格外当心
相关文章
相关标签/搜索