第 3 章:C 程序基本结构
24 分钟阅读
第 3 章:C 程序基本结构
🧐 想象一下,你要建一座房子。首先你得知道房子有哪些"标配房间"吧?门在哪里、客厅怎么走、窗户朝哪开。C 程序也一样,有它固定的"房间格局",你不按这个格局来,编译器(compiler)就一脸懵,不知道你想干嘛。
本章就是带你参观 C 语言的"样板间",把一个最最基本的 C 程序掰开揉碎讲清楚。别怕,咱们从最最简单的例子开始,保证你看完之后对 C 程序的结构了然于胸,甚至能跟朋友吹嘘:“嘿,C 程序嘛,我懂!”
3.1 #include 预处理指令:<> vs "" 的搜索路径差异
先来看一段代码,这大概是 C 语言里你能写出的最简程序之一了:
| |
你有没有好奇过 #include <stdio.h> 这一行是怎么回事?
#include 是一个预处理指令(preprocessor directive),顾名思义,它在正式编译之前就"动手"了。你可以把它理解成——“拷贝粘贴小能手”。当你写 #include <stdio.h> 时,预处理器会找到 stdio.h 这个文件(standard input/output header,标准输入输出头文件),然后把它的全部内容原封不动地"粘贴"到你的代码里。
那 <stdio.h> 和 "stdio.h" 有什么区别呢?区别大了去了:
尖括号 < >:系统标准目录搜索
用尖括号,告诉预处理器:“去系统默认的地方找这个文件,别来烦我!” 系统会到编译器内置的标准 include 目录去找。在 Linux/macOS 上通常是 /usr/include,在 Windows 上取决于你的 IDE 设置。
双引号 "":先本地后系统
用双引号,预处理器会先在当前目录(也就是源代码所在的目录)里找,找不到才去系统目录"碰碰运气"。
一图胜千言
flowchart TD
A["#include 指令"] --> B{"使用 <> 还是 ""?"}
B --> C["<header.h>"]
B --> D["\"header.h\""]
C --> E["直接在系统标准目录查找"]
D --> F{"先在当前源码目录查找"}
F --> G["找到?"]
G -->|是| H["使用该文件"]
G -->|否| I["回退到系统标准目录查找"]
H --> J["预处理器替换"]
I --> J实战演示
假设你有这样的目录结构:
my_project/
├── main.c
├── my_utils.h ← 你自己写的头文件
└── utils/
└── helper.h ← 子目录里的头文件
在 main.c 中:
| |
| |
💡 小技巧:自己写的头文件用双引号,系统头文件用尖括号。这是一个约定俗成的规矩,体现了"谁写的谁负责"的精神。
万一找不到会怎样?
编译时会直接报错,类似这样:
fatal error: my_missing_header.h: No such file or directory
| #include "my_missing_header.h"
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
编译器内心的台词是:“我去哪儿都找不到你说的这个文件,你是不是打错字了?或者路径写错了?” 总之,乖乖去检查文件名和路径吧。
3.2 main 函数:程序入口
如果说 C 程序是一座大楼,那 main 函数就是它的正门——程序运行时,CPU 从这里开始执行指令。没有 main?对不起,这程序没法跑,编译器会毫不客气地给你一个错误。
标准写法:int main(void)
| |
这里 int 表示 main 函数的返回值类型(return type)是整数(integer),main 执行完毕后向操作系统返回一个整数。通常返回 0 表示"一切正常,没毛病",返回非零表示"出了点岔子"。
void 在函数参数列表里表示"这个函数不接受任何参数"。
那么写成 int main()(空括号)行不行呢?这取决于你用的 C 标准,这里有一条容易踩的历史分界线:
| 标准 | int main() 的含义 |
|---|---|
| C89 ~ C17 | “参数未知"(没有原型,编译器不检查实参) |
| C23 及以后 | 等同于 int main(void),即”不接受任何参数" |
C23 采纳了提案 N2841《No Function Declarators without Prototypes》,规定空参数列表就等同于只有
void。也就是说,那个"空括号表示参数未知"的老规则在 C23 里已经被删掉了。这件事现在很要紧:GCC 15 开始默认就是 C23。也就是说,同一段
int main()在 GCC 14 和 GCC 15 下的语义并不一样。
不管在哪个标准下,显式写 void 都是最好的习惯——它把"零参数"这个意图明明白白写出来,不依赖标准版本的差异,也不会让读代码的人去猜。
⚠️ 危险警告:void main() 是非标准扩展,切勿使用!
有些老旧的编译器、某些嵌入式开发环境、或者一些野路子教材会教你写:
| |
千万别学! 这是非标准的(non-standard)。ISO C 标准从来没有定义过 void main() 这种形式。虽然在一些特定的编译器/平台上它可能碰巧能跑,但这不代表它是正确的。
🚨 血的教训:如果你去面试,面试官看到你写
void main(),可能直接在心里给你扣 50 分——这属于"C 语言驾照扣满12分"级别的操作。养成好习惯,从今天起只用int main(void)!
有些平台支持 int main(int argc, char *argv[])
如果你想让程序运行时接收命令行参数(比如 myprogram.exe --help),可以这么写:
| |
运行结果:
收到了 1 个参数
argv[0] = ./a.out
收到了 3 个参数
argv[0] = ./a.out
argv[1] = hello
argv[2] = world
💡
argc至少是 1(程序名本身),argv[argc]是NULL(空指针)。
return 0; 真的需要吗?
在 main 函数末尾,return 0; 其实是可选的(从 C99 开始)。如果你省略它,编译器会帮你隐式地加上 return 0;。但为了代码清晰明了,建议还是显式写出来。
3.3 printf 与转义序列
printf 是 C 语言里最常用的格式化输出函数(formatted output function),来自 stdio.h,专门负责把数据打印到屏幕上。
基本用法
| |
什么是转义序列(Escape Sequence)?
你注意到上面的 \n 了吗?这不是两个字符,而是一个转义序列——以反斜杠 \ 开头的特殊字符,用于表示那些"看不见的"或者"不好直接写"的字符。
常用转义序列一览
| 转义序列 | 含义 | 生活中的比喻 |
|---|---|---|
\n | 换行(newline) | 打字机回车换行 |
\t | 制表符(horizontal tab) | Word 里按 Tab,效果是空出一段 |
\r | 回车(carriage return) | 打字机把手推回左边 |
\b | 退格(backspace) | 键盘上的 Backspace |
\a | 警告/响铃(alert) | 电脑发出"滴"的一声 |
\\ | 反斜杠本身 | 要打一个 \ |
\' | 单引号本身 | 在字符串里打单引号 |
\" | 双引号本身 | 在字符串里打双引号 |
\0 | 空字符(null character) | 字符串的"句号",标志字符串结束 |
实战演示
| |
运行结果:
第一行
第二行 对齐了
ABD
双引号"里面"有双引号
反斜杠\slash
World
C:\Users\name
\n vs \r:傻傻分不清?
在 Linux/macOS 上,换行符是 \n(LF, Line Feed)。
在 Windows 上,传统上用 \r\n(CRLF, Carriage Return + Line Feed)。
在 老 Mac(OS X 之前)上,只用 \r。
现代编译器通常能自动处理,所以一般你只用 \n 就够了。但如果你写跨平台代码涉及文件读写,这事儿得留个心眼。
\0:字符串的"隐形保镖"
每个字符串常量末尾都有一个隐藏的 \0(空字符),它标志着字符串的结束。C 语言的字符串处理函数都靠它来知道"到哪里为止"。
| |
💡 编译器在处理
"ZhangSan"时,偷偷在末尾多加了一个字节,存的就是\0。所以strlen("ZhangSan")返回 8(不数结尾的\0),但sizeof("ZhangSan")返回 9(数上了)——这哥俩差的那个就是\0在搞事情!
3.4 注释规范
注释(comment)是什么?给人类看的说明文字,编译器直接无视。 好的注释能让你的代码三年后自己还能看懂,烂的注释比没注释还害人。
注释的两种形式
单行注释://
| |
多行注释:/* ... */
| |
⚠️ // 是 C99 才支持的!
这是新手很容易踩的坑。// 单行注释是 C99 标准(1999年)才引入的。在此之前,C 语言只有 /* */ 风格的注释。
如果你用古老的 C89/ANSI C 标准编译,早期编译器可能直接报错(“不认识这是个啥玩意儿”),而较新的编译器则可能把 // 当作两个 / 运算符,和后面凑成一个 /,导致代码逻辑完全乱套:
| |
⚡ 建议:现在写代码一律用 C99 及以后的标准(
gcc -std=c11),放心大胆用//。如果有人让你用 C89,那可能是上世纪的教材,赶紧跑。
注释的"潜规则"
注释要解释"为什么",不是"是什么"。代码本身已经说明了"是什么",注释要说清楚动机和意图。
1 2 3 4 5// ❌ 烂注释 i++; // i 加 1 // ✅ 好注释 i++; // 跳过已处理的用户,因为数组紧凑后下标要前移保持注释和代码同步。改代码不改注释,比没注释还误导人。
不要用注释来"注释掉"代码,尤其是在协作项目里。用版本控制(git)来管理历史。
3.5 C 程序的 8 个翻译阶段
你知道你写的 .c 文件是怎么变成可执行程序的吗?这中间经历了8个翻译阶段(translation phases)。听起来吓人?没关系,咱们一个个拆开来看。
flowchart LR
A[".c 源文件"] --> B["阶段1:字符映射到源字符集"]
B --> C["阶段2:删除反斜杠+换行(续行)"]
C --> D["阶段3:切成预处理记号\n(注释替换成一个空格)"]
D --> E["阶段4:预处理\n(宏展开、#include、条件编译)"]
E --> F["阶段5:字符/转义序列\n转成执行字符集"]
F --> G["阶段6:相邻字符串字面量拼接"]
G --> H["阶段7:预处理记号→记号,\n语法语义分析并翻译"]
H --> I["阶段8:解析外部引用并链接"]📌 这 8 个阶段是 C 标准第 5.1.1.2 节明确规定的,而且标准特别强调:这只是概念上的划分,实际编译器完全可能把若干阶段合并在一起做(比如 GCC 的
-E就把 1~4 阶段一口气做完)。
下面是每个阶段准确的职责(很多教程在这里讲错,请认准标准原文):
| 阶段 | 做什么 |
|---|---|
| 1 | 把物理源文件的字符映射到源字符集,并统一换行符 |
| 2 | 删除每一处"反斜杠 + 换行",把物理行拼成逻辑行 |
| 3 | 把源文件切成预处理记号(preprocessing token)和空白/注释;每个注释替换成一个空格 |
| 4 | 执行预处理指令:宏展开、#include、条件编译……结束后所有预处理指令被删除 |
| 5 | 把字符常量和字符串字面量里的字符与转义序列,转成执行字符集中的对应字符 |
| 6 | 把相邻的字符串字面量拼接成一个 |
| 7 | 把预处理记号转成记号,做语法/语义分析,翻译成翻译单元 |
| 8 | 解析所有外部引用,链接库,生成可执行程序映像 |
3.5.1 字符映射与换行符标准化
阶段1:把源文件里的所有字符映射到源字符集(source character set),并且把不同平台的行尾标记统一成换行符。
阶段2:删除每一处反斜杠 + 换行符。这就是所谓的"续行"——\ 把两行粘成一行,物理行由此变成逻辑行。
阶段3:把源文件切成预处理记号(preprocessing token)和空白序列。这个阶段有个很实用的细节:每个注释会被替换成一个空格,而不是"直接删掉"。
| |
💡 这就解释了一个常见疑问:为什么
a/**/b不会变成ab?因为/**/在阶段 3 被换成了一个空格,所以它是a和b两个记号。想拼接记号得用##运算符,那是阶段 4 的宏展开干的事。
3.5.2 续行处理、三字符组(trigraph)替换
续行处理
如果一行代码太长了,可以用 \(反斜杠)在行末续行:
| |
编译后和单行效果完全一样,因为 \ 把两行"粘"在一起了。
三字符组(Trigraph):已经进入历史的"化石"
三字符组是 C89/C90 引入的稀奇古怪的东西,目的是照顾那些字符集里没有某些符号的设备。简单说,就是用三个 ASCII 字符的组合来代表一个"不好直接输入"的字符。
| 三字符组 | 替换为 |
|---|---|
??= | # |
??/ | \ |
??( | [ |
??) | ] |
??< | { |
??> | } |
??' | ^ |
??! | ` |
??- | ~ |
😂 举个例子:
??=会被替换成#,所以??=include <stdio.h>会被当成#include <stdio.h>。听起来很荒谬?确实!它的完整生命周期是这样的:
标准 三字符组的地位 C89/C90 正式引入,在阶段 1 完成替换 C99 标记为过时(obsolescent) C17 依然存在(GCC 需要额外加 -trigraphs才启用)C23 彻底删除(提案 N2940《Remove Trigraphs??!》) ⚠️ 这里要纠正一个流传很广的错误说法:“C23 仍然识别三字符组,只是会给出诊断"是错的。C23 的变更清单里明确列着"移除三字符组”,标准正文里有关三字符组的内容已被完全删掉——在 C23 下
??=就是三个普通的问号和等号,不再有任何特殊含义。
3.5.3 预处理:宏展开、#include 插入
阶段4 是预处理(preprocessing),这是编译前最重要的一步。
预处理指令(以 # 开头的行)在这个阶段被执行:
#include:把头文件内容"粘贴"进来#define:定义宏(macro),做文本替换#if/#ifdef:条件编译- 等等
| |
预处理后,PI 就消失了,全部变成了 3.14159。你可以通过 gcc -E 看到预处理后的结果。
3.5.4 编译 → 汇编 → 目标文件
阶段5 ~ 阶段7:这三个阶段合起来就是我们平时说的"编译"(compile)。标准对它们的定义是:
- 阶段5:把字符常量、字符串字面量里的字符和转义序列,转成执行字符集(execution character set)中对应的字符。比如
'\n'在这时候才真正对应到目标平台上"换行"那个字节值。 - 阶段6:把相邻的字符串字面量拼接成一个。这就是为什么
"abc" "def"等价于"abcdef"。 - 阶段7:把预处理记号转成记号(token),做完整的语法与语义分析,翻译成翻译单元(translation unit)。真正的"翻译"发生在这里——编译器在此生成汇编代码,并在此阶段报出绝大多数语法错误。
⚠️ 常见误解:阶段 5 常被说成"编译成汇编"、阶段 6 被说成"汇编成目标文件"。这其实把翻译阶段的编号和工具链的工序混为一谈了。标准里的阶段 5/6 讲的都是"字符编码转换"和"字面量拼接";“生成汇编"和"生成目标文件"在标准里是阶段 7 之后由具体工具实现的事情。
到了工具链层面,工序通常是这样的:
flowchart LR
A[".c 源文件"] -->|"gcc -E(阶段1~4)"| B[".i 文件"]
B -->|"gcc -S(阶段5~7,编译)"| C[".s 汇编文件"]
C -->|"gcc -c(as 汇编器)"| D[".o 目标文件"]3.5.5 链接 → 可执行文件
阶段8:链接器(linker)出场了。它把一个或多个目标文件(以及你用到的库文件)“拼装"在一起,解析所有外部引用(external reference)。
比如你调用 printf,链接器会去标准 C 库(libc)里找到 printf 的实现,把它的机器码合并进来。
最终生成可执行文件(executable)。在 Linux/macOS 上通常没有扩展名(默认叫 a.out),在 Windows 上是 .exe。
flowchart LR
A[main.o] -->|链接| D[可执行文件]
B[lib.o] -->|链接| D
C[libc.a] -->|链接| D💡 链接分为静态链接(static linking,把库代码直接拷贝进来)和动态链接(dynamic linking,程序运行时才加载库)。动态链接的可执行文件体积更小,而且多个程序可以共享同一份库代码,节省内存。
3.6 编译警告:gcc -Wall -Wextra -pedantic -Werror -Wconversion -Wshadow
编译器不只是"报错”(error),它还会给你"警告”(warning)——那些代码可能有问题但还能编译的地方。警告不是开玩笑,很多bug的根源就是忽略了警告。
GCC 常用警告选项
| 选项 | 含义 |
|---|---|
-Wall | 开启"所有常用警告"(enable all basic warnings) |
-Wextra | 开启额外的一些有用警告(extra warnings) |
-pedantic | 严格遵循 ISO C 标准,不放过任何非标准扩展 |
-Werror | 把警告当错误处理(warnings as errors) |
-Wconversion | 警惕隐式类型转换可能丢失数据 |
-Wshadow | 检测变量遮蔽(内层变量和外层变量同名) |
-Wformat | 检查 printf/scanf 的格式字符串是否匹配 |
-Wunused | 检测未使用的变量或函数 |
实战演示
| |
最佳实践:-Wall -Wextra -Werror
| |
💡 强烈建议把
-Werror带上!有了它,任何警告都会直接导致编译失败,逼着你把每个警告都修掉。听起来很烦人?等你因为忽略了一个-Wconversion警告而在生产环境里算错了用户的银行余额,就知道它的好了。很多大型项目的 CI/CD 流水线都是这么配置的——警告就是警告,不处理就别想代码入库。
3.7 编译选项实战:-std=c11 -O2 -g -D -I -L -l -fsanitize=address
光会写代码不会编译,就像光会做饭不会开火。gcc 的编译选项几十上百个,这里只讲最常用的那些。
-std=c11:指定 C 标准
| |
-O2:优化级别
| 选项 | 优化级别 | 适用场景 |
|---|---|---|
-O0 | 无优化(默认) | 调试时,保留完整调试信息 |
-O1 | 基本优化 | 平衡编译速度和性能 |
-O2 | 较高优化 | 正式发布,性能更好 |
-O3 | 最高优化 | 极致性能,可能增加二进制体积 |
-Ofast | 激进优化 | 忽略严格 IEEE 浮点,可能影响精度 |
| |
-g:生成调试信息
没有 -g,gdb(GNU debugger)没法调试,因为没有行号和变量名的对应关系。
| |
-D:定义宏
| |
| |
-I:指定头文件搜索路径
| |
-L 和 -l:指定库搜索路径和链接库
| |
-fsanitize=address:AddressSanitizer
这是个神器!如果你写程序遇到了神秘的内存问题(越界访问、使用未初始化内存、释放后使用等),用 AddressSanitizer(ASan)来检测。
| |
💡 ASan 是现代 C/C++ 开发的标准工具,Google 内部的 C++ 代码库用它做持续测试。VS Code 配置 clangd 时也可以开启 ASan。
综合示例
| |
这条命令的意思是:
- 用 C11 标准
- 开启 2 级优化
- 开启所有警告并把警告当错误
- 带调试信息
- 定义
DEBUG_MODE宏 - 在
./include找头文件,在./lib找库 - 开启地址消毒剂
- 链接数学库
- 输出可执行文件
myprogram
3.8 C 标识符规则:C99 起支持 Unicode 通用字符名,C23 起 XID_Start/XID_Continue 规则
标识符(identifier)就是变量名、函数名、类型名等东西的名字。你不能随便起名,得遵守规则。
基本命名规则(C89 以来就有的)
- 字符集:只能包含字母(A-Z, a-z)、数字(0-9)和下划线(
_) - 首字符:不能以数字开头
- 大小写敏感:
name和Name是两个不同的标识符 - 不能是关键字(keyword/reserved word),比如
if、while、int等
| |
C99 增强:Unicode 通用字符名
从 C99 开始,你可以用 Unicode 通用字符名(Universal Character Name)来命名标识符,这样就可以用中文、日文等语言命名变量了!语法是 \uXXXX(4位十六进制)或 \UXXXXXXXX(8位十六进制)。
⚠️ 但要注意:C99 并不是"随便什么 Unicode 字符都能用",而是有限制的——只有被纳入允许列表的 Unicode 字符才能作为标识符(具体规则涉及 Unicode Categories)。而且,用
\uXXXX这种转义形式写中文变量名……说实话,挺丑的,不如直接写 UTF-8 中文变量名来得优雅!
| |
💡 在实际编码中,直接写中文变量名往往比写
\uXXXX更方便——现代编译器(GCC、Clang、MSVC)都支持 UTF-8 源码,直接写int 中文变量 = 100;就行。但\u语法是给那些不支持 UTF-8 的老环境准备的。
C23 增强:XID_Start 和 XID_Continue 规则,以及 nullptr 关键字
C23 采用了 Unicode 的 XID_Start 和 XID_Continue 属性来确定哪些字符可以作为标识符的开头字符和后续字符。这是一个更标准、更国际化的规则。
C23 还引入了 nullptr 关键字(类似 C++),用于表示空指针常量——这是对长期以来只能用 NULL 宏的一大改进,妈妈再也不用担心你把 NULL 和 0 搞混了。
简单来说,只要一个 Unicode 字符被标记为"可以用作编程语言的标识符",C23 就支持。理论上,全世界各种语言的字符都可以作为标识符。
标识符的长度
C 标准不规定标识符的最大长度,但编译器通常有实现限制(GCC/Clang 至少支持 63 个字符外部链接,局部标识符几乎无限制)。sizeof 是运算符而非宏,和这些限制是两码事,日常编程基本不用操心。
3.9 C 语言保留标识符(5 条规则)
C 语言有一些标识符是保留的(reserved),你不应该使用它们,否则可能产生未定义行为(undefined behavior)。
保留标识符的 5 条规则
规则 1:以双下划线 __ 开头或单下划线加大写字母 _X 开头的标识符,全部保留
| |
规则 2:以单下划线 _ 开头的标识符,在全局命名空间中保留
| |
💡 简而言之:永远不要用下划线开头的标识符,除非你非常清楚自己在干什么。
规则 3:所有标准库头文件名是保留的
| |
规则 4:标准库中所有以 E 开头后跟大写字母或小写字母的宏名是保留的(用于错误码)
| |
规则 5:标准库标识符(含函数名)不应被重定义或重复声明
| |
一图总结
flowchart TD
A["保留标识符"] --> B["__xxx\n双下划线开头"]
A --> C["_Xxx\n下划线+大写字母"]
A --> D["_xxx\n全局作用域单下划线"]
A --> E["标准库头文件名"]
A --> F["E + 字母\n错误码宏"]
A --> G["标准库函数名"]💡 结论:不要用下划线开头的名字,不要定义和标准库同名的宏或函数。这是 C 语言的"交规",违反了可能不会立刻出事故,但迟早会翻车。
3.10 命名规范(snake_case / camelCase / PascalCase)
代码写得对不对是一回事,看起来舒不舒服是另一回事。好的命名规范让代码自解释(self-documenting),看名字就知道是干啥的。
三大命名风格
snake_case(蛇形命名法)
所有字母小写,单词之间用下划线分隔。
| |
💡 这是 C 语言社区最常用的风格,因为 C 语言标准库本身就是这么命名的(
snprintf、strlen、memcpy)。
camelCase(驼峰命名法)
第一个单词小写,后续单词首字母大写,看起来像驼峰。
| |
💡 C 语言里用 camelCase 的人相对较少,但如果你之前写 JavaScript/Java/Python,迁移过来可能更习惯这个风格。
PascalCase(帕斯卡命名法)
每个单词首字母都大写。
| |
💡 C 语言里 PascalCase 通常用于类型名(typedef、struct tag)。至于常量宏,绝大多数 C 项目用的是全大写的
MAX_BUFFER_SIZE风格,而不是 PascalCase——这一点和 C# / Java 的习惯不同。
一些常见约定
| |
项目风格统一最重要
命名风格没有绝对的对错,但一个项目里必须统一最关键。混用 snake_case 和 camelCase 会让代码看起来像"穿搭灾难"。
💡 如果你加入一个开源项目,第一件事就是看它的
CONTRIBUTING.md或者.clang-format/.editorconfig,了解项目的代码风格规范,然后老老实实遵守。
本章小结
本章我们一起参观了 C 程序的基本结构这座"样板间",主要知识点包括:
#include预处理指令:<>从系统标准目录找头文件,""先本地再系统。预处理器会在编译前把头文件内容原封不动地粘贴进来。main函数:程序入口点,标准签名是int main(void)。绝对不要写void main(),那是非标准的野路子写法。printf和转义序列:printf是格式化输出函数。\n(换行)、\t(制表符)、\r(回车)、\0(空字符)等转义序列用于表示"看不见的"字符。注释规范:单行注释
//(C99+)和多行注释/* */(C89 就支持)。注释解释"为什么",不要解释"是什么"。C 程序的 8 个翻译阶段:字符映射 → 删除反斜杠换行 → 切成预处理记号 → 预处理 → 转成执行字符集 → 拼接相邻字符串 → 语法语义分析并翻译 → 链接。注意三字符组在 C23 已被彻底删除。
编译警告:学会用
-Wall -Wextra -pedantic -Werror把所有警告都当错误处理。警告是朋友,不是噪音。常用编译选项:
-std=c11(C标准)、-O2(优化)、-g(调试信息)、-D(定义宏)、-fsanitize=address(内存问题检测)。标识符规则:C99 支持 Unicode 通用字符名(但有限制),C23 采用 XID_Start/XID_Continue 规则,可以轻松使用中文变量名。
nullptr(C23):C23 引入了nullptr关键字表示空指针,比传统的NULL宏更清晰、更类型安全。保留标识符 5 条规则:双下划线、下划线加大写、单下划线全局、标准库头文件名、以 E 开头的错误码宏,都是雷区,不要踩。
命名规范:snake_case(推荐)、camelCase、PascalCase。风格要统一,项目里不能混用。
🎉 恭喜你!C 语言的"毛坯房"你已经逛完了。接下来几章我们会深入探讨数据类型、运算符、控制语句等"装修工程",让这座房子真正变成你的家!
下一章预告:数据类型和变量——C 语言的"建筑材料"