06-静态持续变量
9.2.3 静态持续变量
和 C 语言一样,C++也为静态存储持续性变量提供了 3 种链接性:外部链接性(可在其他文件中访问)、内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)。这3种链接性都在整个程序执行期间存在,与自动变量相比,它们的寿命更长。由于静态变量的数目在程序运行期间是不变的,因此程序不需要使用特殊的装置(如栈)来管理它们。编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。另外,如果没有显式地初始化静态变量,编译器将把它设置为0。在默认情况下,静态数组和结构将每个元素或成员的所有位都设置为0。
注意: 传统的K&R C不允许初始化自动数组和结构,但允许初始化静态数组和结构。ANSI C和C++允许对这两种数组和结构进行初始化,但有些旧的C++翻译器使用与ANSI C不完全兼容的C编译器。如果使用的是这样的实现,则可能需要使用这3种静态存储类型之一,以初始化数组和结构。
下面介绍如何创建这3种静态持续变量,然后介绍它们的特点。要想创建链接性为外部的静态持续变量,必须在代码块的外面声明它;要创建链接性为内部的静态持续变量,必须在代码块的外面声明它,并使用static限定符;要创建没有链接性的静态持续变量,必须在代码块内声明它,并使用static限定符。下面的代码片段说明这3种变量:
...
int global = 1000; // static duration, external linkage
static int one_file = 50; // static duration, internal linkage
int main()
{
...
}
void funct1(int n)
{
static int count = 0; // static duration, no linkage
int llama = 0;
...
}
void funct2(int q)
{
...
}
正如前面指出的,所有静态持续变量(上述示例中的global、one_file和count)在整个程序执行期间都存在。在funct1()中声明的变量count的作用域为局部,没有链接性,这意味着只能在funct1()函数中使用它,就像自动变量llama一样。然而,与llama不同的是,即使在funct1()函数没有被执行时,count也留在内存中。global和one_file的作用域都为整个文件,即在从声明位置到文件结尾的范围内都可以被使用。具体地说,可以在main()、funct1()和funct2()中使用它们。由于one_file的链接性为内部,因此只能在包含上述代码的文件中使用它;由于global的链接性为外部,因此可以在程序的其他文件中使用它。
所有的静态持续变量都有下述初始化特征:未被初始化的静态变量的所有位都被设置为0。这种变量被称为零初始化的(zero-initialized)。
表9.1总结了引入名称空间之前使用的存储特性。下面详细介绍各种静态持续性。
表9.1指出了关键字static的两种用法,但含义有些不同:用于局部声明,以指出变量是无链接性的静态变量时,static表示的是存储持续性;而用于代码块外的声明时,static表示内部链接性,而变量已经是静态持续性了。有人称之为关键字重载,即关键字的含义取决于上下文。
| 存 储 描 述 | 持 续 性 | 作 用 域 | 链 接 性 | 如 何 声 明 | | :----- | :----- | :----- | :----- | :----- | :----- | :----- | | 自动 | 自动 | 代码块 | 无 | 在代码块中 | | 寄存器 | 自动 | 代码块 | 无 | 在代码块中,使用关键字register | | 静态,无链接性 | 静态 | 代码块 | 无 | 在代码块中,使用关键字static | | 静态,外部链接性 | 静态 | 文件 | 外部 | 不在任何函数内 | | 静态,内部链接性 | 静态 | 文件 | 内部 | 不在任何函数内,使用关键字static |
静态变量的初始化
除默认的零初始化外,还可对静态变量进行常量表达式初始化和动态初始化。您可能猜到了,零初始化意味着将变量设置为零。对于标量类型,零将被强制转换为合适的类型。例如,在C++代码中,空指针用0表示,但内部可能采用非零表示,因此指针变量将被初始化相应的内部表示。结构成员被零初始化,且填充位都被设置为零。
零初始化和常量表达式初始化被统称为静态初始化,这意味着在编译器处理文件(翻译单元)时初始化变量。动态初始化意味着变量将在编译后初始化。
那么初始化形式由什么因素决定呢?首先,所有静态变量都被零初始化,而不管程序员是否显式地初始化了它。接下来,如果使用常量表达式初始化了变量,且编译器仅根据文件内容(包括被包含的头文件)就可计算表达式,编译器将执行常量表达式初始化。必要时,编译器将执行简单计算。如果没有足够的信息,变量将被动态初始化。请看下面的代码:
#include <cmath>
int x; // zero-initialization
int y = 5; // constant-expression initialization
long z = 13 * 13; // constant-expression initialization
const double pi = 4.0 * atan(1.0); // dynamic initialization
首先,x、y、z和pi被零初始化。然后,编译器计算常量表达式,并将y和z分别初始化为5和169。但要初始化pi,必须调用函数atan(),这需要等到该函数被链接且程序执行时。
常量表达式并非只能是使用字面常量的算术表达式。例如,它还可使用sizeof运算符:
int enough = 2 * sizeof (long) + 1; // constant expression initialization
C++11新增了关键字constexpr,这增加了创建常量表达式的方式。但本书不会更详细地介绍C++11新增的这项新功能。