声明和初始化
声明 (Declaration)
声明是一种引入一个或多个标识符 (Identifier) 到程序中,并指定其含义及属性的 C 语言构造。 最简单的声明包括两部分:类型说明符 (Type Specifier)和标识符
int a; // "int"是类型说明符,"a"是标识符运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
相同类型的对象可以在一起声明:类型说明符 标识符 1, 标识符 2, 标识符 3, ... , 标识符 n;
但不推荐这种做法,尤其是类型较复杂时。
int a, b, c; // 声明三个 int 类型的对象运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
声明与定义并不总是同一个动作。以对象为例,extern int x; 只声明名字与类型,不分配存储;int x; 则是定义,会为对象提供存储。把这两个概念分开理解,后续处理多文件项目与链接问题会清楚很多。
/* file_a.c */
int g_count = 0; /* 定义 */
/* file_b.c */
extern int g_count; /* 声明 */2
3
4
5
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
2. 初始化 (Initialization)
对象声明可以通过名为初始化的步骤提供其初始值。
int a = 3;
int a = {3}; // 可选地用花括号环绕2
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
初始化表达式必须与对象类型兼容。标量对象常见写法是直接赋值,聚合类型(数组、结构体)常用花括号初始化器列表。
int x = 1;
double y = 2.0;
int arr[3] = {1, 2, 3};2
3
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
对于静态存储期对象,如果省略初始化器,标准保证其初始值为零值;自动存储期对象若省略初始化器,则初始值不确定,读取会产生未定义行为。
static int s; /* 默认初始化为 0 */
int main(void) {
int a; /* 未初始化,不可直接读取 */
return 0;
}2
3
4
5
6
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
3. 指派初始化器
数组和结构体可以使用指派初始化器指定目标成员或下标,这在写稀疏初始化和配置表时很有用。
struct config {
int width;
int height;
int fps;
};
struct config c = {
.height = 1080,
.width = 1920,
.fps = 60
};2
3
4
5
6
7
8
9
10
11
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
这种写法能减少“位置记忆负担”,在字段增减时也更容易维护。
4. 常见错误
同一作用域内重复定义同名对象会报错;把未初始化自动存储期对象当成已有值使用也属于错误来源。建议把“声明即初始化”作为默认写法,只有确实不能立即给出初值时再拆开。
5. 声明位置与可见范围
声明越靠近首次使用点,可见范围通常越小,语义也越容易保持一致。把对象声明提早放到很远的位置,会增加后续路径误用概率,尤其在长函数里更明显。将声明放在“刚好需要它”的位置,通常能让读者更快建立上下文。
int sum_positive(const int *a, int n) {
int total = 0;
for (int i = 0; i < n; ++i) {
int v = a[i]; /* 靠近首次使用位置声明 */
if (v > 0) {
total += v;
}
}
return total;
}2
3
4
5
6
7
8
9
10
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。
6. 初始化语义要与用途一致
初始化不仅是“给一个值”,也是在表达对象的初始语义:零值是“尚未处理”,特定枚举值是“明确状态”,空指针是“尚未绑定资源”。如果初值只为消除告警而与真实语义无关,后续代码往往会更难读。更稳妥的方式是让初值直接反映对象角色,并在必要时补充简短说明。
enum parse_state {
PARSE_INIT,
PARSE_DONE,
PARSE_FAIL
};
enum parse_state st = PARSE_INIT;2
3
4
5
6
7
运行结果:该代码块主要用于语法或结构说明,单独运行通常无终端输出。