酱瓜正在激情复习C语言中,在这里随便记一下看到的一些易错点。本文按照《C Primer Plus(第6版)》的章节顺序编排。
易错点速查
- 整数除法即为整除
- switch…case 语句漏
break;
Ch3 数据和 C
转义序列 ‘\num’ 的 num 看成八进制数而不是十进制
1 |
|
Output:
1 | 53 5 |
当然,最标准的写法当然是:
1 | char best0 = '\0oo'; //ASCII码为八进制数oo |
四舍五入与去尾
简而言之就是小数->小数就是四舍五入,小数->整数就是直接去尾(截断)。
1 |
|
Output:
1 | 3.542 3 |
Ch4 字符串和格式化输入/输出
两个xxxxf()函数的返回值
printf()
返回实际输出字符串的长度(不包括'\0'
)。
scanf()
返回成功读取的数据个数。
易错:双精度浮点型 (double) 的转换说明
printf()
中输出一个 double 型用 %f
即可,而 scanf()
读入一个 double 型却要用 %lf
。
C Primer Plus 给出的易错提示·节选
- 对于
scanf()
,一定要记得在变量名前加上地址运算符(&)。 scanf()
函数中的转换说明为%s
时,仅可读取一个单词。
printf() 求具体值的顺序
C 语言在执行
printf()
时。对函数中表达式表列的处理顺序是从后往前……对于printf("%d %d %d", n, n++, n--);
先处理n--
,再处理n++
,最后处理n
……某 C语言试题集
printf() 中的修饰符
下表来自《C Primer Plus(第6版)》, P83
修饰符 | 含义 |
---|---|
.数字 | 精度 对于 %s 转换,表示待打印字符的最大数量 对于整型转换,表示待打印数字的最小位数 对于 %e、%E、%f 转换,表示小数点右边数字的位数 对于 %g、%G 转换,表示有效数字的最大位数 |
Ch5 运算符、表达式和语句
整数相除即为整除
这个就很经典了,整数除法只能出整数(趋零截断),浮点数除法(只要一个运算数是浮点数即可)才是平时我们做的除法。不过说归说,一旦这个含除法运算符的表达式太长,有可能忘掉这一点。
例题一则:运算符的优先级问题
p
为指针(int* p;
),试解析语句 a = *p++;
。
【解析】
a = *p++;
⇿
a = *(p++);
⇿
1 | a = *p; |
逻辑运算符:本是同根生,相煎何太急
逻辑与 &&
的优先级要高于逻辑或 ||
。
Ch6 C 控制语句:循环
逗号运算符
逗号运算符把两个表达式连接成一个表达式,并保证最左边的表达式最先求值。逗号运算符通常在 for 循环头的表达式中用于包含更多的信息。整个逗号运算符的值是逗号右侧表达式的值。
《C Primer Plus(第6版)》, P158
虽然「整个逗号运算符的值是逗号右侧表达式的值」,但不代表在那种「人脑Build & Run」题目里面看到逗号运算符就直接看最右边的表达式,因为前面几个表达式可能改变了某些变量的值!比如:
1 | x = (++z * y, y++, z % y); |
执行到 z % y
一步就有:
1 | z == z_original + 1; |
Ch7 C 控制语句:分支和跳转
摘抄
7.3.2 优先级
!
运算符的优先级很高,比乘法运算符还高,与递增运算符的优先级相同,只比圆括号的优先级低。&&
运算符的优先级比||
运算符高,但是两者的优先级都比关系运算符低,比赋值运算符高。因此,表达式
1 a > b && b > c || b > d;相当于
1 ((a > b) && (b > c)) || (b > d);《C Primer Plus(第6版)》, P192
7.3.3 求值顺序
……C通常不保证先对复杂表达式中哪部分先求值。例如,下面的语句,可能先对表达式
5 + 3
求值,也可能先对表达式9 + 6
求值:
1 apples = (5 + 3) * (9 + 6);……但是,逻辑运算符是个例外,C 保证逻辑表达式的求值顺序是从左到右。
&&
和||
运算符都是序列点,所以程序在从一个计算对象执行到下一个运算对象之前,所有的副作用都会生效。而且,C 保证一旦发现某个元素让整个表达式无效,便立即停止求值。(酱瓜注:参见在逻辑判断中偷懒)……《C Primer Plus(第6版)》, P192
Ch12 存储类别、链接和内存管理
静态变量的声明(包括赋初值的声明)只执行一次
下面两个声明很相似:
1
2 int fade = 1;
static int stay = 1;……第 2 条声明实际上并不是
trystat()
函数的一部分。如果逐步调试该程序会发现,程序似乎跳过了这条声明。这是因为静态变量在程序载入内存时已执行完毕。把这条声明放在trystat()
函数中是为了告诉编译器只有trystat()
函数才能看到该变量。这条声明并未在运行时执行。《C Primer Plus(第6版)》, P382
上面那一段引用其实是在围绕下面这个例程说的(这个例程主函数之类的部分省略,在书P381页可以找到,trystat()
函数在主函数中被调用了 3 次):
1 | void trystat(void) |
Output:
1 | fade = 1 and stay = 1 |
后面的章节
这里散落着酱瓜在高速狂刷往年卷的时候收集的易错点。
malloc() 前面要显式转换指针类型
1 | struct node *wrong, *correct; |
这个CC就是逊啦:C语言没有连等判断
这一条是之前帮同学 debug 的时候发现的。
C语言:
1 | if (0==0==0) printf("True"); |
Output:
1 | False |
Python:
1 | if (0==0==0): |
Output:
1 | True |
在逻辑判断中偷懒
有时候会说成「编译器的优化」。
比如这道题:
设m, n, a, b, c, d 均为 0,执行
(m=a==b) || (n=c==d)
后,m, n 的值分别为?
因为 ||
左边那个 (m=a==b)
已经是 True 了,无论右边是啥,整条表达式都肯定是 True,所以程序干脆就不执行 (n=c==d)
了。故此题答案:m 为 1,n 为 0。
没有赋值,白算一遭
像什么写个 k % 2;
让你误以为是 k = k % 2;
或者 k %= 2;
。
注:
上面一行格式很不好看……对,题目就是在不好看的代码里面给插一个坑,更难发现有坑。好看一点的话上面应该写成这样——
像什么写个
1 | k % 2; |
让你误以为是
1 | k = k % 2; |
或者
1 | k %= 2; |
switch … case 结构不加 break;
不细说了,经典易错点,很容易看漏。
欢乐的指针们
下文把 int 型指针
(基本类型 int
的衍生类型)看作名为 int*
的基本类型。
1 | int (*p)(int n); // p 是一个函数指针(指向函数的指针) |
附录:手写稿
附录:方法论
把指针这种衍生类型看作基本类型
尤其在涉及“函数隔离变量”考点的题目里面特别管用。比如这题:
1 |
|
若是把 int *a
看作 int* a;
,下面就不容易看乱了。
此题的输出为:
1 | 3, 4 |
注:
对于这个技巧,更为通用的做法是按照下面这种编码格式去理解:
*
和指针名之间的空格可有可无。通常,程序员在声明时使用空格,在解引用变量时省略空格。《C Primer Plus(第6版)》, P271
比如:
1 | int * p; |