Namespaces
Variants

Declarations

From cppreference.net

一个 声明 是C语言的一种结构,它向程序中引入一个或多个 标识符 并指定其含义和属性。

声明可以出现在任何作用域中。每个声明以分号结束(如同 语句 ),并包含 两个 (C23前) 三个 (C23起) 独立部分:

说明符与限定符 声明符与初始化器  (可选) ; (1)
属性说明序列 说明符与限定符 声明符与初始化器 ; (2) (自 C23 起)
属性说明序列 ; (3) (自 C23 起)

其中

specifiers-and-qualifiers - 以任意顺序排列的、由空格分隔的列表,包含:
  • 类型说明符:


declarators-and-initializers - 以逗号分隔的 declarators 列表(每个声明符提供额外的类型信息和/或要声明的标识符)。声明符可伴随 初始化器 enum struct union 声明可省略 declarators ,此时它们仅引入枚举常量和/或标签。
attr-spec-seq - (C23) 可选的 属性 列表,应用于声明的实体,或在单独出现时构成属性声明。
1,2) 简单声明。引入一个或多个标识符,这些标识符表示对象、函数、结构体/联合体/枚举标签、类型定义或枚举常量。
3) 属性声明。不声明任何标识符,且若标准未指定其含义,则具有实现定义的含义。

例如,

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)
1) 该声明符引入的标识符。
2) 任何声明符都可以用括号括起来;这对于引入指向数组的指针和指向函数的指针是必需的。
3) 指针声明符 :声明 S * cvr D ; 将 D 声明为指向由 S 确定的类型的 cvr 限定指针。
4) 数组声明符 :声明 S D [ N ] D 声明为由 S 确定的类型、包含 N 个对象的数组。 无指针声明符 是除未加括号的指针声明符之外的任何其他声明符。
5) 函数声明符 :声明 S D ( params ) D 声明为接受参数 params 并返回 S 的函数。 noptr-declarator 是指除未加括号的指针声明符之外的任何其他声明符。

这种语法背后的逻辑是:当声明符所声明的标识符出现在与声明符形式相同的表达式中时,该标识符将具有类型说明符序列所指定的类型。

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) 。当紧跟在标识符后出现时,它应用于正在声明的对象或函数。

定义

一个 定义 是提供其所声明标识符全部信息的声明。

每个 enum typedef 的声明都是一个定义。

对于函数而言,包含函数体的声明即为 函数定义

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++ 文档 关于 声明