存储持续性、作用域和链接性(C++)

存储数据的方案:

C++使用三种(在C++11中是四种)不同的方案来存储数据,这些方案的区别就在于数据保留在内存中的时间。

  • 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储持续性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量。
  • 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都为静态。它们在程序整个运行过程中都存在。C++有3种存储持续性为静态的变量。
  • 线程存储持续性(C++11):当前,多核处理器很常见,这些CPU可同时处理多个执行任务。这让程序能够将计算放在可并行处理的不同线程中。如果变量是thread_local声明的,则其生命周期与所属的线程一样长。
  • 动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储(free store)或堆(heap)。

作用域和链接:

作用域(scope)描述了名称在文件(翻译单元)的多大范围可见。作用域为全局的变量在定义位置到文件结尾之间都可用。自动变量的作用域为局部,静态变量的作用域是全局还是局部取决于它是如何被定义的。在函数原型作用域中使用的名称只在包含参数列表的括号内可用。
链接性(linkage)描述了名称如何在不同单元间共享。链接性为外部的名称可在文件间共享,为内部的名称只能由一个文件中的函数共享。自动变量的名称没有链接性,因为它们不能共享。

自动存储持续性:

在默认情况下,在函数中声明的函数参数和变量的储存持续性为自动,作用域为局部,没有链接性。也就是说,如果在main()中声明了一个名为texas的变量,并在函数oil()中也声明了一个名为texas变量,则创建了两个独立的变量——只有在定义它们的函数中才能使用它们。
另外,当程序开始执行这些变量所属的代码块时,将为其分配内存:当函数结束时,这些变量都将消失。
如果在代码块中定义了变量,则该变量的存在时间和作用域将被限制在该代码块内。例如:

int main() {
    int teledeli = 5;
    {   //websight allocated
        cout << "Hello\n";
        int websight = -2;  //websight scope begins
        cout << websight << ' ' << teledeli << endl;
    }   //websight expires
    cout << teledeli << endl;
}   //teledeli expires

websight只在内部代码块中可见,它的作用域是从定义它的位置当该代码块的结尾。而teledeli在内部代码块和外部代码块都是可见的。

当内部代码块的变量也为teledeli,程序执行内部代码块中的语句时,将teledeli解释为局部代码块变量。
即:新的定义隐藏了以前的定义,新定义可见,旧定义暂时不可见。在程序离开该代码块时,原来的定义又重新可见。

#include<iostream>
#include<fstream>
using namespace std;
int main() {
    int teledeli = 5;
    {
        int teledeli = -2;
        cout << teledeli << endl;
    }
    cout << teledeli << endl;
}   //teledeli expires

输出为:
-2
5

自动变量和栈:

由于自动变量地数目随函数的开始和结束而增减,因此程序必须在运行时对自动变量进行管理。常用地方法是留出一段内存,并将其视为栈,以管理变量的增减。之所以被称为栈,是由于新数据被象征性地放在原有数据的上面,当程序使用完后,将其从栈中删除。栈的默认长度取决于实现,但编译器通常提供改变栈长度的选项。
程序使用两个指针来跟踪栈,一个指针指向栈底——栈的开始位置,另一个指针指向堆顶——下一个可用内存单元。当函数被调用时,其自动变量将被加入到栈中,栈顶指针指向变量后面的下一个可用的内存单元。函数结束时,栈顶指针被重置为函数被调用前的值,从而释放新变量使用的内存。

静态持续变量:

和C语言一样,C++也为静态存储持续性变量提供了3中链接性:外部链接性(可在其他文件中访问)、内部链接性(只能在当前文件中访问)和无链接性(只能在当前函数或代码块中访问)。这3种链接性都在整个程序执行期间存在,与自动变量相比,它们的寿命更长。由于静态变量的数量在程序运行期间是不变的,因此程序不需要使用特殊的装置(如栈)来管理它们。编译器将分配固定的内存块来存储所有的静态变量,这些变量在整个程序执行期间一直存在。另外,如果没有显式地初始化静态变量,编译器将把它设置为0。在默认情况下,静态数组和结构将每个元素或成员地所有位都设置为0。

要想创建链接性为外部的静态持续变量,必须在代码块的外面声明它;
要想创建链接性为内部的静态持续变量,必须在代码块的外面声明它,并且使用static限定符;
要想创建链接性为外部的静态持续变量,必须在代码块内声明它,并且使用static限定符;

int global = 1000;
static int one_file = 50;

int main() {
    ...
}   

void funct1(int n) {
    static int count = 0;
    int llama = 0;
    ...
}

void funct2(int q) {
    ...
}

其中,global、one_file与count为静态持续变量,在整个程序执行期间都存在。
在funct1()中定义的count的作用域为局部,没有链接性,这意味着只能在funct1()函数中使用它,就像自动变量llama一样。然而,与llama不同的是,即使在funct1()函数没有被执行时,count也留在内存中。
global和one_file的作用域都为整个文件,即在从声明位置到文件结尾的范围内都可以被使用。由于one_file的链接性为内部,因此只能在包含上述代码的文件中使用它;由于global的链接性为外部,因此可以在程序的其他文件中使用它。
所有的静态持续变量都又下述初始化特征:未被初始化的静态变量的所有位都不被设置为0。这种变量被称为零初始化的。

5种变量储存方式

存储描述 持续性 作用域 链接性 如何声明
自动 自动 代码块 在代码块中
寄存器 自动 代码块 在代码块中,使用关键字register
静态,无链接性 静态 代码块 在代码块中,使用关键字static
静态,外部链接性 静态 文件 外部 不在任何函数内
静态,内部链接性 静态 文件 内部 不在任何函数内,使用static

