C 语言要学的语法非常少:大约 32 个关键字,加上几条标点规则。你将来写的几乎每个程序都有相同的外层结构,所以值得认真地把这个结构拆开看一遍。
最小的完整程序
下面这段代码可以编译、可以运行,但什么也不做:
没有头文件包含,没有输出,也没有样板式的类或模块。main 是程序的入口:操作系统启动你的程序时,会调用 main。main 返回时程序结束,它返回的值就是退出状态。
要加上输出,就需要一个头文件包含:
预处理指令
以 # 开头的行在正式编译之前,由预处理器处理。它们不是 C 语句,不加分号。
#include <stdio.h> /* standard library header - angle brackets */
#include "myutils.h" /* your own header - quotes, searched locally first */
#define MAX 100 /* a macro - MAX is replaced by 100 everywhere below */
#include 就是原样粘贴文本:把指定文件的全部内容插入到这个位置。stdio.h 里有 printf 的声明,编译器正是靠它才知道 printf 接受哪些参数。去掉这个包含,你就会得到 implicit declaration of function 'printf'(函数 printf 隐式声明)的提示。
尖括号和双引号的区别在于编译器去哪里查找:<...> 表示系统头文件目录,"..." 表示先在当前文件旁边查找。
main 函数
int main(void) {
return 0;
}
四个部分,各有含义:
int:main 的返回类型。这个值作为退出状态交给操作系统,0 表示成功。main:运行时调用的保留名称。每个托管环境下的 C 程序都恰好有一个。(void):参数列表。这里的void表示“不接受参数”。改写成main()也合法,但约束更弱:C 语言中的空括号表示“参数未指定”,这会关闭参数检查。{ ... }:函数体。
另一种标准形式接受命令行参数:
int main(int argc, char *argv[]) { ... }
在需要命令行参数之前,用 int main(void) 就好。
语句和分号
语句是一条单独的指令,以分号结尾。
四条语句,四个分号。分号是结束符,而不是分隔符:代码块中的最后一条语句也需要它。
不加分号的地方:
#include <stdio.h> /* preprocessor line - no semicolon */
int add(int a, int b) { /* function definition header - no semicolon */
return a + b;
} /* closing brace of a function - no semicolon */
if (x > 0) {
...
} /* closing brace of a control block - no semicolon */
每个人都踩过的坑,是在本该写 if 主体的地方放了一个分号:
if (score > 90); /* BUG: this ; is the entire if body */
printf("Excellent!\n"); /* always runs, whatever score is */
这是完全合法的 C 代码。if 控制的是一条空语句,而 printf 只是按顺序执行的下一行。-Wall 会对此发出警告,但编译器不会阻止你。
代码块和花括号
花括号把多条语句组合成一个代码块,在任何需要一条语句的地方,代码块都算作一条语句。
当主体只有一条语句时,花括号可以省略,但许多著名的 bug 正是这样产生的:以后再加第二行,它会悄悄地脱离 if。请始终使用花括号。
代码块还会创建作用域:在 { ... } 内声明的变量,到右花括号处就不存在了。
大小写敏感和空白字符
C 语言在任何地方都区分大小写。total、Total 和 TOTAL 是三个不同的名字。所有关键字都是小写,所以 Int、Return 和 Printf 都会报错;而 Printf 的错误信息还很让人困惑,因为编译器会以为你指的是某个它从没见过的函数。
相比之下,空白字符几乎完全自由。编译器把任意连续的空格、制表符和换行都当作一个分隔符。下面的写法是合法的:
int main ( void )
{
printf
( "Hello\n" ) ;
return 0 ;
}
这样写也合法:
int main(void){printf("Hello\n");return 0;}
两者编译出的结果完全相同。既然编译器不在乎,格式就完全是写给人看的;而约定俗成的排版(每行一条语句,每层嵌套缩进四个空格或一个制表符,左花括号放在同一行)值得遵守,因为你读到的每个 C 代码库都会用与之相近的风格。
空白字符确实有影响的地方有两处:不能把关键字或标识符拆开(in t 不是 int),也不能在普通字符串字面量中间换行。
标识符:命名规则
标识符是你自己取的任何名字:变量、函数、类型。规则如下:
- 由字母、数字和下划线组成。
- 不能以数字开头。
total2可以,2total不行。 - 不能是关键字:不能有名为
int、for或return的变量。 - 区分大小写。
- 以下划线开头的名字,以及包含两个连续下划线的名字,都保留给编译器和标准库实现使用,不要自己定义。
int score; /* good */
int total_items; /* good - snake_case is the C convention */
int _hidden; /* legal but reserved - avoid */
int 2fast; /* error */
int float; /* error - keyword */
一个逐行注释的完整程序
把上面的内容放在一起:
这里的先后顺序很重要:with_tax 在 main 使用它之前就定义好了。C 语言从上到下读取文件,所以函数必须先声明再调用。要么把它定义在前面,要么在文件顶部写一个函数原型,然后在后面定义。
常见问题
C 程序的基本结构是什么?
首先是预处理指令(#include <stdio.h>),然后是函数定义,其中必须有一个是 main。在函数内部,语句以分号结尾,代码块用花括号包起来。最小的程序就是 #include <stdio.h> 加上 int main(void) { return 0; }。
为什么要写 int main(void),而不是只写 main()?
int 是 main 的返回类型,也就是程序交还给操作系统的退出状态。(void) 明确表示 main 不接受任何参数。写成空括号的 main() 是一种旧风格,它没有告诉编译器任何关于参数的信息,所以编译器做的检查更少。
所有 C 语句都需要分号吗?
每条语句都需要,但不是每一行都需要。预处理指令(#include、#define)、函数定义,以及代码块或 if/for/while 的右花括号后面都不加分号。if (x) 后面多出的一个分号是合法的 C 代码,却会悄悄让 if 的主体无条件执行。
C 语言区分大小写吗?
区分,而且是完全区分。count、Count 和 COUNT 是三个不同的标识符,Int 也不是关键字 int。C 语言的关键字全部是小写,所以写成 Return 或 Printf 这种带大写字母的形式会报错。