命名空间
变体
操作

声明

来自 cppreference.cn
< c‎ | 语言

声明 是 C 语言构造,它将一个或多个 标识符 引入程序,并指定它们的含义和属性。

声明可以出现在任何作用域中。每个声明都以分号结尾(就像 语句 一样),并且由 两个(直到 C23)三个(自 C23 起) 不同的部分组成

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

其中

说明符和限定符 - 空格分隔的列表,顺序任意,包括:
  • 类型说明符


声明符和初始化器 - 逗号分隔的 声明符 列表(每个声明符提供附加的类型信息和/或要声明的标识符)。声明符可以伴随 初始化器enumstructunion 声明可以省略 声明符,在这种情况下,它们仅引入枚举常量和/或标签。
属性说明序列 - (C23)属性的可选列表,应用于声明的实体,或者如果单独出现,则形成属性声明。
1,2) 简单声明。引入一个或多个表示对象、函数、结构体/联合体/枚举标签、typedef 或枚举常量的标识符。
3) 属性声明。不声明任何标识符,并且如果标准未指定含义,则具有实现定义的含义。

例如,

int a, *b=NULL; // "int" is the type specifier,
                // "a" is a declarator
                // "*b" is a declarator and NULL is its initializer
const int *f(void); // "int" is the type specifier
                    // "const" is the type qualifier
                    // "*f(void)" is the declarator
enum COLOR {RED, GREEN, BLUE} c; // "enum COLOR {RED, GREEN, BLUE}" is the type specifier
                                 // "c" is the declarator

在声明中引入的每个标识符的类型由 类型说明符 指定的类型及其 声明符 应用的类型修改的组合来确定。如果使用 auto 说明符,变量的类型也可能被推断出来。(自 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 的函数。非指针声明符 是除未加括号的指针声明符之外的任何其他声明符。

这种语法的理由是,当声明符声明的标识符以与声明符相同的形式出现在表达式中时,它将具有由类型说明符序列指定的类型。

struct C
{
    int member; // "int" is the type specifier
                // "member" is the declarator
} obj, *pObj = &obj;
// "struct C { int member; }" is the type specifier
// declarator "obj" defines an object of type struct C
// declarator "*pObj" declares a pointer to C,
// initializer "= &obj" provides the initial value for that pointer
 
int a = 1, *p = NULL, f(void), (*pf)(double);
// the type specifier is "int"
// declarator "a" defines an object of type int
//   initializer "=1" provides its initial value
// declarator "*p" defines an object of type pointer to int
//   initializer "=NULL" provides its initial value
// declarator "f(void)" declares a function taking void and returning int
// declarator "(*pf)(double)" defines an object of type pointer
//   to function taking double and returning int
 
int (*(*foo)(double))[3] = NULL;
// the type specifier is int
// 1. declarator "(*(*foo)(double))[3]" is an array declarator:
//    the type declared is "/nested declarator/ array of 3 int"
// 2. the nested declarator is "*(*foo)(double))", which is a pointer declarator
//    the type declared is "/nested declarator/ pointer to array of 3 int"
// 3. the nested declarator is "(*foo)(double)", which is a function declarator
//    the type declared is "/nested declarator/ function taking double and returning
//        pointer to array of 3 int"
// 4. the nested declarator is "(*foo)" which is a (parenthesized, as required by
//        function declarator syntax) pointer declarator.
//    the type declared is "/nested declarator/ pointer to function taking double
//        and returning pointer to array of 3 int"
// 5. the nested declarator is "foo", which is an identifier.
// The declaration introduces the identifier "foo" to refer to an object of type
// "pointer to function taking double and returning pointer to array of 3 int"
// The initializer "= NULL" provides the initial value of this pointer.
 
// If "foo" is used in an expression of the form of the declarator, its type would be
// int.
int x = (*(*foo)(1.2))[0];

不是另一个声明符的一部分的每个声明符的结尾都是一个 序列点

在所有情况下,属性说明序列 都是 属性 的可选序列(自 C23 起)。当紧跟在标识符之后出现时,它应用于正在声明的对象或函数。

[编辑] 定义

定义 是提供有关其声明的标识符的所有信息的声明。

每个 enumtypedef 的声明都是定义。

对于函数,包含函数体的声明是 函数定义

int foo(double); // declaration
int foo(double x) { return x; } // definition

对于对象,分配存储(自动或静态,但不是 extern)的声明是定义,而不分配存储(外部声明)的声明不是定义。

extern int n; // declaration
int n = 10; // definition

对于 structunion,指定成员列表的声明是定义

struct X; // declaration
struct X { int n; }; // definition

[编辑] 重声明

如果同一 作用域 中同一标识符的另一个声明更早出现,则声明不能引入标识符,除非

  • 具有链接(外部或内部)的对象的声明可以重复
extern int x;
int x = 10; // OK
extern int x; // OK
 
static int n;
static int n = 10; // OK
static int n; // OK
  • 非 VLA typedef 可以重复,只要它命名相同的类型
typedef int int_t;
typedef int int_t; // OK
struct X;
struct X { int n; };
struct X;

这些规则简化了头文件的使用。

[编辑] 注释

在 C89 中,任何 复合语句(块作用域)中的声明必须出现在块的开头,在任何 语句 之前。

此外,在 C89 中,返回 int 的函数可以通过 函数调用运算符 隐式声明,并且使用旧式 函数定义 时,类型为 int 的函数参数不必声明。

(直到 C99)

禁止空声明符;简单声明必须至少有一个声明符或声明至少一个结构体/联合体/枚举标签,或引入至少一个枚举常量。

如果声明符的任何部分是 可变长度数组 (VLA) 声明符,则整个声明符的类型被称为“可变修改类型”。从可变修改类型定义的类型也是可变修改的 (VM)。

任何可变修改类型的声明只能出现在块作用域或函数原型作用域中,并且不能是结构体或联合体的成员。虽然 VLA 只能具有自动或分配的 存储期,但 VM 类型(例如指向 VLA 的指针)可以是静态的。VM 类型的使用还有其他限制,请参阅 gotoswitchlongjmp

(自 C99 起)

static_asserts 从 C 语法角度来看被认为是声明(因此它们可以出现在声明可以出现的任何地方),但它们不引入任何标识符,也不遵循声明语法。

(自 C11 起)

属性声明也被认为是声明(因此它们可以出现在声明可以出现的任何地方),但它们不引入任何标识符。单个 ; 而没有 属性说明序列 不是属性声明,而是语句。

(自 C23 起)

[编辑] 参考文献

  • C23 标准 (ISO/IEC 9899:2024)
  • 6.7 声明 (p: TBD)
  • 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 声明 (p: 97-130)
  • C89/C90 标准 (ISO/IEC 9899:1990)
  • 3.5 声明

[编辑] 参见

C++ 文档 关于 声明