Menu

C 语言语法:C 程序的基本结构详解

每个 C 程序都有相同的骨架:头文件包含、main 函数、以分号结尾的语句,以及用花括号包起来的代码块。本文讲解每个部分的作用,以及编译器真正会检查的规则。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

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 这种带大写字母的形式会报错。

Coddy 编程语言插图

和 Coddy 一起学编程

开始