整本书讲的就是 C 程序跑起来之后发生了什么;这个附录反过来,教你把那个 C 程序亲手写出来。
正文十二章是把程序【拆开看】,这个附录是把它【装起来用】——两边合起来,这本书才完整。
一、C语言的特点
编译型:写完要先编译成可执行文件,再运行(第八章讲过这条链子)。 类型明确:每个变量都得声明类型,整数多少字节是语言规定死的。 贴近硬件:指针、内存这些概念在 C 里是摆在明面上的,这正是它适合写操作系统和驱动的理由。 可移植但要看指令集:换平台往往要重新编译(7.2 讲过 CPU 指令集这件事)。
二、变量、函数与数据类型
变量要带类型声明:int a = 1;——类型写在名字前面。
函数也要写返回类型:int add(int a, int b)——逗号分隔的参数也各带类型。
常见类型:int(4 字节)、double(8 字节)、char(1 字节),指针在 64 位系统上是 8 字节。
程序的入口是 main 函数——这一点和 Python 不同(Python 是从头往下执行)。
三、输入、运算、输出
输出用 printf:printf("%d\n", a);——用格式符告诉它这个值该怎么写出来。
输入用 scanf:scanf("%d", &a);——注意那个取地址符号,它要你把变量的地址交出去。
运算符和别的语言差不多:加加减减、比大小、与或非,写法几乎一样。
要留意的是整数除法:两个整数相除,结果会直接丢小数——这是 C 里最常见的意外之一。
四、创建和使用函数
用之前要先让编译器知道它长什么样——所以在文件开头写函数声明,或者把定义放到前面。 声明和定义是两件事:声明只说【有这么个函数、参数和返回是什么】,定义才写清【里面干什么】。 函数的参数是【复制进去的】——在函数里改参数,改的是副本,外面的变量不受影响。 想让函数改外面的变量,得传指针——这也是第十章讲的全局变量与局部变量的分界。
五、局部变量与全局变量
局部变量:定义在函数里面,函数一结束就没了(第十章在汇编层看过这件事)。 全局变量:定义在所有函数外面,整个程序运行期间都活着,谁都能改。 两者的取舍和第十章说的一样:小的、临时的用局部;要跨函数共享才用全局,而且要小心。 还有个细节:全局变量会自动清零,局部变量不会——这是很容易忽略的一处差别。
六、数组与循环
数组是【一串同类型的格子】:int a[5] 就是五个 int 挨着放(第四章讲过内存里的样子)。
下标从 0 开始:a[0] 是第一个,a[4] 才是最后一个。
C 不会替你检查越界——a[10] 照样让你访问,这是 C 里最危险的坑。
循环用 for 或 while:和第十章看到的汇编完全对得上——比较、执行、跳回去。
七、其他语法
指针:装地址的变量(第四章讲过)——C 的灵魂,也是它最容易出事的地方。
结构体:把几个不同类型的数据打包成一个新类型,是 C 里做复杂数据的方式。
预处理:#include 把别的文件引进来;#define 定义一个替换用的名字。
注意这两样都发生在【编译之前】——所以它们不属于 C 的语法本身。
八、动手 Demo:亲手尝试C语言
【📋 实操路径】C 语言的第一次运行只要两条命令:gcc 编译、./hello 运行。 沙箱里没有 C 编译器,但 C 的几个特点都能用 Python 实测出来。
import struct
# C 里每种类型的宽度是明确的
for name, code in (("int", "i"), ("double", "d"),
("char", "c"), ("指针", "P")):
print(name, struct.calcsize(code), "字节")
# 装不下会绕回去,这是 C 特有的现象
big = 2147483647
print("加 1 之后(模拟 32 位)", ((big + 1) & 0xFFFFFFFF) - 0x100000000)
实跑输出:
--------------------------------------------------------------------
亲手尝试C语言
--------------------------------------------------------------------
【📋 实操路径】**C 语言的第一次运行,只要两条命令:**
写文件 hello.c
编译 gcc hello.c -o hello
运行 ./hello (Windows 上是 hello.exe)
沙箱里没有 C 编译器,**但 C 的几个特点都能用 Python 实测出来。**
**一、每种类型占多少字节,是语言规定死的:**
类型 字节数 说明
----------------------------------------------------
int 4 字节 整数,最常用
float 4 字节 单精度小数
double 8 字节 双精度小数
char 1 字节 一个字符
long 4 字节 本机上和 int 一样宽
指针 8 字节 地址,64 位系统上 8 字节
**这就是 C 和 Python 最大的不同之一:C 里每个类型的宽度是明确的。**
所以 C 程序员必须关心【这个数会不会装不下】——**Python 里不用,整数能一直长。**
**二、装不下会【绕回去】,这是 C 特有的现象:**
32 位有符号整数的最大值 2147483647
再加 1,结果变成 -2147483648
再减 1 回来 2147483647
**不是报错,是悄悄变成负数**——**这类问题在 C 里叫溢出,是最常见的坑之一。**
而 Python 里同样的式子:**2147483648,一点事都没有。**
**三、C 的字符串没有【长度】,靠一个结尾的 0 表示结束:**
Hello 在内存里其实是 b'Hello\x00'
字符 5 个,实际占位 6 字节(末尾多一个 0)
忘记补那个 0 会怎样 读下去读到别的内容,直到碰巧遇见 0
**这就是 C 里字符串函数都要靠【找到 0 才停】的原因。**
实测:int 4 字节、double 8 字节、char 1 字节、指针 8 字节。 32 位整数加 1 之后悄悄变成负数——这叫溢出;而在 Python 里整数能一直长,不会出事。
30 秒实验
- 在纸上把 a[5] 里五个元素的地址写出来(基址加序号乘元素长度)。
- 想一想:为什么 C 不检查数组越界,是漏了还是故意的。
常见误区
- 以为局部变量和全局变量一样会清零。只有全局才会自动清零。
- 以为数组越界会报错。C 不管,你得自己看着。
自测三问(点开对答案)
1. C 语言里变量为什么必须声明类型? —— 因为每个类型的宽度是确定的,编译器要据此分配内存、决定运算方式。 2. 局部变量和全局变量各有什么特点? —— 局部变量定义在函数内、函数结束就消失;全局变量定义在函数外、程序运行期间一直存在,会自动清零。 3. 为什么 C 的数组容易出越界问题? —— 因为 C 不做越界检查,超出范围的访问照样允许,只会读到别的数据或写坏内存。- ☐ 下一节(亲手尝试Python)
相关笔记
- Python交互模式的使用方法 — 用另一种语言试试手
- 编译器负责翻译源代码 — C 的编译那一步在干什么
- 全局变量和局部变量的工作原理 — 这两类变量在汇编里的样子
- 🔷 C++基础 · 索引 — 在 C 基础上再加一层
- ⚙️ 程序是怎样跑起来的 · 索引