Menu

C 语言变量:声明、初始化与赋值

如何在 C 语言中声明变量,声明和初始化的区别,命名规则,一次声明多个变量,以及为什么未初始化的变量是新手最常见的 bug。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

变量是一块有名字的内存,保存着程序可以读取和修改的值。在 C 语言中,你总是要事先说明它保存的是哪种值,而且这个选择在变量的整个生命周期内都不会改变。

声明变量

格式是类型、名字、分号:

int age;
double price;
char grade;

每一行都为对应的数据类型预留足够的内存,并给它起一个名字。此时还没有存入任何东西:空间已经存在,但里面的内容是之前运行的程序留下的任何东西。

你也可以在声明的同时赋一个值,这叫作初始化:

注意格式说明符:int 用 %d,double 用 %f,char 用 %c。printf 不会检查传给它的值,它完全信任你的格式字符串,所以一旦不匹配,打印出来的是乱七八糟的结果,而不是报错。

声明之后再赋值

变量存在以后,用 = 来修改它:

C 语言中的 = 是赋值,不是相等。score = score + 10 的意思是“取出 score 中的值,加上 10,再把结果放回 score”。比较运算符是 ==,把两者搞混是非常常见的 bug,布尔值页面专门为它写了一节。

简写 score += 10 用更少的字符做同样的事;复合赋值运算符涵盖了所有的算术运算。

未初始化变量的陷阱

这是 C 语言新手最常见的 bug,而且它不会报错:

运行一下。你可能看到 15,这是正确答案。你也可能看到一个巨大的随机数。第二次运行或换一台机器,你还可能看到别的结果。这些都是“正确”的行为,因为读取未初始化的局部变量属于未定义行为:C 标准对会发生什么不作任何要求。

和许多语言不同,C 不会把局部变量清零。局部变量是栈上的一小块空间,而栈上满是之前函数调用留下的残余数据。有时这些残余数据恰好是零,这正是这个 bug 如此危险的原因:它在测试时常常能正常工作,到了生产环境却出问题。

修复只需要一处小改动:

养成习惯:在声明每个变量时就初始化它。如果你确实还没有合理的值,用 0 或 NULL 也比垃圾值强。另外,用 -Wall 编译,它能捕获大多数这类情况:

warning: 'total' is used uninitialized in this function

全局变量和 static 变量是例外,它们会被自动初始化为零。依赖这一点没有问题,但还是写上 = 0,可以表明你的意图。

一次声明多个变量

相同类型的变量可以写在同一个声明里,带不带初始值都可以:

对于 x, y, z 这类紧密相关的值,这种写法合法又常见。但一个经典的误读也藏在这里:

int a = 0, b = 0, c = 0;   /* all three are 0 */
int d, e, f = 0;           /* only f is 0 - d and e are garbage */

初始值只属于一个名字,而不是整行。当几个值的含义不同时,每个变量单独写一行,读起来更清楚,diff 也更清楚。

在一行里声明多个指针也有同样的陷阱,而且更隐蔽:

int *p, q;   /* p is a pointer to int; q is a plain int */

* 绑定的是名字,不是类型。指针要一行声明一个。

命名规则和惯例

编译器的规则:

  • 只能使用字母、数字和下划线。
  • 不能以数字开头:total2 可以,2total 不行。
  • 不能是 C 语言关键字:不能把变量命名为 int、return、for、double。
  • 区分大小写:count、Count 和 COUNT 是三个不同的变量。
  • 以下划线开头或包含双下划线的名字保留给编译器和标准库使用。不要使用这样的名字。
int itemCount;       /* legal */
int item_count;      /* legal - the usual C style */
int _count;          /* legal but reserved - avoid */
int item-count;      /* error: '-' is the minus operator */
int 3rd_place;       /* error: starts with a digit */
int float;           /* error: keyword */

大多数 C 代码遵循的惯例是:变量和函数用 snake_case,宏常量用 UPPER_CASE,名字要说明值的含义,而不是它的类型。elapsed_ms 好过 t;customer_count 好过 n2。

在作用域很小的地方,短名字依然没问题:i 作为循环计数器人人都懂,谁也不想写 for (int loop_iteration_index = 0; ...)。

变量在哪里有效

在哪里声明变量,决定了谁能看到它、它能存在多久。这就是作用域,简单来说:

变量从声明处开始可见,直到包含它的代码块的右花括号为止。全局变量在整个程序运行期间都存在,并且处处可见,这听起来很方便,而这正是它们惹麻烦的原因:任何函数都能修改它,要查出是谁改的,就得把整个文件翻一遍。优先使用局部变量,并通过参数把值传给函数。

随处声明(C99 及以后)

在最初的 C89 中,代码块里的所有声明都必须出现在任何语句之前:

int main(void) {
    int i, sum = 0;      /* all declarations first */
    printf("Starting\n");
    for (i = 0; i < 10; i++) { sum += i; }
    return 0;
}

从 C99 开始,你可以在第一次需要这个值的地方声明它,包括在 for 的头部:

这是更好的风格:声明紧挨着第一次使用的地方,而且 i 不会泄漏到函数的其余部分。用 -std=c17(或任何 C99 及以上的设置)编译,在哪里都能正常工作。

常见问题

C 语言怎么声明变量?

先写类型,再写名字,最后加分号:int age;。你也可以同时给它一个值,写成 int age = 30;,这叫作初始化,而且几乎总是你想要的写法。

声明变量和初始化变量有什么区别?

声明是预留空间并给它起名字(int count;)。初始化是在创建的那一刻就放入一个值(int count = 0;)。只声明而未初始化的局部变量,里面是那个地址上原本就有的字节,读取它属于未定义行为。

C 语言变量名有哪些规则?

只能包含字母、数字和下划线;不能以数字开头;不能是 int、for 这样的关键字;并且区分大小写,所以 total 和 Total 是两个不同的变量。以下划线开头的名字是保留的,应避免使用。

C 语言可以在函数的任何位置声明变量吗?

从 C99 开始可以,每个变量在第一次需要时再声明即可。在最初的 C89 标准中,所有声明都必须出现在代码块的开头,所以老代码在真正干活之前总有一大堆声明。

Coddy 编程语言插图

和 Coddy 一起学编程

开始