4.2 C程序设计基础
阅读入口
类型与字节数
C的每个对象在编译时就有确定的类型,类型决定三件事:占多少个字节、这串位按什么规则解释、能做哪些运算。变量必须先声明类型再使用。
char、short、int、long、long long是整数类型,float、double是浮点类型,size_t是无符号整数类型,void * 是指针类型。标准只保证大小顺序,不规定具体位数:sizeof(char) 恒为1,且sizeof(char) <= sizeof(short) <= sizeof(int) <= sizeof(long) <= sizeof(long long)。具体的位数由平台ABI决定。32位和Windows 64位(ILP32 / LLP64)中long是4字节,Linux与macOS的64位(LP64)中long是8字节。
常见取值如下。大小一列以常见平台为准,写代码时应使用sizeof而不是记住具体数字。
| 类型 | 常见大小 | 有符号范围 | 说明 |
|---|---|---|---|
char | 1 byte | 至少8位;是否有符号由实现决定 | |
short | 2 bytes | ||
int | 4 bytes | 约 亿 | |
long | 4或8 bytes | 随平台 | Windows 64位为4,Linux/macOS 64位为8 |
long long | 8 bytes | 约 | |
float | 4 bytes | 约 位有效数字 | 单精度 |
double | 8 bytes | 约 位有效数字 | 双精度 |
size_t | 4或8 bytes | 无符号 | sizeof的返回类型 |
void * | 4或8 bytes | 无符号 | 指针的字节宽度 |
整数在内存中用补码表示,负数编码与范围见补码与带符号整数运算;浮点数的字段划分与精度见浮点数与IEEE 754。
普通char是否有符号取决于编译器和平台,需要明确范围时应写signed char或unsigned char,不要依赖char的符号性。
本节先给类型表,再讨论溢出与截断;题目未指定环境时按LP64取long为8字节,其余按表处理。
格式串的细节
一个转换说明可包含标志、最小字段宽度、精度、长度修饰符和转换字符。宽度不足时默认在左侧补空格;- 改为左对齐,0可在数值左侧补零。宽度是最小值,不会截短更长的输出。
对于 %f,精度表示小数点后的位数;对于 %s,精度限制最多输出的字符数。不同转换字符对精度的解释可能不同。
| 写法 | 含义 | 示例输出 |
|---|---|---|
%5d | 右对齐,宽度至少为5 | 12 |
%-5d | 左对齐,宽度至少为5 | 12 |
%05d | 不足的宽度补零 | 00012 |
%8.3f | 宽度至少为8,小数点后3位 | 3.142 |
%d需要int *,%f需要float *,%lf需要double *。字符数组作为实参时通常转换为首元素指针,例如char word[16] 可传word。用 %15s最多读取15个字符,并为结尾的 \0留出空间;不限制宽度可能导致数组越界。
大多数转换会跳过前导空白;%c、%[ 和 %n是例外。格式串中的空白会匹配任意多个输入空白,因此 " %c" 会先跳过空白再读取一个字符。%s在空白处停止;需要读取一整行时可使用fgets。
返回值是成功赋值的项数。例如scanf("%d %d", &a, &b) 在输入12 abc时返回1。在第一次成功转换或匹配失败之前发生输入失败,返回EOF。应按期望的赋值数检查返回值。
格式串是本节最容易在选择题里失分的部分。读的时候把宽度与精度、空白符、返回值分成三组记,后面“与4.1的对应关系”再把两门语言逐项对照。
预处理与存储类
C的翻译过程包含预处理、编译和链接等阶段。预处理器处理包含、宏替换与条件编译。预处理指令以 # 开头、独占一行、不以分号结尾,常见的有三类:
#include:把头文件的内容原地插入;#define:定义宏,编译前做文本替换;#if/#ifdef/#ifndef/#elif/#else/#endif:条件编译,按条件保留或丢弃一段文本。
宏替换按预处理记号进行,不执行C的类型检查。
存储类说明符auto、register、static、extern描述对象的三个不同属性:作用域(名字在哪些代码里可见)、存储期(对象何时创建、何时销毁)、链接(哪些翻译单元能看到这个名字)。
| 存储类 | 用在哪里 | 作用域 | 存储期 | 链接 |
|---|---|---|---|---|
auto | 块内局部变量,可省略 | 块 | 自动 | 无 |
register | 块内局部变量 | 块 | 自动 | 无 |
static | 块内局部变量 | 块 | 静态(程序全程) | 无 |
static | 文件作用域的变量、函数 | 文件 | 静态 | 内部 |
extern | 变量、函数的声明 | 由声明位置决定 | 对象通常为静态存储期 | 通常为外部,也可能继承已有内部链接 |
学习安排
本节汇总C程序中需要按考试细节核对的规则。四块内容分别回答四类问题:
- 一个值占几个字节、能表示多大范围:类型、
sizeof、溢出与截断; - 读进来的是什么、输出长什么样:格式串、空白符、返回值与缓冲区;
- 地址怎么算、数组和指针是什么关系:退化、指针算术与结尾的
\0; - 编译之前源码被改了什么:宏替换、头文件、条件编译与存储类。
四块之间有依赖,建议按教材顺序读:先类型,它决定后面所有格式符和指针步长;再输入输出,可以立刻用上类型;然后指针,地址要按元素大小算;最后比较预处理和存储类:前者在翻译时处理源码,后者描述对象的存储期等属性。
格式串和指针运算最容易在选择题里失分,留到最后复查。每块读完先合上表格手算两三个表达式,再用题集核对。
与4.1的对应关系
4.1与4.2是同一批考点的两种语言版本,可以逐项对照:Python的整数与切片对应C的定宽整数与数组下标,str.format与 % 对应printf、scanf的格式串,Python的优先级表对应C的15级表。
差异集中在三处:整数除法与取模的符号、可变参数的类型提升、字符串用 \0还是长度来标记结尾。做题前先确认手里是哪种语言,再套该语言的规则。
课程约定
- 题目没给环境时按64位平台(LP64)作答:
int4字节、long8字节、指针8字节。同一份代码在Windows上long只有4字节;题目给了环境就以题目为准。 - 整数除法按C的规则向零截断,不要沿用4.1的向下取整:
-5 / 2是-2,-5 % 2是-1。 scanf读double用%lf,printf输出double用%f;格式符与参数类型必须配对。- 题中的“字符串长度”默认指
strlen的结果,不含结尾的\0。 - 运算符优先级按C的表判断,完整顺序、经典陷阱与两种语言的差异见 运算符优先级。
- 本节只讨论C。Python的规则见4.1 Python程序设计基础。
易错点
- 格式符与类型不匹配不是“输出一个奇怪的值”,而是未定义行为:整数与浮点参数走不同的传递方式,
%d配double读到的是无关数据。 %c不跳过空白,读字符前要先处理上一行留下的换行。- 数组名作实参后退化成指针,函数里
sizeof拿不到数组长度,长度必须另传。 - 对
static只记住“延长生命周期”不够:修饰文件作用域的变量或函数时,它改变的是链接,名字变成只在当前文件可见。 - 宏展开后在纸面上补括号再计算,
SQUARE(1 + 2)得到5而不是9。
复习要点
复习时按“声明 → 读入 → 定位 → 展开”自测一遍:先默写各类型的字节数与格式符;再手写一段scanf并检查返回值;然后把 *p++、(*p)++、a[i]、*(a+i) 各展开一次;最后用SQUARE(1 + 2) 检查宏的括号。
指针题只要把地址和值分两列写,基本不会混。