2.2 词法规则
词法规则就像英语中的拼写规则,决定你在源代码中如何形成单独的字符片段,也就是标记(token)。
一个ANSI C程序由声明和函数组成。函数定义了需要执行的工作,而声明则描述了函数和(或)函数将要操作的数据类型(有时候是数据本身)。注释可以散布于源文件的各个地方。
2.2.1 字符
标准并没有规定C环境必须使用哪种特定的字符集,但它规定字符集必须包括英语所有的大写和小写字母、
数字0~9,以及下面这些符号:
! " # % ' ( ) * + , - . / :
; < > = ? [ ] ^ _ { } | ~
换行符用于标志源代码每一行的结束,当正在执行的程序的字符输入就绪时,它也用于标志每个输入行的
末尾。如果运行时环境需要,换行符也可以是一串字符,但它们会被当作单个字符处理。字符集还必须包括
空格、水平制表符、垂直制表符和格式反馈字符。这些字符加上换行符,通常被称作空白字符,因为当它们
被打印出来时,在页面上出现的是空白而不是各种记号。
标准还定义了几个三字母词(trigrph),三字母词就是几个字符的序列,合起来表示另一个字符,三字母词
使C环境可以在某些缺少一些必需字符的字符集上实现。下面是一些三字母词所代表的字符。
??( [ ??< { ??= #
??) ] ??> } ??/
??! | ??'' ^ ??- ~
两个问号开头再尾随一个字符的形成一般不会出现在其他表达式形成中,所以把三字母词用这种形式来表示,这样就不致引起误解。
警告:
之所以选择??这个序列作为每个三字母词的开始,是因为它们出现的形式很不自然,但它们仍然隐藏着危险。
如下所示:
printf("Delete file (are you really sure??): ");
结果输出中将产生]字符,这无疑会令你大吃一惊。
#include
#include
int main( void ){
printf( "print trigrph:n" );
printf( "[ = ??(, { = ??<, # = ??=n"
"] = ??), } = ??>, \ = ??/??/n"
"| = ??!, ^ = ??', ~ = ??-n" );
printf( "print character set that be stipulated by C standard:n" );
printf( "! " # %% ' ( ) * + , - . / :n"
"; < > = ? [ ] \ ^ _ { } | ~n" );
printf( "trigrph is used accidentally in an amazed way:n" );
printf( "Delete file (are you really sure??): n" );
printf( "print original look of string we hope by using escape sequence: n" );
printf( "Delete file (are you really sure??): " );
return EXIT_SUCCESS;
}
当你编写某些C源代码时,你在一些上下文环境里使用某个特定的字符,却可能无法如愿,因为该字符在这个环境里有特别的意义。双引号"用于界定字符串常量,在K&R C定义了几个转移序列(escape sequence)或字符
转义(character escape),用于克服在字符串常量内部包含一个双引号。ANSI C在它的基础上又增加了几个
转义序列。转移序列由一个反斜杠加上一个或多个其他字符组成。下面列出的每个转义序列代表反斜杠后面的
那个字符,并未给这个字符增加特别的意义。
? 在书写连续多个问号时使用,防止它们被解释为三字母词。
" 用于表示一个字符串常量内部的双引号。
' 用于表示字符常量'。
\ 用于表示一个反斜杠,防止它被解释为一个转义序列符。
有许多字符并不在源代码中出现,但它们在格式化程序输出或操纵终端显示屏时非常有用。C语言提供了一些这
方面的转义符。
ANSI C:
a 警告字符。它将奏响铃声或产生其他一些可听见或可看见的信号。
b 退格键
f 进纸字符
n 换行符
r 回车符
t 水平制表符
v 垂直制表符
ddd ddd表示1~3个八进制数字。这个转义符表示的字符就是给定的八进制数值所代表的字符
xddd 与上例类似,只是八进制数换成了十六进制数。
注意,任何十六进制数都有可能包含在xddd序列中,但如果结果值的大小超出了表示字符的范围,其结果就是未定义的。
#include
#include
int main( void ){
printf( "aaacharacter set test begins: n" );
printf( "here, use \b will move mouse ahead and delete previous character, maybe be different in your compiler.n" );
printf( "This word is stu...dentbbbb. Can you guess which word it is:?n" );
printf( "There are 3 options:t"dent"t"dy"t"ff", please select one.n" );
printf( "\ is used to concatenate to string.n" );
printf( "Do you want to know my gender which is 'm' or 'w'?
you can try to guess.nIf you guess exactly, I will give you a surprise.n" );
printf( "\r will make your mouse move the beginning of string in this line and override character latter.n" );
printf( "aaacharacter test ends.nhave fun for you.rstudent," );
printf( "n0777 largest value is %d, 0xfff largest value is %d.n", 0777, 0xfff );
printf( "\ddd or \xddd is more than largest value of character set, the result of \ddd or \xddd is undefinited.n" );
printf( "\101 = %c, \101 = %c, \xa0 = %d, \xa0 = %c.n", '101', '101', 'xa0', 'xa0' );
return EXIT_SUCCESS;
}
2.2.2 注释
C语言的注释以结束,中间可以包含除*/之外的任何字符。在源代码中,一个注释可能跨越多行,
但它不能嵌套于另一个注释中。注意,如果出现在字符串字面值内部,就不再起注释定界符的作用。所有的注释
都会被预处理器拿掉,取而代之的是一个空格。因此,注释可以出现在任何空格可以出现的地方。
警告:
下面的代码只有第1个变量才被初始化。
x1=0;
#include
#include
int
main( void ){
printf( "I am a student.n");
int x1=0;
return EXIT_SUCCESS;
}
2.2.3 自由形式的源代码
C唯一的规则就是相邻的标记之间必须出现一至多个空白字符(或注释),不然它们可能被解释为单个标记。
合法的:
y=x+1;
y = x + 1;
y = x
+
1;
合法的:
int
x;
int x;
intx;
不合法的:
intx;
#include
#include
int main( void ){
int x, y;
x = 1;
y = x + 1;
y=x+1;
y = x + 1;
y = x
+
1;
int
z;
int z2;
printf( "x = %d, y = %dn", x, y );
printf( " can appears anywhere space can appear.n" );
intz3;
return EXIT_SUCCESS;
}
2.2.4 标识符
标识符(identifier)由大小写字母、数字和下划线组成,但不能以数字开头。C是一种区分大小写的语言。标识符的长度没有限制,但标准规定允许编译器忽略第31个字符以后的字符。标准同时允许编译器对用于表示外部名字(也就是由链接器操纵的名字)的标识符进行限制,只识别前6位不区分大小写的字符。
下列C语言关键字是被保留的,它们不能作为标识符使用:
auto do goto signed unsigned
break double if sizeof void
case else int static volatile
char enum long struct while
const extern register switch
continue float return typedef
default for short union
2.2.5 程序的形式
一个C程序可能保存于一个或多个源文件中。虽然一个源文件可以包含一个以上的函数,但每个函数都必须完整地出现于同一个源文件中。标准并没有明确规定,但一个C程序的源文件应该包含一组相关的函数,这才是较为合理的组织形式。这种做法还有一个额外的优点,就是它使实现抽象数据类型成为可能。
#ifndef HEADER_H
#define HEADER_H
#include
void print_star( void );
#endif
#include "header.h"
void print_star( void ){
printf( "*****n" );
}
#include
#include "header.h"
int main(int argc, char** argv) {
print_star();
return 0;
}
预处理指令是个例外,它是以行定位的。
从技术上说,使用#include指令,一个函数可以分在两个源文件中定义,只要把其中一个包含到另一个就行,但这个用法可不是#include指令的合理用法。
#include
void print( void ){
printf( "Hello world!n");
}
#include
void print( void ){
printf( "Hello, everyone!n" );
}
#include
#include
#include "print_version_1.cpp"
int main() {
print();
return EXIT_SUCCESS;
}
#include
#include
#include "print_version_2.cpp"
int main() {
print();
return EXIT_SUCCESS;
}



