下面这个程序做一件很朴素的事:从标准输入读文本,把每一行中指定列范围的内容提取出来打印。列号从 0 开始,成对给出,以一个负数结束。比如输入 3 7 -1,就提取每行的第 3~7 列:
#include <stdio.h>
#include <string.h>
#define MAX_COLS 20 /* 最多处理的列对数 */
#define MAX_INPUT 1000 /* 每行输入的最大长度 */
/* 读列对:成对存入 cols,遇到负数或读满停止,返回对数 */
static int read_columns(int cols[], int max_cols)
{
int n = 0, v;
while (n < max_cols && scanf("%d", &v) == 1 && v >= 0) {
cols[n++] = v; /* 起点 */
if (n < max_cols && scanf("%d", &v) == 1 && v >= 0)
cols[n++] = v; /* 终点 */
else
break; /* 凑不满一对:结束 */
}
return n / 2;
}
int main(void)
{
int cols[MAX_COLS];
char line[MAX_INPUT];
int pairs = read_columns(cols, MAX_COLS);
int start, end, i;
while (fgets(line, MAX_INPUT, stdin) != NULL) {
line[strcspn(line, "\n")] = '\0'; /* 去掉行尾换行 */
for (i = 0; i < pairs; i++) {
start = cols[2 * i];
end = cols[2 * i + 1];
printf("%.*s\n", end - start + 1, line + start);
}
putchar('\n');
}
return 0;
}逐块看它都用了什么:
| 代码片段 | 涉及的知识点 |
|---|---|
| #include <stdio.h> | 预处理指令:把标准库的函数声明抄进来(第 14 章) |
| #define MAX_COLS 20 | 符号常量:给「魔法数字」起名字(第 14 章) |
| int cols[], char line[] | 数组:一串同类型数据住在连续内存里(第 8 章) |
| line + start | 指针运算:数组名退化成指针再偏移(第 6、8 章) |
| printf("%.*s\n", ...) | 格式化 I/O:域宽、精度那些 % 开头的暗号(第 15 章) |
| static / return | 函数与存储类型(第 3、7 章) |
计算机只认机器码,C 源文件必须经过一条流水线变成可执行文件:
日常一条命令 gcc main.c -o prog 其实把这几步全做了。分步执行可以用 -E(只预处理)、-c(只编译到目标文件)观察中间产物。
undefined reference to 'foo'——编译器见过 foo 的声明所以不报错,但链接器找不到它的定义。九成是忘了写实现或忘了链上对应的目标文件。① 警告要当错误看。 C 编译器非常宽容,很多「能跑」的程序其实埋着雷。把 -Wall -Wextra 开成习惯,警告清零再谈运行。
② 每个知识点配一个最小实验。 学到指针运算就写五行代码验证 *(p+1) 到底等于什么;sizeof 一个怀疑的类型就 printf("%zu\n", sizeof(int))。机器永远比记忆可靠。
③ 出错先想内存。 段错误、乱码、诡异崩溃,大多源于越界、未初始化、悬空指针。养成习惯:声明变量就初始化,用数组先问长度。