前言
关于这门课程
欢迎来到《零基础C语言入门到进阶》。本课程将带领你从零开始,系统地学习 C 语言——一门古老而又深刻的编程语言。
本课程使用的开发环境
本课程使用 VSCode + Clang + CMake 这套组合:VSCode 负责代码编辑,Clang 负责编译,CMake 负责组织多文件项目的构建。
市面上大多数 C 语言教材使用的是 Visual Studio(VS)——开箱即用,点击“运行“就能看到结果。但我们刻意选择了更难入门的 VSCode。原因很简单:VS 替你隐藏了编译和链接的所有细节,你甚至不需要知道“编译器“三个字的存在;而 C 语言本身就是一门需要理解底层才能学好的语言。从终端亲手输入 clang 命令,亲眼看到 .c 文件变成可执行程序——这份认知,是 C 语言基本功的一部分。关于工具选择的详细讨论,请见第 0.5 章。
配套源码的第一至第十六章提供了 build.sh 和 build.ps1 两套手动编译脚本,可直接使用 Clang 编译本章所有示例,并将生成的程序放入对应章节的 bin/ 目录。第十七章以后开始使用 CMake 组织项目构建。
课程内容
本课程分为两大部分:
入门篇(已完稿)——从计算机基础概念开始,逐步学习 C 语言的核心语法和基本数据结构:
- 第零章:计算机和编程的基础概念扫盲
- 第 0.5 章:开发环境与工具(VSCode + Clang)
- 第一至第十五章:C 语言核心语法体系,包括类型、数组、字符串、结构体、内存模型、指针、函数指针、动态内存、生命周期、共用体、枚举、宏与文件 I/O
- 第十六章:头文件、源文件与多文件代码组织,使用 Clang 手动编译
- 第十七章:CMake 初识,把多文件项目交给统一的构建系统组织
- 第十八章:基本数据结构,包括单链表、双链表、栈、链式队列和环形队列
- 第十九至第二十章:常见概念辨析与入门阶段总结
进阶篇(待完稿)——深入 C 语言的高级编程技巧和实战项目:
- CMake 进阶:创建与导入库、编译参数配置、构建优化
- 函数接口设计:利用
const type *约束只读语义、借助void *实现通用接口,设计出不易误用的 API - goto 的使用:C 语言最具争议的关键字——
goto的语法、陷阱与合理使用场景(错误集中处理、跳出嵌套循环、状态机) - 宏魔法:利用预处理器消除样板代码、实现编译期分支和代码生成
- 内存对齐:分析
struct与union的成员排布和整体大小,理解对齐规则如何影响内存布局;利用共用体实现 SSO(Small String Optimization)等空间优化技术 - 柔性数组:在结构体末尾放置可变长度缓冲区,一次分配同时容纳元数据和数据
- 面向对象编程:不止于结构体嵌函数指针——手动构造虚表(vtable),在 C 中实现封装、继承与运行时多态
- initcall 风格机制:程序启动时的自动注册思路,属于平台/工具链扩展话题
- 树与图:二叉树、平衡树、图的遍历与常见算法
- 实战项目:待定
C 语言:没有 GC,面向底层
C 语言诞生于 1970 年代,由 Dennis Ritchie 在贝尔实验室创造。五十余年过去,它依然健在——操作系统内核(Linux、Windows、macOS)、嵌入式设备、数据库引擎、编程语言的运行时(Python 的解释器、Lua 的虚拟机)……这些构成现代软件世界基础设施的核心组件,几乎全部由 C 语言(及其后代 C++)编写。
C 语言有两个鲜明的特点,值得你在学习之初就有所认知:
第一,C 语言没有垃圾回收(Garbage Collection,GC)。
如果你之前接触过 Java、Python、Go、JavaScript 等语言,你可能已经习惯了这样一件事:创建一个对象,用完之后就不管了——“反正 GC 会来处理的”。在 C 语言中,这个前提不成立。你通过 malloc 申请的每一块内存,都必须在不再使用时通过 free 显式归还。忘记释放会导致内存泄漏;过早释放或重复释放则会导致悬空指针和程序崩溃。这意味着你需要对每一块内存的“生命周期“保持清醒的分析——这块内存是谁分配的?它会在什么时候、由谁释放?这种纯手动内存管理使得 C 语言编程对程序员的要求更高,但也恰恰是这种“没有中间商“的透明性,使得 C 语言能够胜任那些对性能和资源控制有极致要求的场景。
第二,C 语言非常接近底层。
你写的每一个变量、每一层指针、每一次类型转换,都紧密对应着内存中的实际布局。你在 C 语言中操作的地址,直接对应操作系统虚拟地址空间中的地址。C 语言没有虚拟机,没有解释器,没有沙箱——编译器将你的代码直接翻译成 CPU 可以执行的机器指令。这意味着你需要理解一些“底层“的概念:内存模型、栈与堆、指针与地址……这些概念在学习初期可能让人感到困惑,但一旦掌握,你将对“计算机到底是怎样工作的“建立起一种不可替代的直觉。
如何学习 C 语言
每位学习者的背景不同,适合的学习路径也有所不同。请根据自己的情况,选择适合你的方式。
学习 C 语言的重点
如果你明确了 C 语言“没有 GC、贴近底层“这两个特点,那么学习 C 语言的重点也就自然浮现了——几乎全部围绕内存展开:
- 内存模型:C 语言把内存划分为栈(stack)、堆(heap)、静态区等不同区域,每一块内存属于哪个区域,决定了它的分配方式和生命周期。理解这个模型,是后续一切内存操作的基础。
- 指针与内存操作:指针是 C 语言最核心也最独特的机制。通过指针,你可以直接读写任意地址上的内存——这正是 C 语言“贴近底层“的具体体现。但这也意味着一次错误的指针操作就能让整个程序崩溃,几乎没有缓冲地带。
- 手动内存管理:C 语言没有 GC。你通过
malloc申请的每一块堆内存,都必须在你认为合适的时机通过free归还。忘记释放 → 内存泄漏;过早释放 → 悬空指针;重复释放 → 未定义行为。你需要对每一块动态内存的“所有权“有清晰的认识。 - 生命周期分析:一个变量在什么时候有效?一块内存在什么时候被释放?谁负责释放?这些问题在 GC 语言中几乎不用思考,但在 C 语言中,它们是日常。你需要养成一个思维习惯:看到一块内存,就下意识追问——它从哪来?什么时候失效?我还能不能安全地使用它?
这些概念分散在本课程的多个章节中——第六章(内存模型)、第七章(指针)、第十章(堆内存分配)、第十二章(生命周期)——它们不是彼此孤立的,而是共同构成了 C 语言的“内存世界观“。你会在反复的实践中将它们串联起来。
如果你有编程基础(学习过其他语言)
你已经有编程基础,懂得变量、循环、分支、函数等基本概念。你可以:
- 直接从第一章的猜数游戏开始——通过一个完整的代码示例快速熟悉 C 语言的语法风格,然后按章节顺序正常学习即可。
- 重点关注 C 语言与其他语言的不同之处:C 没有类(但有结构体),没有异常处理(通过返回值),没有命名空间,没有函数重载——这些“缺失“不是缺陷,而是由 C 语言的设计定位决定的。
- 如果你之前学习的是 GC 语言(Java、Python、Go、JavaScript 等),你需要特别注意:C 语言没有 GC。每当你使用
malloc分配内存时,你都要问自己一个问题——“这块内存什么时候会被释放?由谁来释放?“这种对内存生命周期的主动分析,是 GC 语言转向 C 语言时最需要刻意练习的能力。
如果你没有编程基础(零基础入门)
欢迎!编程是一项可以通过学习掌握的技能,不需要任何“天赋“作为前提。本课程在设计时已经充分考虑了零基础学习者的需求:
- 从第零章开始——第零章专门为你准备了计算机和编程的最基础概念科普:什么是 CPU、什么是内存、什么是二进制、什么是编译……这些概念是后续学习所必需的,需要提前掌握。
- 先“过“语法,再看示例——学习语法时,第一遍只需要知道“哦,原来 C 语言里有这么个东西,它大概长这个样子,大概是用来做这个的“。不必追求第一遍就百分之百掌握。然后看示例代码:先看懂,再自己手敲。看懂每一行在做什么之后,关闭文档,自己从头敲一遍。这个过程反复进行,慢慢地你就会发现语法已经从“记忆“变成了“习惯“。
- 遇到问题不要慌——编译器的错误提示看起来密密麻麻,但大多数时候,你只需要看第一行错误信息就够了。读它,理解它指向的问题,修复它。这个过程反复发生,渐渐地你就能快速定位和解决问题。
无论你属于哪一类学习者,学习 C 语言最好的方式都是一样的:多写代码。阅读一百行代码,不如自己敲十行;自己敲十行,不如自己独立写一个程序。从模仿开始,慢慢走向独立。
AI 时代为什么要学 C
在动笔写这门课程的时候,AI 辅助编程正在以惊人的速度进化。一个很自然的问题是:当 AI 能帮你写代码,我们还需要学 C 吗?在给出答案之前,先看三个客观现实。
三个客观现实
第一,AI 越来越强,“vibe coding” 能完成的事情越来越多。
从 GPT-4 到 Claude Opus 4.8,AI 的代码生成能力在过去两年里发生了质的飞跃。如今,你完全可以用自然语言描述需求,让 AI 替你生成一个可用的网站、一个命令行工具、甚至一个简单的游戏。“Vibe coding”——凭感觉和 AI 对话式编程——已经从概念变成了很多开发者的日常。
第二,截止到 2026 年中(GPT-5.5 / Opus 4.8 时代),AI 在 C 和 C++ 上依然表现不佳。
这是一个值得深思的事实。同一个模型,写 Python 可能行云流水,写 C 却屡屡踩坑——悬空指针、内存泄漏、未定义行为、复杂的编译链接问题……AI 在这些问题上仍然会犯低级错误,而且错误往往更隐蔽、更难排查。C 语言的“没有 GC“和“贴近底层“这两个特点,恰恰也是 AI 目前最不擅长处理的场景。
第三,C 正在受到 Rust 的冲击。
Rust 带着现代语言设计理念——所有权系统、借用检查、零成本抽象——正在系统编程领域快速扩张。Linux 内核已经接受了 Rust 作为第二种官方语言,越来越多的底层项目开始认真考虑用 Rust 替代 C。C 的地位不再是毫无争议的默认选项。
那么我们为什么还要学 C?
正因为以上三点,有些人可能会得出一个结论:C 正在过时,不值得学。但我们的看法恰好相反。
首先,从就业和技能角度,C 仍然是必要选择。
C 是当前底层开发——尤其是 Linux 内核开发和嵌入式开发——的绝对主流语言。Linux 内核超过 95% 的代码、FreeRTOS 等主流嵌入式实时操作系统、以及海量的设备驱动和固件,全部由 C 语言编写。Rust 在底层领域的渗透确实在加速,但它能不能真正主导底层开发、甚至在可见的未来取代 C++,至今仍是巨大的未知数。这些基础设施的代码存量以亿行为单位,它们的维护、演进和新增需求,将在未来数十年持续产生对 C 程序员的需求。把 C 作为你技能栈的一部分,是对这项确定性事实的务实回应。
其次,C 是理解计算机世界最直接的通道。
在所有主流编程语言中,C 是最接近硬件的语言——没有之一。没有虚拟机,没有垃圾回收,没有解释器。你写的每一个变量、每一层指针、每一次类型转换,都直接映射到内存中的真实字节。C 语言就是整个当代计算机系统的基石:操作系统用它写,数据库用它写,Python 的解释器用它写,Lua 的虚拟机也用它写。学习 C,就是在学习计算机底层的“母语“。
这一点的重要性,不会因为你最终选择 Java、Python、Go 还是 Rust 而改变。当你理解了栈和堆、理解了指针和地址、理解了编译和链接的全过程,你再去学任何一门高级语言,都会发现那些“魔法“不再是魔法——你知道它们在底层大概是怎么运作的。C 语言给你的,是计算机世界的底层直觉,是一种不可替代的内功。
换句话说:学 C 不一定是为了“写 C“,而是为了真正理解计算机。
⚠️ 一个严肃的提醒
AI 辅助编程越来越强,有些学习者可能会产生一种错觉:基础没那么重要了,反正 AI 能写。
这种想法很危险,尤其是对于 C 语言。理由有二:
第一,AI 无法兜底,最终兜底的人是你。
AI 能帮你写出 80% 甚至 90% 的代码,但它搞不定的那 10%,恰恰是最棘手的部分——隐蔽的内存泄漏、微妙的数据竞争、难以复现的未定义行为。换句话说,AI 过滤掉了简单问题,留给人类处理的是它不擅长的复杂问题。如果你的基础不扎实,面对这些问题你将毫无头绪——因为你连排查的起点都找不到。AI 越强,它留给你的“残局“就越难,对你的基础要求反而越高。
第二,C 语言不同于任何应用层语言——它太底层了,而 AI 对底层的理解远没有你想象的好。
同一个模型,在应用层语言上表现游刃有余,面对 C 语言却频繁出现悬空指针、栈溢出、越界访问等低级错误——根源在于 AI 对内存生命周期、所有权边界这些底层约束缺乏精确判断。
但这不仅仅是 AI 的问题——这也是你作为一个 C 程序员必须面对的现实。C 不是那种可以无脑 new 一个对象、剩下的交给 JVM 或 GC 来善后的语言。一个 C 语言程序员不懂内存管理、不懂进程调度、不懂内核的基本原理——这是说不过去的。你的程序可能跑在一块只有几十 MB 内存的单片机上,你的代码直接操作每一字节的物理内存,每一块内存都要算着用;你可能在亲手优化一个操作系统的调度器,也可能在改造一个内核模块。这种工作,即使 AI 帮你完成了 99%,它也永远不可能是 100%——世界上不存在 100% 正确的代码,AI 也不例外。而当那 1% 的问题出现、AI 无法解决的时候,你是否有能力兜底?
把 AI 生成的 C 代码不经审查直接放进项目,等于把炸弹埋进了地基。在 C 语言的世界里,你不是 AI 的监督者——你是它最后的防线。
不要因为 AI 的强大,就觉得基础内容不重要。恰恰相反——在这个 AI 能替你写大量代码的时代,你能走多远,取决于你的基础有多深。 基础,是你兜底 AI 的底牌。