字符集与标识符
C 程序由字符序列构成,编译器首先将源代码分解为词法单元(token)。标识符是程序员为变量、函数、类型等起的名字,必须遵循严格的命名规则。理解字符集和标识符规则,是避免编译错误的第一步。
基本源字符集
C99 §5.2.1 规定了基本源字符集,包含以下字符:
- 大小写英文字母:
A–Z、a–z - 十进制数字:
0–9 - 图形字符:
! " # % & ' ( ) * + , - . / : ; < = > ? [ \ ] ^ _ { | } ~ - 空白字符:空格、水平制表符、垂直制表符、换页符、换行符
C99 还支持通用字符名(Universal Character Names),允许在标识符中使用部分 Unicode 字符:\uXXXX 或 \UXXXXXXXX 形式。但实际编译器支持程度不一,为保证可移植性,建议只使用基本字符集的字母和数字。
标识符命名规则
标识符由字母、数字和下划线组成,且不能以数字开头。C99 §6.4.2 规定:
int valid_name; /* 合法 */
int _underscore; /* 合法:以下划线开头 */
int name123; /* 合法:包含数字 */
int Name123; /* 合法:C 区分大小写 */
int 123name; /* 非法:以数字开头 */
int name$; /* 非法:$ 不是标准字符 */
int my-name; /* 非法:- 被解释为减号 */
大小写敏感:count、Count、COUNT 是三个不同的标识符。
长度限制:C99 要求编译器至少能区分前 63 个字符(外部标识符 31 个),但大多数现代编译器支持更长的标识符。
标识符名字空间
C 语言定义了 4 个独立的名字空间(C99 §6.2.3),同名标识符如果属于不同名字空间,不会冲突:
标签名字空间:goto 语句使用的标签
goto end;
/* ... */
end: printf("Done\n"); /* 标签 end */
标记名字空间:struct、union、enum 的标记名
struct end { int x; }; /* 标记 end,与上面的标签不冲突 */
成员名字空间:每个 struct 或 union 的成员各自拥有独立空间
struct A { int end; }; /* 成员 end */
struct B { int end; }; /* 另一个结构体的成员 end,不冲突 */
普通标识符名字空间:变量、函数、typedef 名、枚举常量等
int end; /* 变量 end,与标签、标记、成员都不冲突 */
实际应用:struct stat { int stat; }; struct stat stat; 是完全合法的——标记 stat、成员 stat、变量 stat 分属三个名字空间。
命名约定与最佳实践
虽然编译器只强制执行语法规则,但良好的命名约定能大幅提升代码可读性:
常见风格:
| 风格 | 示例 | 使用场景 |
|---|---|---|
| snake_case | student_name | 变量、函数(Unix/Linux 传统) |
| camelCase | studentName | 变量、函数(Windows 传统) |
| PascalCase | StudentName | 结构体名、typedef 名 |
| UPPER_CASE | MAX_SIZE | 宏、常量 |
避免以下划线开头的名字:
- 单下划线 + 大写字母开头(如
_Reserved):保留给实现(编译器/标准库) - 双下划线开头(如
__internal):保留给实现 - 以下划线结尾的名字:可能冲突
int _value; /* 合法但不推荐 */
int __value; /* 保留给编译器,用户代码应避免 */
选择有意义的名称:
/* 差 */
int a, b, c;
/* 好 */
int width, height, area;
避免过短或过长的名字:
int n; /* 循环计数器可以短 */
int numberOfStudentsInThisClass; /* 太长,考虑 students_count */
三字符组(Trigraphs)
C99 支持三字符组,用三个字符序列表示某些键盘上可能缺失的字符:
| 三字符组 | 等价字符 |
|---|---|
??= | # |
??( | [ |
??) | ] |
??< | { |
??> | } |
??/ | \ |
??=include <stdio.h> /* 等价于 #include <stdio.h> */
三字符组在 C99 中已标记为废弃,现代键盘都能直接输入这些字符,实际代码中几乎不会用到。某些编译器默认不启用三字符组处理。