# 变量

变量（variable）可以理解成一块内存区域的名字。通过变量名，可以引用这块内存区域，获取里面存储的值。由于值可能发生变化，所以称为变量，否则就是常量了。

## 变量名

变量名在 C 语言里面属于标识符（identifier），命名有严格的规范。

- 只能由字母（包括大写和小写）、数字和下划线（`_`）组成。
- 不能以数字开头。
- 长度不能超过63个字符。

下面是一些无效变量名的例子。

```c
$zj
j**p
2cat
Hot-tab
tax rate
don't
```

上面示例中，每一行的变量名都是无效的。

变量名区分大小写，`star`、`Star`、`STAR`都是不同的变量。

并非所有的词都能用作变量名，有些词在 C 语言里面有特殊含义（比如`int`），另一些词是命令（比如`continue`），它们都称为关键字，不能用作变量名。另外，C 语言还保留了一些词，供未来使用，这些保留字也不能用作变量名。下面就是 C 语言主要的关键字和保留字。

> auto, break, case, char, const, continue, default, do, double, else, enum, extern, float, for, goto, if, inline, int, long, register, restrict, return, short, signed, sizeof, static, struct, switch, typedef, union, unsigned, void, volatile, while

另外，两个下划线开头的变量名，以及一个下划线 + 大写英文字母开头的变量名，都是系统保留的，自己不应该起这样的变量名。

## 变量的声明

C 语言的变量，必须先声明后使用。如果一个变量没有声明，就直接使用，会报错。

每个变量都有自己的类型（type）。声明变量时，必须把变量的类型告诉编译器。

```c
int height;
```

上面代码声明了变量`height`，并且指定类型为`int`（整数）。

如果几个变量具有相同类型，可以在同一行声明。

```c
int height, width;

// 等同于
int height;
int width;
```

注意，声明变量的语句必须以分号结尾。

一旦声明，变量的类型就不能在运行时修改。

## 变量的赋值

C 语言会在变量声明时，就为它分配内存空间，但是不会清除内存里面原来的值。这导致声明变量以后，变量会是一个随机的值。所以，变量一定要赋值以后才能使用。

赋值操作通过赋值运算符（`=`）完成。

```c
int num;
num = 42;
```

上面示例中，第一行声明了一个整数变量`num`，第二行给这个变量赋值。

变量的值应该与类型一致，不应该赋予不是同一个类型的值，比如`num`的类型是整数，就不应该赋值为小数。虽然 C 语言会自动转换类型，但是应该避免赋值运算符两侧的类型不一致。

变量的声明和赋值，也可以写在一行。

```c
int num = 42;
```

多个相同类型变量的赋值，可以写在同一行。

```c
int x = 1, y = 2;
```

注意，赋值表达式有返回值，等于等号右边的值。

```c
int x, y;

x = 1;
y = (x = 2 * x);
```

上面代码中，变量`y`的值就是赋值表达式（`x = 2 * x`）的返回值`2`。

由于赋值表达式有返回值，所以 C 语言可以写出多重赋值表达式。

```c
int x, y, z, m, n;

x = y = z = m = n = 3;
```

上面的代码是合法代码，一次为多个变量赋值。赋值运算符是从右到左执行，所以先为`n`赋值，然后依次为`m`、`z`、`y`和`x`赋值。

C 语言有左值（left value）和右值（right value）的概念。左值是可以放在赋值运算符左边的值，一般是变量；右值是可以放在赋值运算符右边的值，一般是一个具体的值。这是为了强调有些值不能放在赋值运算符的左边，比如`x = 1`是合法的表达式，但是`1 = x`就会报错。

## 变量的作用域

作用域（scope）指的是变量生效的范围。C 语言的变量作用域主要有两种：文件作用域（file scope）和块作用域（block scope）。

文件作用域（file scope）指的是，在源码文件顶层声明的变量，从声明的位置到文件结束都有效。

```c
int x = 1;

int main(void) {
  printf("%i\n", x);
}
```

上面示例中，变量`x`是在文件顶层声明的，从声明位置开始的整个当前文件都是它的作用域，可以在这个范围的任何地方读取这个变量，比如函数`main()`内部就可以读取这个变量。

块作用域（block scope）指的是由大括号（`{}`）组成的代码块，它形成一个单独的作用域。凡是在块作用域里面声明的变量，只在当前代码块有效，代码块外部不可见。

```c
int a = 12;

if (a == 12) {
  int b = 99;
  printf("%d %d\n", a, b);  // 12 99
}

printf("%d\n", a);  // 12
printf("%d\n", b);  // 出错
```

上面例子中，变量`b`是在`if`代码块里面声明的，所以对于大括号外面的代码，这个变量是不存在的。

代码块可以嵌套，即代码块内部还有代码块，这时就形成了多层的块作用域。它的规则是：内层代码块可以使用外层声明的变量，但外层不可以使用内层声明的变量。如果内层的变量与外层同名，那么会在当前作用域覆盖外层变量。

```c
{
  int i = 10;

  {
    int i = 20;
    printf("%d\n", i);  // 20
  }

  printf("%d\n", i);  // 10
}
```

上面示例中，内层和外层都有一个变量`i`，每个作用域都会优先使用当前作用域声明的`i`。

最常见的块作用域就是函数，函数内部声明的变量，对于函数外部是不可见的。`for`循环也是一个块作用域，循环变量只对循环体内部可见，外部是不可见的。

```c
for (int i = 0; i < 10; i++)
  printf("%d\n", i);

printf("%d\n", i); // 出错
```

上面示例中，`for`循环省略了大括号，但依然是一个块作用域，在外部读取循环变量`i`，编译器就会报错。