C 语言应试笔记

前言

这是我在大一时 C 语言课程考试前总结的坑们,每次相关考试前都会复习一次。如果你发现可以补充的地方,欢迎评论。

正文

一言以概
任何看上去有简单答案的问题都藏有坑。

看见定义变量,总是注意变量是否被初始化过。

注意 if 中的等号是 = 还是 ==

if 语句没有花括号时只包含之后一行,如:

1
2
3
if (condition)
optionally_executed();
always_executed();

if else语句在没有花括号时,else总是匹配最近的if(Dangling Else),如:

1
2
3
if (a)
if (b) do_b();
else do_c();

实际上是:

1
2
3
4
5
6
7
if (a) {
if (b) {
do_b();
} else {
do_c();
}
}

switch语句在缺少 break 时将 Fall through。

求值逻辑表达式时 10。 判断逻辑表达式时, 非 00

truefalseTRUEFALSE 是合法的变量名,因为在 C 中它们不是关键字。

*p++等价于 *(p++),因后自增++ 优先级高于解引用*

注意整型除法结果被截断, int n = 2; 1 / n == 0

注意本地变量需要初始化。

注意变量作用域屏蔽,如:

1
2
int b;
void f() { int a = 0, b = 1; }

注意函数为传值,指针也是值传递,例如:

1
2
3
4
5
void swap(int *a, int *b) {
int *temp = a;
a = b;
b = temp;
}

并不影响外部。

注意 int 溢出,若易发生溢出(如阶乘操作)应使用 longdouble代替。

char a = 255,打印后值为 -1,因为 char 为有符号类型并采用补码表示,其范围为 -128 ~ 127

负数的位移操作中符号位也参与位移;左操作数为负数的右移 >> 结果由实现定义,Turbo C 为补 1。

a/ba%b 结果在 ab中有一个为负数时由实现定义,但保证 a / b * b + a % b 结果为a

过滤回车结束的输入字符串:

1
2
3
4
5
6
7
8
i = 0;
while ((ch = getchar()) != '\n') {
if (ch ...) {
continue;
}
str[i++] = ch;
}
str[i] = '\0';

C 语言二维数组存储采用 Row-major 方式,即在内存中存储为 row1, row2, row3…,初始化时必须给出列数以确定行中元素个数,静态变量中未初始化元素自动为 0,使用时可以将列数溢出到下一行,即偏移值计算为 row_count * row + column。

注意分号:

1
2
for (...);
do_something();

do_something只执行一次。

1
2
if (0);
do_something();

do_something一定会被执行。

sizeof()表达式的值在编译时确定。编译器不计算其中表达式的值,仅将其替换为对应类型。sizeof应用于数组时结果为 数组元素个数 * 元素大小,应用于指针时为指针变量长度(在 32 位机器上地址长度为 32,故值为 8;64 位机器上为 16)。

无论 x 是数组还是指针,在定义上编译器认为 x[3]*(x+3)是等价的。但根据 x 的类型是数组还是指针, 编译器将为 x+3sizeof(x)生成不同的代码。

类似 char [] 类型的数组名被视为指向 char 的指针,char[][]也被视为指向 char[] 的指针。

注意 &&||具有 短路求值 特性,不会执行无必要求值的表达式。

注意含中文字符的文件应保存为 GB* 编码,例如GB18030

strcpy(dst, src)中目标在前,源在后。

注意 int a, b, cc = 2a + b 非法(需要*)。

注意 x<=y<=z 意为(x<=y)<=z,意义非预期但合法。

scanf遇到空白字符截止,为输入一行可使用 scanf("[^\n]",str)gets()

注意:scanf("%s", s)输入 "How are you?" 遇到空格截断,只得到How

strcpy(char *dst, char *src) {while(*dst++ = *src++); };++ 优先级高于*

[]()优先级高于 *,故int *array[1] 等价于 (int *)array[1],为 int 指针的数组;int (*p)[1] 为指向 int 数组的指针。

解读方法:以 int **a[1][2] 为例。从 a 出发,优先向右解读。(来源

  1. a is … int
  2. a is array of 1 … int
  3. a is array of 1 array of 2 … int
  4. a is array of 1 array of 2 pointers to … int
  5. a is array of 1 array of 2 pointers to poiner to int

优先级:后置自增 / 自减 、函数调用、数组元素、结构成员 > 前置自增 / 自减、正负、类型装换、 解引用 、取地址、数据类型大小、内存操作、(逻辑、位)非 > 结构成员解引用 > 乘除、取余 > 加减 > 位移 > 比较 > 等价 > (逻辑、位)与、或、异或 > 三元条件、(复合)赋值。

字符串字面值与字符常量中转义序列 \ddd 可以为 1、2、3 位 8 进制数(0~7),上限为 377;\xhh可以为 1、2 位十六进制数(0~F)。

转义序列列表:

转移序列 含义
\a BEL
\b BS
\f FF
\n LF
\r CR
\t HT
\v VT
\\ \
\' '
\" "
\0 NULL
\ddd 八进制
\xhh 十六进制

0~255 的数字也可以作为有效的字符取值。

int i = -1; printf("%d", (unsigned int)i);打印出 -1,因为%d 为有符号整型。

使用草稿纸记录变量取值以计算程序输出,或对某些类型可理解程序意图猜测程序输出,或两者结合。

10^-6表示为1e-6

通过 memset(array,0,sizeof(array)) 可以实现数组重初始化。(需要string.h

# 代表将此后的文本变为字符串;##代表连接文本;含有 ###的宏不进行参数展开,可利用包装宏展开。(参考

宏函数定义:

1
2
3
4
#define func_name(var) \
do { \
do_something(); \
} while (0)

调用与函数调用一致:

1
func_name(var);

scanfffloatlfdoubleprintffdoublelf 未定义。

,逗号表达式对左侧表达式求值并丢弃返回值,之后对右侧表达式求值并返回其返回值。

#define SWAP(a,b) (a)^=(b)^=(a)^=(b)可实现无临时变量的交换。

字符 ASCII
0 48
A 65
a 97

变量命名法则:[A-Za-z_][A-Za-z0-9_]*,且不含关键字。

autoregistervolatile均为 C 关键字。

007为八进制数字,注意范围为 0-70x0F 为十六进制数字。

strcat实现:

1
2
3
4
void strcat(char *str1, char *str2) {
while (*str && *++str);
while (*str1++ = *str2++);
}
相关阅读
《C 陷阱与缺陷》笔记 | 孙耀珠的博客