当前位置:嗨网首页>书籍在线阅读

06-自动变量

  
选择背景色: 黄橙 洋红 淡粉 水蓝 草绿 白色 选择字体: 宋体 黑体 微软雅黑 楷体 选择字体大小: 恢复默认

12.1.4 自动变量

属于自动存储类别的变量具有自动存储期、块作用域且无链接。默认情况下,声明在块或函数头中的任何变量都属于自动存储类别。为了更清楚地表达你的意图(例如,为了表明有意覆盖一个外部变量定义,或者强调不要把该变量改为其他存储类别),可以显式使用关键字auto,如下所示:

int main(void)
{
     auto int plox;

关键字 auto 是存储类别说明符(storage-class specifier)。 auto 关键字在C++中的用法完全不同,如果编写C/C++兼容的程序,最好不要使用 auto 作为存储类别说明符。

块作用域和无链接意味着只有在变量定义所在的块中才能通过变量名访问该变量(当然,参数用于传递变量的值和地址给另一个函数,但是这是间接的方法)。另一个函数可以使用同名变量,但是该变量是存储在不同内存位置上的另一个变量。

变量具有自动存储期意味着,程序在进入该变量声明所在的块时变量存在,程序在退出该块时变量消失。原来该变量占用的内存位置现在可做他用。

接下来分析一下嵌套块的情况。块中声明的变量仅限于该块及其包含的块使用。

int loop(int n)
{
     int m; // m 的作用域
     scanf("%d", &m);
     {
          int i; // m 和 i 的作用域
          for (i = m; i < n; i++)
               puts("i is local to a sub-block\n");
     }
     return m; // m 的作用域,i 已经消失
}

在上面的代码中, i 仅在内层块中可见。如果在内层块的前面或后面使用 i ,编译器会报错。通常,在设计程序时用不到这个特性。然而,如果这个变量仅供该块使用,那么在块中就近定义该变量也很方便。这样,可以在靠近使用变量的地方记录其含义。另外,这样的变量只有在使用时才占用内存。变量 nm 分别定义在函数头和外层块中,它们的作用域是整个函数,而且在调用函数到函数结束期间都一直存在。

如果内层块中声明的变量与外层块中的变量同名会怎样?内层块会隐藏外层块的定义。但是离开内层块后,外层块变量的作用域又回到了原来的作用域。程序清单12.1演示了这一过程。

程序清单12.1  hiding.c 程序

// hiding.c -- 块中的变量
#include <stdio.h>
int main()
{
     int x = 30;            // 原始的 x
     printf("x in outer block: %d at %p\n", x, &x);
     {
          int x = 77;        // 新的 x,隐藏了原始的 x
          printf("x in inner block: %d at %p\n", x, &x);
     }
     printf("x in outer block: %d at %p\n", x, &x);
     while (x++ < 33)        // 原始的 x
     {
          int x = 100;       // 新的 x,隐藏了原始的 x 
          x++;
          printf("x in while loop: %d at %p\n", x, &x);
     }
     printf("x in outer block: %d at %p\n", x, &x);
     return 0;
}

下面是该程序的输出:

x in outer block: 30 at 0x7fff5fbff8c8
x in inner block: 77 at 0x7fff5fbff8c4
x in outer block: 30 at 0x7fff5fbff8c8
x in while loop: 101 at 0x7fff5fbff8c0
x in while loop: 101 at 0x7fff5fbff8c0
x in while loop: 101 at 0x7fff5fbff8c0
x in outer block: 34 at 0x7fff5fbff8c8

首先,程序创建了变量 x 并初始化为 30 ,如第1条 printf() 语句所示。然后,定义了一个新的变量 x ,并设置为 77 ,如第2条 printf() 语句所示。根据显示的地址可知,新变量隐藏了原始的 x 。第3条 printf() 语句位于第1个内层块后面,显示的是原始的 x 的值,这说明原始的 x 既没有消失也不曾改变。

也许该程序最难懂的是 while 循环。 while 循环的测试条件中使用的是原始的 x

while(x++ < 33)

在该循环中,程序创建了第3个 x 变量,该变量只定义在 while 循环中。所以,当执行到循环体中的 x++ 时,递增为 101 的是新的 x ,然后 printf() 语句显示了该值。每轮迭代结束,新的 x 变量就消失。然后循环的测试条件使用并递增原始的 x ,再次进入循环体,再次创建新的 x 。在该例中,这个 x 被创建和销毁了3次。注意,该循环必须在测试条件中递增 x ,因为如果在循环体中递增 x ,那么递增的是循环体中创建的 x ,而非测试条件中使用的原始 x

我们使用的编译器在创建 while 循环体中的 x 时,并未复用内层块中 x 占用的内存,但是有些编译器会这样做。

该程序示例的用意不是鼓励读者要编写类似的代码(根据C的命名规则,要想出别的变量名并不难),而是为了解释在内层块中定义变量的具体情况。

1.没有花括号的块

前面提到一个C99特性:作为循环或 if 语句的一部分,即使不使用花括号( {} ),也是一个块。更完整地说,整个循环是它所在块的子块(sub-block),循环体是整个循环块的子块。与此类似, if 语句是一个块,与其相关联的子语句是 if 语句的子块。这些规则会影响到声明的变量和这些变量的作用域。程序清单12.2演示了 for 循环中该特性的用法。

程序清单12.2  forc99.c 程序

// forc99.c -- 新的 C99 块规则
#include <stdio.h>
int main()
{
     int n = 8;
     printf("   Initially, n = %d at %p\n", n, &n);
     for (int n = 1; n < 3; n++)
          printf("      loop 1: n = %d at %p\n", n, &n);
     printf("After loop 1, n = %d at %p\n", n, &n);
     for (int n = 1; n < 3; n++)
     {
          printf(" loop 2 index n = %d at %p\n", n, &n);
          int n = 6;
          printf("      loop 2: n = %d at %p\n", n, &n);
          n++;
     }
     printf("After loop 2, n = %d at %p\n", n, &n);
     return 0;
}

假设编译器支持C语言的这个新特性,该程序的输出如下:

     Initially, n = 8 at 0x7fff5fbff8c8
loop 1: n = 1 at 0x7fff5fbff8c4
loop 1: n = 2 at 0x7fff5fbff8c4
After loop 1, n = 8 at 0x7fff5fbff8c8
loop 2 index n = 1 at 0x7fff5fbff8c0
loop 2: n = 6 at 0x7fff5fbff8bc
loop 2 index n = 2 at 0x7fff5fbff8c0
loop 2: n = 6 at 0x7fff5fbff8bc
After loop 2, n = 8 at 0x7fff5fbff8c8

第1个 for 循环头中声明的 n ,其作用域作用至循环末尾,而且隐藏了原始的 n 。但是,离开循环后,原始的 n 又起作用了。

第2个 for 循环头中声明的 n 作为循环的索引,隐藏了原始的 n 。然后,在循环体中又声明了一个 n ,隐藏了索引 n 。结束一轮迭代后,声明在循环体中的 n 消失,循环头使用索引 n 进行测试。当整个循环结束时,原始的 n 又起作用了。再次提醒读者注意,没必要在程序中使用相同的变量名。如果用了,各变量的情况如上所述。

注意 支持C99和C11 有些编译器并不支持C99/C11的这些作用域规则(Microsoft Visual Studio 2012就是其中之一)。有些编译会提供激活这些规则的选项。例如,撰写本书时,gcc默认支持了C99的许多特性,但是要用 –std=c99 选项激活程序清单12.2中使用的特性:

gcc –std=c99 forc99.c

与此类似,gcc或clang都要使用 –std=c1x-std=c11 选项,才支持C11特性。

2.自动变量的初始化

自动变量不会初始化,除非显式初始化它。考虑下面的声明:

int main(void)
{
     int repid;
     int tents = 5;

tents 变量被初始化为 5 ,但是 repid 变量的值是之前占用分配给 repid 的空间中的任意值(如果有的话),别指望这个值是 0 。可以用非常量表达式(non-constant expression)初始化自动变量,前提是所用的变量已在前面定义过:

int main(void)
{
     int ruth = 1;
     int rance = 5 * ruth;    // 使用之前定义的变量