Chapter 4

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

Chapter 5

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

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

      • 左移时与逻辑移位效果相同,结果可能与原值不同正负,但这被视为溢出,不予处理
      • 右移时只能一位一位移,符号位为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的值也不一定符合期望。

  • ⚠️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
  • ⚠️自增 & 自减

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

    唯一的用处可能是用来简化同时出现在while循环前面和内部的语句(可以用逗号连接起来放进while的条件里,然后把原来的条件作为最后一个表达式)。。因为while每一轮开始前都会执行一遍条件
  • 对于一个任意的整形值,显式地测试它的具体值比把它当作布尔值直接判断真假要更好
  • ⚠️隐式类型转换

    C中的整型算数运算至少都会以缺省整型类型的精度来进行,即便所有操作数都是更小的类型(竟然是这样!):

    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;
        }
      • 这些宏无法知道实际参数的数量和类型,并且可变参数实际上会先进行缺省参数类型提升再传入函数,要格外小心

小明的贤鱼
2 声望0 粉丝

while(true) -1s;