前言
这是我在大一时 C 语言课程考试前总结的坑们,每次相关考试前都会复习一次。如果你发现可以补充的地方,欢迎评论。
正文
一言以概任何看上去有简单答案的问题都藏有坑。
看见定义变量,总是注意变量是否被初始化过。
注意 if
中的等号是 =
还是 ==
。
if
语句没有花括号时只包含之后一行,如:
1 | if (condition) |
if else
语句在没有花括号时,else
总是匹配最近的if
(Dangling Else),如:
1 | if (a) |
实际上是:
1 | if (a) { |
switch
语句在缺少 break
时将 Fall through。
求值逻辑表达式时 真
为 1
, 假
为 0
。 判断逻辑表达式时, 非 0
为 真
,0
为 假
。
true
、false
、TRUE
、FALSE
是合法的变量名,因为在 C 中它们不是关键字。
*p++
等价于 *(p++)
,因后自增++
优先级高于解引用*
。
注意整型除法结果被截断, int n = 2; 1 / n == 0
。
注意本地变量需要初始化。
注意变量作用域屏蔽,如:
1 | int b; |
注意函数为传值,指针也是值传递,例如:
1 | void swap(int *a, int *b) { |
并不影响外部。
注意 int
溢出,若易发生溢出(如阶乘操作)应使用 long
或double
代替。
char a = 255
,打印后值为 -1
,因为 char
为有符号类型并采用补码表示,其范围为 -128 ~ 127
。
负数的位移操作中符号位也参与位移;左操作数为负数的右移 >>
结果由实现定义,Turbo C 为补 1。
a/b
与 a%b
结果在 a
与b
中有一个为负数时由实现定义,但保证 a / b * b + a % b
结果为a
。
过滤回车结束的输入字符串:
1 | i = 0; |
C 语言二维数组存储采用 Row-major 方式,即在内存中存储为 row1, row2, row3…,初始化时必须给出列数以确定行中元素个数,静态变量中未初始化元素自动为 0,使用时可以将列数溢出到下一行,即偏移值计算为 row_count * row + column。
注意分号:
1 | for (...); |
do_something
只执行一次。
1 | if (0); |
do_something
一定会被执行。
sizeof()
表达式的值在编译时确定。编译器不计算其中表达式的值,仅将其替换为对应类型。sizeof
应用于数组时结果为 数组元素个数 * 元素大小,应用于指针时为指针变量长度(在 32 位机器上地址长度为 32,故值为 8;64 位机器上为 16)。
无论 x
是数组还是指针,在定义上编译器认为 x[3]
与*(x+3)
是等价的。但根据 x 的类型是数组还是指针, 编译器将为 x+3
或sizeof(x)
生成不同的代码。
类似 char []
类型的数组名被视为指向 char
的指针,char[][]
也被视为指向 char[]
的指针。
注意 &&
或||
具有 短路求值
特性,不会执行无必要求值的表达式。
注意含中文字符的文件应保存为 GB*
编码,例如GB18030
。
strcpy(dst, src)
中目标在前,源在后。
注意 int a, b, c
,c = 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 出发,优先向右解读。(来源)
a
is …int
a
isarray
of 1 …int
a
isarray
of 1array
of 2 …int
a
isarray
of 1array
of 2pointer
s to …int
a
isarray
of 1array
of 2pointer
s topoiner
toint
优先级:后置自增 / 自减 、函数调用、数组元素、结构成员 > 前置自增 / 自减、正负、类型装换、 解引用 、取地址、数据类型大小、内存操作、(逻辑、位)非 > 结构成员解引用 > 乘除、取余 > 加减 > 位移 > 比较 > 等价 > (逻辑、位)与、或、异或 > 三元条件、(复合)赋值。
字符串字面值与字符常量中转义序列 \ddd
可以为 1、2、3 位 8 进制数(07),上限为 377;F)。\xhh
可以为 1、2 位十六进制数(0
转义序列列表:
转移序列 | 含义 |
---|---|
\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 |
调用与函数调用一致:
1 | func_name(var); |
scanf
中 f
为float
,lf
为 double
;printf
中f
为 double
;lf
未定义。
,
逗号表达式对左侧表达式求值并丢弃返回值,之后对右侧表达式求值并返回其返回值。
#define SWAP(a,b) (a)^=(b)^=(a)^=(b)
可实现无临时变量的交换。
字符 | ASCII |
---|---|
0 | 48 |
A | 65 |
a | 97 |
变量命名法则:[A-Za-z_][A-Za-z0-9_]*
,且不含关键字。
auto
,register
,volatile
均为 C 关键字。
007
为八进制数字,注意范围为 0-7,0x0F
为十六进制数字。
strcat
实现:
1 | void strcat(char *str1, char *str2) { |
相关阅读《C 陷阱与缺陷》笔记 | 孙耀珠的博客