静态持续性、外部链接性:

链接性为外部的变量通常简称为外部变量,它们的存储持续性为静态,作用域为整个文件。外部变量在函数外部定义的,因此对所有函数而言都是外部的。外部变量也称全局变量(相对于局部的自动变量)。

单定义规则:
一方面,在每个使用外部变量的文件中,都必须声明它;另一方面,C++有“单定义规则”,该规则指出,变量只能有一次定义。为满足这种需求,C++提供两种变量声明。一种是定义声明(defining declaration)或简称为定义(definition),它给变量分配存储空间;另一种是引用声明(referencing declaration)或简称声明(declaration),他不给变量分配存储空间,因为它使用已有的变量。

引用声明使用关键词extern,且不进行初始化;否则,声明为定义,导致分配空间,如果要在多个文件中使用外部变量,只需在一个文件中包含该变量的定义(但定义规则),但在使用该变量的其他所有文件中,都必须使用关键字extern声明它:

//file1.cpp
extern int cats = 20;   //definition because of initialization
int dogs = 22;  //also a definition
int fieas;  //also a definition

//file2.cpp
//use cats, dogs from file1.cpp
extern int cats;    //not definitions because they use
extern int dogs;    //extern and have no initialization


//file3.cpp
//use cats, dogs, and fieas from file1.cpp
extern int cats;
extern int dogs;
extern int fieas;

其中file1.cpp中的cats因为存在赋值,因此该语句并非声明而是定义,extern可以忽略,同样定义了cats和dogs。file2.cpp中声明了cats和dogs,但没有重新声明fieas,因此无法访问它。

单定义规则并非意味着不能有多个变量的名称相同,例如,在不同函数中声明的同名自动变量是彼此独立的,它们都有自己的地址。

external.cpp

#include<iostream>
using namespace std;
double warming = 0.3;
void update(double dt);
void local();

int main() {
    cout << "Global warming is " << warming << " degrees.\n";
    update(0.1);
    cout << "Global warming is " << warming << " degrees.\n";
    local();
    cout << "Global warming is " << warming << " degrees.\n";
    return 0;
}

support.cpp

#include<iostream>
using namespace std;
extern double warming;
void update(double dt);
void local();
void update(double dt) {
    extern double warming;
    warming += dt;
    cout << "Updating global warming to " << warming << " degrees.\n";
}
void local() {
    double warming = 0.8;
    cout << "Local warming = " << warming << " degree.\n";
    cout << "But global warming = " << ::warming << " degree.\n";
}

程序输出:

Global warming is 0.3 degrees.
Updating global warming is 0.4 degrees.
Global warming is 0.4 degrees.
Local warming = 0.8 degrees.
But global warming = 0.4 degrees.
Global warming is 0.4 degrees.

support.cpp中,声明了warming,由此我们可知使用的是external.cpp中的warming,因此update()修改了静态变量warming的值,另外update()中使用关键字extern重新声明了变量 warming,这个关键字的意思是,通过这个名称使用在外部定义的变量。由于即使省略该声明,update()的功能也相同,因此该声明是可选的。

loca()函数中定义了一个新的局部变量warming,局部变量隐藏全局变量。但C++比C语言更进一步——它提供了作用域解析运算符(::)。放在变量名前面时,该运算符表示使用变量的全局版本。

静态持续性、内部链接性:

如果要在其他文件中使用相同的名称来表示其他变量:其中省略关键字extern是错误的,因为这种做法违反了单定义规则。

但如果文件定义了一个静态外部变量,其名称与另一个文件中声明的常规外部变量相同时,则在该文件中,静态变量将隐藏常规外部变量:

//file1.cpp
int errors = 20;

//file2.cpp
static int errors = 5;
void froobish() {
    cout << errors; //uses errors defined in file2
}

这没有违法单定义规则,因为关键字static指出标识符errors的链接性为内部,因此并非要提供外部定义。

静态存储持续性、无链接性:

静态持续家族的第三个成员:无链接性的局部变量。
将static限定符用于在代码块中定义的变量。在代码块中使用static时,将导致局部变量的存储持续性为静态的。这意味着虽然该变量值在该代码块中可用,但它在该代码块不处于活动状态时仍然存在。但它在该代码块不处于活动状态时仍然存在。因此在两次函数调用之间,静态局部变量的值将保持不变。另外,如果初始化了静态局部变量,则程序只在启动时进行一次初始化。以后再调用函数时,将不会像自动变量那样再次被初始化。

#include<iostream>
const int ArSize = 10;
void strcount(const char *str);
int main() {
    using namespace std;
    char input[ArSize];
    char next;

    cout << "Enter a line:\n";
    cin.get(input, ArSize);
    while (cin) {
        cin.get(next);
        while (next != '\n')    cin.get(next);
        strcount(input);
        cout << "Enter next line (empty line to quit):\n";
        cin.get(input, ArSize);
    }
    cout << "Bye\n";
    return 0;
}
void strcount(const char *str) {
    using namespace std;
    static int total = 0;
    int count = 0;
    cout << "\"" << str << "\" contains ";
    while (*str++)  count++;
    total += count;
    cout << count << " characters\n";
    cout << total << " characters total\n";
}

结果

每次函数被调用时,自动变量count都将被重置为0。然而,静态变量total只在程序运行时被设置为0,以后在两次函数调用之间,其值将保持不变,因此能够记录读取的字符总数。

声明:以上整理自个人理解和Stephen Prata 著的《C++ Primer Plus》

猜你喜欢

转载自blog.csdn.net/MoooLi/article/details/82726137