清风日常

Stay curious. Stay gentle.

听一会儿

4 首收藏
LOVE 2000 封面

LOVE 2000

遠野ひかる

0:004:23
首页/C Primer Plus/正文

C Primer Plus(第6版)· 第1章:初识 C 语言

学习 C 的第一步,是建立一条完整的认识链:先明确要解决的问题,再用 C 描述处理方法,经过编译和链接生成程序,最后通过运行与测试确认结果。

文章目录 31 个章节

第1章:初识 C 语言

学习 C 的第一步,是建立一条完整的认识链:先明确要解决的问题,再用 C 描述处理方法,经过编译和链接生成程序,最后通过运行与测试确认结果。

这一章的重点是理解 C 的定位和程序的形成过程。具体语法可以在后续学习中逐步补齐。

1. C 是一门怎样的语言

1.1 从解决实际问题出发

C 语言诞生于 20 世纪 70 年代初的贝尔实验室,由 Dennis Ritchie 在 B 语言的基础上发展而来,与 UNIX 操作系统的开发关系密切。

它的设计目标偏重实用:让程序员既能用较容易理解的方式组织程序,又能有效利用计算机的硬件资源。

因此,理解 C 可以抓住三个关键词:

  • 抽象:用变量、表达式和函数描述任务,不必逐条编写机器指令。
  • 控制:能够较细致地控制内存、数据表示和底层操作。
  • 责任:程序员需要主动保证操作合理,语言不会替你拦住所有错误。

1.2 主要优势与代价

特点 具体含义 学习时应形成的认识
适合结构化设计 可以把大问题拆成小任务,再通过函数组织起来 先考虑任务如何分解,再写代码
高效 便于生成紧凑的代码,并控制运行时间和内存使用 高效依赖算法、实现和编译器,不能只看语言名称
可移植 遵循标准、少依赖特定平台的源代码,较容易移到其他系统 移植通常需要在目标平台重新编译
功能灵活 既能编写一般应用,也能参与系统和硬件相关开发 灵活性使它适用面广,也增加了出错空间
库支持丰富 可以调用已有函数完成常用任务 语言本身与库要配合理解
表达紧凑 运算符丰富,较少的代码就能表达较多操作 应优先保证清楚易读,避免刻意写得晦涩

尤其需要留意指针和内存相关操作。它们赋予程序员较强的控制能力,也可能带来难以定位的错误。

控制能力越强,对设计、检查和调试的要求也越高。

1.3 典型应用方向

C 的特性使它适合以下任务:

  • 操作系统及相关基础软件。
  • 嵌入式设备和微控制器程序。
  • 编译器、解释器以及其他语言的运行支持。
  • 科学与工程计算。
  • 对性能和资源占用有明确要求的应用。

C 与 C++ 有共同的历史和许多相似语法,但学习时仍应区分两种语言的规则,使用 C 模式编译 C 程序。

2. 计算机为什么需要编译器

2.1 程序运行涉及哪些部件

部件 主要作用 可以怎样理解
CPU 执行指令,完成运算和控制 实际执行任务的处理单元
寄存器 保存 CPU 当前处理的数据、指令或地址等信息 CPU 内部容量很小的工作区
RAM(内存) 保存运行中使用的程序和数据 程序执行时的工作空间,通常断电后内容消失
硬盘、固态硬盘等存储设备 长期保存文件 程序和数据的持久存放位置
输入、输出设备 接收输入并呈现结果 键盘、屏幕等人与程序交互的通道

程序保存在磁盘上,与程序正在运行,是两种不同状态。运行时,操作系统会把所需内容装入内存,并安排执行。

2.2 CPU 执行的是具体指令

可以先把 CPU 的工作过程简化为:

text
取得指令 → 理解指令 → 执行操作 → 继续取得指令

它能执行的指令构成指令集。这些指令通常十分具体,例如移动数据、执行算术运算、比较数值或跳转。

在程序员看来,两数相加只需写成:

c
total = left + right;

在机器层面,它可能需要取出数据、完成加法、保存结果等多条指令。实际采用什么指令,由目标机器和编译器等因素决定。

数据和指令在计算机中都以二进制形式表示。CPU 能直接执行的是机器指令,不能直接执行 C 源文件中的文字。

2.3 三种表达层次

层次 表达方式 与硬件的关系
机器语言 用机器指令编码表示操作 直接对应特定指令集
汇编语言 用助记符等形式表达机器指令 仍与特定体系结构紧密相关
C 等高级语言 用变量、表达式、函数等表达处理逻辑 可以把同一套逻辑交给不同目标平台的工具处理

编译器承担翻译工作,使程序员可以集中表达处理逻辑。 不同目标平台需要与之匹配的编译工具。

3. 从源代码到可执行程序

3.1 先分清三类文件

文件 常见名称 包含什么 能否直接运行
源代码文件 hello.c 程序员编写的 C 文本 通常不能
目标代码文件 hello.o、hello.obj 编译得到的机器代码及链接所需信息 通常不能
可执行文件 hello、hello.exe 链接形成、供操作系统加载执行的程序 在兼容的运行环境中可以

