上回我们成功运行了人生中第一个 C 程序,屏幕上跳出 Hello, World! 的那一刻,你已经和计算机进行了一次正式的对话。不过,你大概也注意到了——我们只是把代码“照抄”进去,并不清楚每一行到底在做什么。
今天,我们就拿这只“麻雀”开刀,把 hello.c 拆开揉碎,看清楚它的骨骼和内脏。当你理解了这个最简单的程序,往后所有复杂的程序,不过是这些骨骼上长出的血肉。
一、一个最简 C 程序的五要素
先回顾一下我们写的 hello.c:
#include <stdio.h>
int main(void) {
printf("Hello, World!\n");
return 0;
}
别看只有寥寥几行,它已经包含了 C 程序的五个核心要素:
- 预处理指令:
#include <stdio.h> - 主函数声明:
int main(void) - 函数体:
{ ... } - 执行语句:
printf("Hello, World!\n"); - 返回值:
return 0;
缺了任何一个,这个程序要么无法编译,要么虽然能跑,但不符合 C 语言标准。我们来逐一解剖。
二、预处理指令:#include <stdio.h>
以 # 开头的行,叫做预处理指令。它并不是 C 语言本身的语句,而是在编译之前,由预处理器进行处理的命令。
#include <stdio.h> 这条指令的意思是:“在正式开始编译之前,请把 stdio.h 这个文件的内容原封不动地复制粘贴到这里。”
那 stdio.h 又是什么?它是 标准输入输出头文件(Standard Input Output Header)。里面声明了 printf、scanf 等我们用来在屏幕上打印、从键盘读取数据的函数。没有它,编译器就不认识 printf 是谁,会报出“未声明的标识符”之类的错误。
你可以简单理解为:#include 就是“导入工具箱”。你要用什么工具,就得先导入相应的工具箱。stdio.h 是最常用的一个,后面我们还会遇到 stdlib.h、string.h 等等。
小提示:尖括号
< >表示“去系统标准路径下查找这个头文件”;如果以后你看到双引号" "包含的头文件,比如#include "myheader.h",那是告诉编译器“先在当前目录下找,找不到再去系统路径找”。
三、主函数:int main(void)
每个 C 程序都有且仅有一个 main 函数。程序启动时,操作系统会首先调用它,从它的第一条语句开始执行。可以说,main 是程序的“入口”。
那一长串 int main(void) 到底是什么意思呢?拆开看:
int:这是函数的返回类型,意思是main函数执行完毕后,会返回一个整数给操作系统。通常返回0表示“正常结束”,返回其他数字表示“异常结束”。main:函数的名字。这个名字是 C 语言规定的,不能改。(void):括号里是参数列表,void在这里表示这个函数不接受任何参数。
在初学阶段,你可能会看到 int main() 的写法(括号里什么都不写),也可以看到 int main(int argc, char *argv[]) 的写法(用于接收命令行参数)。目前我们先用 int main(void),它最清晰明确。
有的老式教材会写 void main(),但那是非标准的写法,现代 C 语言标准要求 main 函数必须返回 int。所以请一律用 int main(void)。
四、函数体与花括号
main 后面那一对花括号 { },就是函数体。所有在函数运行时要执行的语句,都必须放在这对花括号里面。
C 语言是大小写敏感的,Main 和 main 是两回事。花括号也是成对出现的,初学者最容易犯的错误之一就是“少写一个花括号导致编译错误”。一个好习惯是:写左花括号时立刻敲出右花括号,再在中间填内容。
int main(void) { // 左花括号
// 在这里写语句
return 0;
} // 右花括号
五、执行语句:printf("Hello, World!\n");
这是我们程序唯一真正“干活”的语句。它调用了 C 标准库里的 printf 函数,在屏幕上输出一段文字。
printf:函数名,代表“按格式打印”(print formatted)。("Hello, World!\n"):括号里是传给函数的参数,这里是一个字符串,用双引号包起来。;:分号是 C 语句的结束符。每一条完整的语句都必须用分号结尾。忘记分号是初学阶段最常见的错误。
字符串里的 \n 是一个转义字符,代表“换行”。试试去掉它,再编译运行,你会发现光标停在了输出内容的末尾,而不是另起一行。如果你想让程序在输出后换到新行,这个符号就必不可少。
常用的转义字符还有:
\t:制表符(Tab)\\:一个反斜杠本身\":一个双引号(因为双引号用于标识字符串边界,内部要用转义)
六、返回值:return 0;
return 语句做了两件事:
- 立即结束当前函数的执行。
- 将
return后面的值(这里是0)作为函数的“产物”交还给调用者。对main函数而言,调用者是操作系统。
返回值 0 是一个约定俗成的“成功信号”。当你用命令行运行程序后,操作系统可以获取这个值来判断程序是否正常结束。在 Linux/macOS 终端里,可以用 echo $? 查看上一个程序的返回值。试试把 return 0 改成 return 7,编译运行后再执行 echo $?,你就能看到 7。
七、注释:给代码写给人看的说明
我们还没有在 hello.c 里写注释,但它非常重要,必须提前认识。
C 语言有两种注释方式:
单行注释(C99 加入):
// 这是一个单行注释,从双斜杠开始到这行结束
多行注释(传统方式):
/*
这是一个多行注释
可以跨越多行
编译器会完全忽略它们
*/
注释的作用是给阅读代码的人(包括未来的你自己)解释某段代码的意图。好的注释说“为什么”,而不是“做什么”,因为代码本身已经说了“做什么”。比如:
// 糟糕的注释
int x = 10; // 把 10 赋值给 x
// 好的注释
int max_retries = 10; // 最大重试次数,防止无限循环
从现在开始,每写一个程序都试着给关键处加上注释。这是一个受益终生的习惯。
八、附录:编译器在背后干了什么?(感性了解)
你输入 gcc hello.c -o hello 后,看似一键完成,实际上编译器暗地里经历了四个阶段。这四阶段不需要你现在就精通,但有一个感性的认知,会帮你理解很多日后遇到的“奇怪错误”。
假设我们有一个最简单的源文件 hello.c。
第一阶段:预处理(Preprocessing)
- 处理所有
#开头的指令:#include展开头文件,#define替换宏。 - 去掉注释。
- 输出一个纯净的
.i文件(通常不保留,但可以用gcc -E查看)。
第二阶段:编译(Compilation)
- 将预处理后的
.i文件翻译成汇编语言,生成.s文件。 - 这是编译器最核心的环节,进行词法分析、语法分析、语义分析、优化。
第三阶段:汇编(Assembly)
- 将汇编代码转换成机器码,生成目标文件
.o(或.obj)。 - 此时文件里已经是二进制的 CPU 指令,但还不能直接运行,因为调用了像
printf这样的外部函数还没“连”上。
第四阶段:链接(Linking)
- 将一个或多个
.o文件与库文件(如包含printf实现的标准 C 库)合并。 - 解决所有函数和变量的引用关系,最终生成可执行文件
hello(或hello.exe)。
如果你好奇,可以用 gcc 的分步参数亲眼看看:
# 只预处理,输出到 stdout
gcc -E hello.c
# 只编译到汇编,生成 hello.s
gcc -S hello.c
# 只编译和汇编,不链接,生成 hello.o
gcc -c hello.c
# 最后链接(gcc hello.o -o hello)
常见错误属于哪个阶段?比如少写分号,是第二阶段“编译”报语法错误;拼错 printf,编译阶段会报“未声明”,但链接阶段才会发现“找不到实现”,报 undefined reference。理解这些阶段,你就不会对着错误信息一脸茫然。
九、小结
今天我们拆解了 hello.c 的五个核心要素,认识了注释,还窥探了编译器背后的工作流程。现在你再看到 #include、int main(void)、return 0,应该不只是“照着敲”了,而是知道每一个存在的理由。
从下一篇开始,我们就要正式和数据打交道——学习变量和数据类型。你会看到,C 语言的世界里,一切都是围绕数据展开的。
课后小练习
- 给
hello.c加上注释:在#include上方写一段多行注释,说明这个程序的作用和作者(你)。 - 修改程序,在一行里用多个
printf打印多行内容(比如一首诗),注意换行。 - 用
gcc -E预处理一下你的hello.c,看看#include <stdio.h>到底展开了多少内容(慎用,可能会输出好几千行,重点感受一下规模即可)。 - (思考题)如果把
main改成Main,编译会报什么错误?如果你改了,再改回来。
我们下期见!
2641

被折叠的 条评论
为什么被折叠?



