Declarations
一个 声明 是C语言的一种结构,它向程序中引入一个或多个 标识符 并指定其含义和属性。
声明可以出现在任何作用域中。每个声明以分号结束(如同 语句 ),并包含 两个 (C23前) 三个 (C23起) 独立部分:
说明符与限定符
声明符与初始化器
(可选)
;
|
(1) | ||||||||
属性说明序列
说明符与限定符
声明符与初始化器
;
|
(2) | (自 C23 起) | |||||||
属性说明序列
;
|
(3) | (自 C23 起) | |||||||
其中
| specifiers-and-qualifiers | - |
以任意顺序排列的、由空格分隔的列表,包含:
|
| declarators-and-initializers | - | 以逗号分隔的 declarators 列表(每个声明符提供额外的类型信息和/或要声明的标识符)。声明符可伴随 初始化器 。 enum 、 struct 和 union 声明可省略 declarators ,此时它们仅引入枚举常量和/或标签。 |
| attr-spec-seq | - | (C23) 可选的 属性 列表,应用于声明的实体,或在单独出现时构成属性声明。 |
例如,
int a, *b=NULL; // "int" 是类型说明符, // "a" 是声明符 // "*b" 是声明符,NULL 是其初始化器 const int *f(void); // "int" 是类型说明符 // "const" 是类型限定符 // "*f(void)" 是声明符 enum COLOR {RED, GREEN, BLUE} c; // "enum COLOR {RED, GREEN, BLUE}" 是类型说明符 // "c" 是声明符
每个声明中引入的标识符类型由 类型说明符 指定的类型与其 声明符 应用的类型修饰共同决定。 若使用 auto 说明符,变量的类型也可被推导得出。 (since C23)
属性 (C23起) 可以出现在 说明符与限定符 中,此时它们应用于由前述说明符确定的类型。
目录 |
声明符
每个声明符是以下之一:
| 标识符 属性说明符序列 (可选) | (1) | ||||||||
(
声明符
)
|
(2) | ||||||||
*
属性说明符序列
(可选)
限定符
(可选)
声明符
|
(3) | ||||||||
无指针声明符
[
static
(可选)
限定符
(可选)
表达式
]
无指针声明符
|
(4) | ||||||||
无指针声明符
(
参数或标识符列表
)
|
(5) | ||||||||
这种语法背后的逻辑是:当声明符所声明的标识符出现在与声明符形式相同的表达式中时,该标识符将具有类型说明符序列所指定的类型。
struct C { int member; // "int" 是类型说明符 // "member" 是声明符 } obj, *pObj = &obj; // "struct C { int member; }" 是类型说明符 // 声明符 "obj" 定义了一个 struct C 类型的对象 // 声明符 "*pObj" 声明了一个指向 C 的指针 // 初始化器 "= &obj" 为该指针提供了初始值 int a = 1, *p = NULL, f(void), (*pf)(double); // 类型说明符是 "int" // 声明符 "a" 定义了一个 int 类型的对象 // 初始化器 "=1" 提供了其初始值 // 声明符 "*p" 定义了一个指向 int 的指针类型对象 // 初始化器 "=NULL" 提供了其初始值 // 声明符 "f(void)" 声明了一个接受 void 并返回 int 的函数 // 声明符 "(*pf)(double)" 定义了一个指向 // 接受 double 并返回 int 的函数指针类型对象 int (*(*foo)(double))[3] = NULL; // 类型说明符是 int // 1. 声明符 "(*(*foo)(double))[3]" 是一个数组声明符: // 声明的类型是“/嵌套声明符/ 3个int的数组” // 2. 嵌套声明符是 "*(*foo)(double)",这是一个指针声明符 // 声明的类型是“/嵌套声明符/ 指向3个int数组的指针” // 3. 嵌套声明符是 "(*foo)(double)",这是一个函数声明符 // 声明的类型是“/嵌套声明符/ 接受double并返回 // 指向3个int数组指针的函数” // 4. 嵌套声明符是 "(*foo)",这是一个(按函数声明符语法要求 // 加括号的)指针声明符 // 声明的类型是“/嵌套声明符/ 指向接受double并返回 // 指向3个int数组指针的函数的指针” // 5. 嵌套声明符是 "foo",这是一个标识符 // 该声明引入标识符 "foo" 来引用一个 // “指向接受double并返回指向3个int数组指针的函数的指针”类型的对象 // 初始化器 "= NULL" 提供了该指针的初始值 // 如果 "foo" 以声明符的形式在表达式中使用,其类型将是 // int int x = (*(*foo)(1.2))[0];
每个不属于其他声明符的声明符的末尾都是一个 序列点 。
在所有情况下, attr-spec-seq 是一个可选的 属性 序列 (since C23) 。当紧跟在标识符后出现时,它应用于正在声明的对象或函数。
定义
一个 定义 是提供其所声明标识符全部信息的声明。
对于函数而言,包含函数体的声明即为 函数定义 :
int foo(double); // 声明 int foo(double x) { return x; } // 定义
对于对象而言,分配存储空间的声明( 自动或静态 ,但不包括extern)属于定义,而不分配存储空间的声明( 外部声明 )则不属于定义。
extern int n; // 声明 int n = 10; // 定义
struct X; // 声明 struct X { int n; }; // 定义
重声明
声明不能引入一个标识符,如果在同一 作用域 中较早出现了同一标识符的其他声明,除非
- 具有 链接 (外部或内部)的对象声明可以重复:
extern int x; int x = 10; // 正确 extern int x; // 正确 static int n; static int n = 10; // 正确 static int n; // 正确
- 非变长数组的 typedef 只要定义的是相同类型就可以重复声明:
typedef int int_t; typedef int int_t; // 正确
struct X; struct X { int n; }; struct X;
这些规则简化了头文件的使用。
注释
|
在C89中,任何 复合语句 (块作用域)内的声明必须出现在块的起始位置,位于所有 语句 之前。 此外在C89中,返回 int 类型的函数可通过 函数调用运算符 隐式声明,且在使用旧式 函数定义 时不需要声明 int 类型的函数参数。 |
(C99前) |
禁止使用空的声明符;简单声明必须至少包含一个声明符,或至少声明一个结构体/联合体/枚举标签,或至少引入一个枚举常量。
|
若声明符的任何部分是 可变长度数组 (VLA)声明符,则整个声明符的类型被称为“可变修改类型”。从可变修改类型派生的类型同样属于可变修改(VM)类型。 任何可变修改类型的声明仅可出现在 块作用域 或函数原型作用域中,且不能作为结构体或联合体的成员。虽然可变长度数组仅可具有自动或动态 存储期 ,但指向可变长度数组的指针等可变修改类型可以是静态类型。可变修改类型在使用上还存在其他限制,参见 goto 、 switch 和 longjmp 。 |
(C99起) |
|
静态断言 从C语法角度被视为声明(因此可以出现在任何声明出现的位置),但它们不引入任何标识符且不遵循声明语法。 |
(C11起) |
|
属性
声明同样被视为声明(因此它们可以出现在任何声明可以出现的位置),但它们不引入任何标识符。单个不带
attr-spec-seq
的
|
(since C23) |
参考文献
- C23 标准 (ISO/IEC 9899:2024):
-
- 6.7 声明 (p: 待定)
- C17 标准 (ISO/IEC 9899:2018):
-
- 6.7 声明 (p: 78-105)
- C11 标准 (ISO/IEC 9899:2011):
-
- 6.7 声明 (p: 108-145)
- C99标准(ISO/IEC 9899:1999):
-
- 6.7 声明(第97-130页)
- C89/C90 标准 (ISO/IEC 9899:1990):
-
- 3.5 声明
参见
|
C++ 文档
关于
声明
|