
这个小节和下一个小节,只是讲了标题的内容,没有一点点多余的,完全就是在尬聊
本小节基本属于废话章节,对于一个有着基础编程的程序员,这是完全可以避免的错误
同上,略
1.3 词法分析中的“贪心法”这个小节比较有趣,主要讲述了编译器处理符号的原理 “贪心法”
编译器将程序分解成符号的方法是,从左到右一个字符一个字符地读入,如果该字符能够组成一个符号,那么再读入下一个字符,判断已经读入的两个字符组成的字符串是否可能是一个符号的组成部分;如果可能,继续读入下一个字符,重复上述判断,直到读入的字符串不能再组成一个有意义的符号。
这也是双字符符号中间不能含有空格等其他空白字符的原因,因为无法组成可识别的符号。
1.4 整型常量这一个章节是讲述常量被编译器识别的时候会出现一个和程序员理解偏差的点
比如整型的数据如果在程序中写入初始值 0123,则编译会将其当作八进制来处理
在C语言中,函数的调用和声明是采用返回类型、函数指针、调用操作符三者进行标识的。
void main ()
插句题外话,函数指针是啥,怎么定义?怎么用?
函数指针就是定义的时候将函数名换成一个指针的形式,这样在后续调用的时候就可以调用这个指针来寻址这个函数,其实看下面代码的第五行也能够很明显的看出来,函数名称在程序内的单独使用(不搭配函数操作符号),就是计算函数Function的地址,并不去调用函数,同理这种函数指针也是可以像变量指针一样被简单的使用,例如第六行。
int temp; int input; int Function(int x); int (*Fp) (int x); Fp = Function; temp = (*Fp)(input);
如果能理解函数名其实也是某种意义上的函数指针,程序中单独调用函数名其实是在计算函数在内存中的地址,函数名加函数操作符才是调用函数并且这个操作与调用指针寻址变量是一致的,这三句话,那么这一节基本上是完全了解了。
其实不仅是函数,其他的变量定义也是遵循着这种规则,引用书中的原话
任何C变量的声明都是由两部分组成:类型以及一组类似表达式的声明符(declarator)。声明符从表面上看与表达式有些类似,对它求值应该返回一个声明中给定的类型的结果。
书中给出了一个极其古怪的声明
(*(void(*)())0)();
有兴趣的可以自己解释一下,对于他声明了啥我是没兴趣,但是这个0,我还是很有兴趣的,作者称这个0为“哑变量”,也就是直接将函数指针的值初始化为0(直接调用存储在0位置的程序)。这让我想起了我在工作中遇到的一种对于直接寻址的写法(工作经常使用单片机,地址都是固定的比较熟悉,而且这也是单片机硬件驱动库的常见写法,一般不推荐这么写)
这种写法就是直接去寻址位置为 xxx 的程序。
2.2 运算符的优先级问题这段文章也没啥意思,作者的态度是,可以用括号来解决,但是我不,我就要硬记,因为括号太多不好看。
呵呵,劝你直接用括号吧,duck不必费劲记这玩意,没必要,这种问题出错了,那可是真的不好检查。
如果你不听劝,那么给出下面这个表
| 运算符 | 结合性 | |
|---|---|---|
| ( ) [ ] -> . | 左到右 | |
| ! ~ ++ – - (强转) *(取值) &(取地址) sizeof | 右到左 | 单目运算符 |
| * / % | 左到右 | 双目运算符 |
| + - | 左到右 | 双目运算符 |
| << >> | 左到右 | 双目运算符 |
| < <= >= > | 左到右 | 双目运算符 |
| == != | 左到右 | 双目运算符 |
| & | 左到右 | 双目运算符 |
| ^ | 左到右 | 双目运算符 |
| | | 左到右 | 双目运算符 |
| && | 左到右 | 双目运算符 |
| || | 左到右 | 双目运算符 |
| ?: | 右到左 | 三目运算符 |
| = /= *= %= += -= >>= &= ^= |= | 右到左 | |
| , | 左到右 |
上面有些单目双目归类不是很正确,主要由于懒
那么总结下来可以概括为以下几条
1.单目优先级高,然后是双目,没目的更高
2.操作符>奇怪的运算符>乘除>加减>位移>大小关系>相等关系>逻辑运算>逻辑判断>三目>赋值
根据上面的顺序写了一个优先级判断的顺口溜,如果实在闲着无聊,可以自己没事儿背着玩儿,反正我是背不下来。
2.3 注意作为语句结束标志的分号操作符,奇怪算
乘除加减后位移
先大小,后相等
逻辑运算再判断
三目前,赋值后
优先级来顺口溜
这节纯纯的凑字数了,主要讲了你不写分号,编译器可能会把前后的语句连在一起解析,恰好语法没问题不报错,恰好结果也是对的,恰好发现不了,真是的,哪来那么多恰好,但凡有个测试,这种bug基本没有。
或许是作者那个年代没有自动格式化软件,有自动格式化软件,这个问题直接格式化出来了,完全没有藏身的地方,格式化我推荐LLVM里面的clang-format ,和vscode兼容的很好,而且里面的那个visual stdio 预定义模式,对嵌入式c语言非常的友好,裂墙推荐,安装教程参考我另一篇文章
这节作者在讲述switch 不写break的后果,确实这个还蛮容易发生的,因此使用switch的时候得注意写break,但是不写break顺序执行也是switch的优势和特性,也经常这么用,怎么避免出错也没啥好办法,也就能写个注释告诉你这地方是我故意没写break的。嗯,也就能这样了。
2.5 函数调用完全没啥,前面都说过了,引用原文吧
2.6 “悬挂”else引发的问题如果 f 是一个函数
f ( );
是一个函数调用的语句,而
f ;
确是一个什么也不做的语句。更精确的说,这个语句计算函数f的地址,却并不调用该函数。
这个问题还真挺常见的,就是因为懒,在if和else后面不写花括号引起的一系列问题,这种问题在实际编码中,如果用了自动格式化软件,基本也不存在,或者手勤快一点,在每个if和else后面都写上{},基本也能够避免。
第三章 语义“陷阱” 3.1 指针与数组 3.2 非数组的指针 3.3 作为参数的数组声明 3.4 避免“举隅法” 3.5 空指针并非空字符串 3.6 边界计算与不对称边界 3.7 求值顺序 3.8 运算符&&、|| 和 ! 3.9 整数溢出 3.10 为函数 main 提供返回值 第四章 连接 4.1 什么是连接器 4.2 声明与定义 4.3 命名冲突与static修饰符 4.4 形参、实参与返回值 4.5 检查外部类型 4.6 头文件 第五章 函数库 5.1 返回整数的 getchar 函数 5.2 更新顺序文件 5.3 换成输出与内存分配 5.4 使用 errno 检测错误 5.5 库函数 signal 第六章 预处理器 6.1 不能忽视宏定义的空格 6.2 宏并不是函数 6.3 宏并不是语句 6.4 宏并不是类型定义 第七章 可移植性缺陷 7.1对应C语言标准变更 7.2 标识符名称的限制= 7.3 整数的大小 7.4 字符数有符号整数还是无符号整数 7.5 位移运算符 7.6 内存位置0 7.7 出发运算时发生的截断 7.8 随机数的大小 7.9 大小写转换 7.10 首先释放,然后重新分配 7.11可移植性问题的一个例子