源文件应保存为纯文本。把文件名从 .txt 改成 .c,只改变名称,并不等于完成编译。

3.2 编译与链接各做什么

先用两大阶段理解生成程序的过程:

text
源代码 hello.c
       │
       │ 编译过程
       ▼
目标文件 hello.o ───────┐
其他目标文件 ──────────┤
所需库与启动支持 ──────┤
                       │ 链接
                       ▼
                  可执行程序

编译器负责检查和翻译源代码,生成目标代码。若出现阻止编译完成的错误,需要修改后重新编译。

链接器负责连接各部分代码,解析跨文件和库函数的引用,形成可执行程序。

这里的“编译过程”是概括说法;进一步细分还会涉及预处理、编译和汇编等环节。入门时先把“翻译代码”和“连接代码”这两件事分清。

3.3 为什么目标文件还不能直接运行

目标文件里虽然已有机器代码,却通常还存在尚未解决的引用,并缺少构成完整程序所需的其他部分。

例如,程序调用了 printf(),并不意味着编译器已经把它的实现写进该目标文件。链接阶段还需要处理程序对库函数的依赖。

启动支持则负责程序进入正式执行前所需的运行环境准备,以及与系统约定的衔接。

对于库,还要避免一种过度简化的理解:生成可执行文件不代表所有库实现都会完整复制进去。 静态链接可以把所需代码纳入程序;动态链接则可能保留依赖,在加载或运行时使用相应的共享库。

3.4 分开编译的意义

当程序由多个源文件组成时,可以先分别生成目标文件,再统一链接。

这样做有两个直接好处:

  1. 便于按功能划分模块,分别开发和检查。
  2. 某个源文件改变后,通常只需重新编译受影响的部分,再重新链接。

因此,目标文件是支持模块化构建的中间成果。

4. 可移植性与语言标准

4.1 可移植主要指源代码

可移植性关注的是:把程序转到另一个系统时,需要付出多少修改和适配工作。

例如,同一份遵循标准的计算代码,可以交给两个平台各自的编译器,生成不同的机器代码。

text
                 同一份 C 源代码
                 /             \
        平台 A 的编译工具    平台 B 的编译工具
               ↓                 ↓
        平台 A 的程序        平台 B 的程序

不要把源代码可移植理解成“一个可执行文件能在所有计算机上直接运行”。可执行程序还受到 CPU 架构、操作系统、文件格式和运行库等条件限制。

以下因素都会增加移植工作:

  • 使用某个操作系统独有的接口。
  • 直接操作特定硬件。
  • 使用编译器专有扩展。
  • 假定不同平台的数据类型大小等细节完全一致。

4.2 标准为什么重要

如果不同编译器对同一段代码采用互不兼容的规则,程序就难以移植。语言标准为不同实现提供共同的约定。

需要区分三层概念:

  • 语言标准:约定语法、语义和标准库接口等规则。
  • C 实现:编译器、库及相关运行支持对这些规则的具体实现。
  • 编译选项:决定本次构建采用哪套规则、是否启用扩展等。

4.3 本章涉及的标准演进

阶段 需要掌握的重点
K&R C 正式标准出现前的重要语言描述,常称经典 C
C89 / C90 ANSI 于 1989 年发布、ISO 于 1990 年采纳的同一套基础标准,规范语言和标准库
C99 在保持 C 风格的基础上,增强国际化、数值计算等支持,并修补实际使用中的不足
C11 继续适应新技术和安全需求,引入并发相关支持,并把部分特性设为可选

这些名称描述的是本章涉及的演进阶段。学习具体特性时,应留意它属于哪一版标准,以及所用实现是否支持。

标准化也不意味着抹平所有硬件差异。C 会为实现保留一些选择空间,以适应不同机器。

5. 编程的七个步骤

编写代码只是整个过程中的一部分。真正的起点是把问题说明白。

步骤 核心问题 应形成的成果
1. 定义目标 程序要做什么?输入和输出是什么? 明确的任务描述与约束
2. 设计程序 用什么数据表示信息?怎样处理?如何与用户交互? 处理流程、数据方案和模块划分
3. 编写代码 怎样用 C 表达设计? 源文件及必要注释
4. 编译 代码能否正确翻译并完成链接? 可执行程序,或需要处理的诊断信息
5. 运行 程序在目标环境中如何表现? 实际执行结果
6. 测试和调试 结果是否符合目标?错误在哪里? 测试结论与修正后的程序
7. 维护和修改 如何修复问题、增加功能或适配环境? 持续改进的程序

5.1 目标与设计有什么不同

目标说明“做成什么样”,设计说明“准备怎样做”。

例如,目标是“把输入的长度换算成另一种单位并显示结果”;设计则要决定使用什么数据类型、怎样读取输入、采用哪个公式,以及如何处理不合法的内容。

先用自然语言和流程描述问题,有助于把需求判断与语法细节分开。

5.2 测试与调试有什么不同

  • 测试:选择输入和场景,检查实际结果是否符合预期。
  • 调试:发现异常后,定位原因并修复。

