⚙️ 附录
📄 一句话概括

整本书讲的就是 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 秒实验

  1. 在纸上把 a[5] 里五个元素的地址写出来(基址加序号乘元素长度)。
  2. 想一想:为什么 C 不检查数组越界,是漏了还是故意的。

常见误区

  1. 以为局部变量和全局变量一样会清零。只有全局才会自动清零。
  2. 以为数组越界会报错。C 不管,你得自己看着。
自测三问(点开对答案) 1. C 语言里变量为什么必须声明类型? —— 因为每个类型的宽度是确定的,编译器要据此分配内存、决定运算方式。 2. 局部变量和全局变量各有什么特点? —— 局部变量定义在函数内、函数结束就消失;全局变量定义在函数外、程序运行期间一直存在,会自动清零。 3. 为什么 C 的数组容易出越界问题? —— 因为 C 不做越界检查,超出范围的访问照样允许,只会读到别的数据或写坏内存。

相关笔记