📖 第 1 章:快速上手(A Quick Start)

🔤 Kenneth Reek《Pointers on C》(C和指针)· Chapter 1
🗨️ 本章金句:「先把整个流程跑通一遍,细节留给后面的章节慢慢填。」
这一章在干嘛? 开门见山:用一个完整的小程序把「写 C 代码 → 翻译成机器能执行的文件 → 运行看结果」整条链路走一遍,对 C 的样子建立第一印象。细节不懂没关系,后面每一章都会把对应的部分展开讲透。
1.1 解剖一个完整的小程序1.2 从源码到运行:翻译的完整流水线1.3 给初学者的三条上手建议

1.1 解剖一个完整的小程序

下面这个程序做一件很朴素的事:从标准输入读文本,把每一行中指定列范围的内容提取出来打印。列号从 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 的知识点是一张网:指针连着数组、数组连着函数参数、函数连着声明。先见过「整体长什么样」,再逐个击破,比从孤立语法片段学起效率高得多。

1.2 从源码到运行:翻译的完整流水线

计算机只认机器码,C 源文件必须经过一条流水线变成可执行文件:

$$\text{源文件 } .c \xrightarrow{\ \text{预处理}\ } \text{展开后的代码} \xrightarrow{\ \text{编译}\ } \text{目标文件 } .o \xrightarrow{\ \text{链接器}\ } \text{可执行文件}$$

日常一条命令 gcc main.c -o prog 其实把这几步全做了。分步执行可以用 -E(只预处理)、-c(只编译到目标文件)观察中间产物。

链接期错误的典型长相: undefined reference to 'foo'——编译器见过 foo 的声明所以不报错,但链接器找不到它的定义。九成是忘了写实现或忘了链上对应的目标文件。

1.3 给初学者的三条上手建议

① 警告要当错误看。 C 编译器非常宽容,很多「能跑」的程序其实埋着雷。把 -Wall -Wextra 开成习惯,警告清零再谈运行。

② 每个知识点配一个最小实验。 学到指针运算就写五行代码验证 *(p+1) 到底等于什么;sizeof 一个怀疑的类型就 printf("%zu\n", sizeof(int))。机器永远比记忆可靠。

③ 出错先想内存。 段错误、乱码、诡异崩溃,大多源于越界、未初始化、悬空指针。养成习惯:声明变量就初始化,用数组先问长度。

本章通关标准: 能独立编译运行一个小程序;能说出 #include 和 #define 在哪个阶段起作用;看到 undefined reference 知道该查链接而不是语法。
🧠 小测验
1. C 从源文件到可执行文件要经过哪几个阶段?
预处理(文本替换:头文件、宏、条件编译)→ 编译(翻译为目标文件)→ 链接(合并目标文件与库、解析外部符号)。gcc 一条命令实际串了三步,可用 -E / -c 分步观察。
2. 编译能过但链接报 undefined reference,问题出在哪?
声明只告诉编译器「有这么个函数」,定义才产生真正的代码。链接器找不到定义就会报这个错——检查是否漏写实现、漏链目标文件。
3. 为什么第一课建议读完整程序而不是背语法?
C 的知识是一张互相引用的网(指针→数组→函数参数→声明)。先建立整体印象,后面各章展开时才有挂靠点,比孤立记忆语法片段高效。
← 上一篇🏠 顶层目录下一篇 →