“编译成功”“能运行”和“结果正确”是三个不同层次。例如,把换算公式写反,代码仍可能通过编译并正常结束,只是答案错误。

测试应覆盖普通情况、边界情况和不合法输入;修复问题后,还需要重新验证。

5.3 七个步骤会反复往返

设计不合适时,要修改设计;测试暴露需求遗漏时,也可能需要重新定义目标。这些步骤构成迭代过程。

清楚的命名、合理的模块划分和说明意图的注释,可以减少后续修改时重新理解程序的成本。

6. 建立一个最小的编译运行流程

下面用一个小程序把前面的概念串起来。先关注操作流程,不必立即掌握全部语法。

6.1 保存源代码

把以下内容保存到纯文本文件 hello.c:

c
#include <stdio.h>

int main(void)
{
    printf("Ready to learn C.\n");
    return 0;
}

暂时只需知道:

  • <stdio.h> 提供标准输入、输出相关声明。
  • main 是这种普通命令行程序的入口函数。
  • printf 用于输出,字符串中的 \n 表示换行。
  • return 0; 表示程序正常结束。

6.2 一次完成构建

在已提供 cc 命令的 macOS 或类 UNIX 命令行环境中,进入源文件所在目录后运行:

sh
cc -std=c11 -Wall -Wextra hello.c -o hello
./hello

预期输出:

text
Ready to learn C.
命令部分 含义
cc 调用系统配置的 C 编译驱动程序
-std=c11 按 C11 模式处理代码
-Wall -Wextra 启用一组常用警告,帮助发现可疑写法
hello.c 输入源文件
-o hello 指定输出文件名
./hello 执行当前目录中的 hello

一条构建命令可以自动调用编译、链接等工具。命令数量与内部阶段数量并不相同。

6.3 分两步观察目标文件

sh
cc -std=c11 -Wall -Wextra -c hello.c -o hello.o
cc hello.o -o hello
./hello

第一条命令中的 -c 表示生成目标文件后停止,不执行链接。第二条命令再通过编译驱动程序调用链接器,生成可执行文件。

这就对应了:

text
hello.c → hello.o → hello

6.4 工具名称与开发环境

GCC 和 Clang 都能处理 C 程序;cc 在不同环境中可能指向不同工具,可以用 cc --version 查看。

IDE 把编辑、构建、运行和调试等功能集中到一个界面中。使用 IDE 时,也应理解这些操作对应的阶段。入门示例通常适合创建控制台或命令行项目,并确认源文件按 C 语言处理。

如果程序结束后窗口立即关闭,可以从已有终端运行它,便于观察输出。具体菜单和按钮会随工具而变,构建与执行的基本机制是一致的。

7. 用一次单位换算练习程序设计

以“输入英寸,输出厘米”为例,先完成目标定义和设计,不急着写 C 代码。

7.1 明确目标

  • 输入:一个用英寸表示的长度,允许包含小数。
  • 处理:使用 厘米数 = 英寸数 × 2.54 换算。
  • 输出:换算结果,并明确标出单位。
  • 约束:本例把长度限定为非负数;非数字或负数输入需要给出错误提示。

“长度必须非负”属于需求约束,应在设计前明确下来。

7.2 设计处理流程

text
开始
  ↓
提示输入长度,并说明输入单位是英寸
  ↓
读取输入
  ↓
输入是否为有效的非负数?
  ├─ 否 → 显示错误提示 → 结束
  └─ 是 → 乘以 2.54 → 显示厘米结果 → 结束

数据表示应能容纳小数,避免把 1.5 英寸截成整数。输出也应事先约定精度,例如保留两位小数。

7.3 先写出测试预期

输入(英寸) 预期表现 检查目的
1 输出 2.54 厘米 基本换算
0 输出 0.00 厘米 零值边界
1.5 输出 3.81 厘米 小数处理
100 输出 254.00 厘米 普通较大数值
-1 提示长度不能为负 需求约束
abc 提示输入无效 非数字输入

先写测试预期,可以在实现之前发现需求中的模糊之处。

8. 学习时应采用的方式

采用逐步深入的方法:先理解一个概念的用途,在小程序中使用,再回头学习细节。函数、循环、字符串等内容都适合这样反复接触。

阅读示例时,可以采用以下顺序:

  1. 先预测程序会做什么。
  2. 亲手输入并编译运行。
  3. 对照预测解释结果。
  4. 每次只改动一个地方,观察变化。
  5. 遇到诊断信息时,尝试理解其原因。

阅读命令和交互示例时,要分清程序输出、用户输入和表示按键的说明。占位名称需要替换成实际值;表示按键的文字也不一定需要原样输入。

Aron 的清风日常

作者 Aron · 发布于 2026.07.10

愿这份记录能为你提供一点参考。发现问题,欢迎通过 GitHub 或 邮件交流。

如果这篇记录对你有用,留下一份喜欢。

← 所有文章继续阅读C Primer Plus(第6版)· 附录整理:语法与标准库速查 →
SEARCH MY NOTES

在记录里,找一点答案

搜索标题、标签与全文 · 按 Esc 关闭