C++ Primer Plus书之--C++存储持续性, 作用域和链接性

存储持续性, 作用域和链接性

C++里数据存储的方案按数据保留在内存中的时间可以分为:

1.自动存储持续性: 在函数体内声明的变量(包括函数参数)的存储持续性是自动的, 也就是说它们在程序开始执行其所属的函数或者代码块时被创建, 在执行完函数或代码块时, 它们使用的内存被释放.

2.静态存储持续性: 在函数体外定义的变量和使用关键字static定义的变量, 它们在程序整个运行过程中都存在.

3.线程存储持续性: 如果变量是使用关键字thread_local声明的, 则其生命周期与所属的线程一样长.

4.动态存储持续性: 用new运算符分配的内存将一直存在, 直到使用delete运算符将其释放或程序结束为止. 这种内存的存储持续性为动态, 也被称为自由存储或堆.

作用域和链接

作用域(scope): 描述了名称在文件的多大范围内可见. 例如: 函数中定义的变量可在该函数中使用, 但不能在其他函数中使用; 而在文件中的函数定义之前定义的变量则可在所有函数中使用.

链接性(linkage): 描述了名称如何在不同单元间共享, 自动变量的名称没有链接性, 因为他们不能共享.

自动变量只在包含它们的函数或代码块中可见

如上图所示, #2的teledeli在它的可见范围内会隐藏#1teledeli, 因为, 内层的局部变量会在自己的作用范围内, 将外部的同名的变量给隐藏掉.

这个和java中的局部变量的功能一样, 这里就不做详解

看一个简单的demo

// 局部变量, 也叫自动存储变量的特性
#include <iostream>
using namespace std;

void oil(int x);

int main()
{
    int t = 30;
    int year = 2018;
    
    cout << "in main() , t = " << t << ", &t = " << &t << endl;
    cout << "in main() , year = " << year << ", &year = " << &year << endl;
    oil(t);
    cout << "in main() , t = " << t << ", &t = " << &t << endl;
    cout << "in main() , year = " << year << ", &year = " << &year << endl;
    return 0;
}

// 这里的x是副本
void oil(int x)
{
    int t = 5;
    cout << "in oil() , t = " << t << ", &t = " << &t << endl;
    // 由于x是mian中的t的副本, 所以x的值一样, 但是地址不一样
    cout << "in oil() , x = " << x << ", &x = " << &x << endl;
    
    {
        // 会把oil里的t给覆盖掉
        int t = 111;
        cout << "in block , t = " << t << ", &t = " << &t << endl;
        // 由于x是mian中的t的副本, 所以x的值一样, 但是地址不一样
        cout << "in block , x = " << x << ", &x = " << &x << endl;
    }
    // 又回归到block之前的t了
    cout << "Post-block t = " << t << ", &t = " << &t << endl;
}

运行的结果为:

自动变量和栈

自动变量(也就是在函数内声明的变量, 也包含函数的形参的变量), 是存储在栈中的, 是一段连续的内存单元, 并且遵循栈的先进后出的规则

这个书中的图片显示出了栈的先进后出的规则,

 

静态持续变量

C++为静态存储持续性变量提供了3中连接性:

1.外部链接性(可在其他文件中访问)

2.内部链接性(只能在当前文件中访问)

3.无链接性(只能在当前函数或代码块中访问)

这3种链接性都在整个程序执行期间存在, 并且静态变量的数目在程序运行期间是不变的, 因此编译器将分配固定的内存块来存储所有的静态变量, 这些静态变量在整个程序执行期间一直存在. 并且如果没有显示的初始化静态变量, 编译器将把它设置为0.

怎么声明这三种链接性的变量呢?

要创建链接性为外部的静态持续变量, 必须在代码块的外面声明它;

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

要创建没有链接性的静态持续变量, 必须要在代码块内声明它, 并且使用static限定符.

下面给出了这三种变量的声明例子:

...
// 外部链接性的静态持续变量
int global = 1000;
// 内部连接性的静态持续变量
static int one_file = 50;
int main()
{
    ...
}

void fun1(int n)
{
    // 无连接性的静态持续变量
    static int count = 0;
    int llama = 0;
}

void fun2()
{
    ...
}

上述例子中的global, one_file, count在整个程序执行期间都存在, 但是count是无链接性的, 并且只能在fun1函数内部使用, 就像自动变量llama一样, 但是与llama不同的是在函数fun1()没有被执行的时候count已经存在了. global和one_file的作用域都为整个文件, 也就是能在main(), fun1(), fun2()函数中使用它们, 但是one_file的是内部连接性的也就是只能在本文件内使用, 而global的连接性为外部, 可以在程序的其他文件中使用global;

所有的静态持续变量在未被初始化的时候所有位都被设置为0, 这种该变量被称为零初始化的(zero-initialized)

 除了默认的零初始化外, 还可对静态变量进行常量表达式初始化或动态初始化. 对于标量类型, 零将被强制转换为合适的类型, 例如: 在c++中, 空指针用0表示, 但内部可能采用非零表示, 因此指针变量将被初始化成相应的内部表示. 结构成员被零初始化, 且填充位都被设置为0.

零初始化和常量表达式初始化被统称为静态初始化, 这意味着在编译器处理文件时初始化变量, 动态初始化意味着变量将在编译后初始化.

初始化过程如下:

1.所有静态变量都被零初始化, 不管程序是否显示的初始化了它.

2.接下来如果使用常量表达式初始化了变量, 且编译器仅根据文件内容(包括被包含的头文件)就可以计算表达式, 编译器将执行常量表达式初始化.

3.必要时编译器将执行简单计算, 如果没有足够的信息, 变量将被动态初始化

举个例子:
#include <cmath>
// 初始化为0
int x;
// 初始化为5, 常量表达式初始化
int y = 5; 
// 初始化为169, 常量表达式初始化
long z = 13 * 13;
// 动态初始化
const double pi = 4.0 * atan(1.0);

过程如下:首先x,y,z, pi都被初始化为0, 然后变异鸡计算常量表达式, 并将y, z设置为5, 169, 但是pi的初始化必须调用函数atan(), 这需要等到该函数被链接且程序执行时才能进行.

静态连续性和外部链接性

链接性为外部的变量通常称为外部变量, 他们存储持续性为静态, 作用域为整个文件, 外部变量是在函数外部定义的, 因此对所有函数而言都是外部的. 例如: 在main()前面或头文件中定义它们, 可以在文件中位于外部变量定义后面的任何函数中使用, 因此外部变量也成为全局变量(自动变量称为局部变量).

单定义规则:

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

引用声明使用关键字extern, 且不进行初始化; 否则声明为定义, 导致分配存储空间:

// 定义声明
double up;
// 引用声明, blem在其他位置定义了
extern int blem;
// 定义声明, 因为进行初始化了
extern char gr = 'z';

如果要在多个文件中使用外部变量, 只需要在一个文件中包含该变量的定义, 但在使用该变量的其他所有文件中, 都必须使用关键字extern声明它:

// file1.cpp
// 定义声明, 因为初始化了
extern int cats = 20;
// 定义声明
int dogs = 22;
// 定义声明
int fleas;
...

// file2.cpp
// 引用声明
extern int cats;
extern int dogs;
...

// file3.cpp
extern int dogs;
extern int cats;
extern int fleas;
...

这里所有文件都使用了file1.cpp中定义的cats和dogs, 但file2.cpp中没有重新声明变量fleas因此无法访问fleas.

举一个例子:

// external.cpp
// compile with support.cpp
#include <iostream>
using namespace std;

// 定义, 外部链接性变量
double warming = 0.3;

// 函数原型
void update(double dt);
void local();

int main()
{
    cout << "Global warming is " << warming <<endl;
    update(0.1);
    cout << "Global warming is " << warming << endl;
    local();
    cout << "Global warming is " << warming << endl;
    return 0;
}
// support.cpp 使用外部链接性变量
// compile with external.cpp
#include <iostream>
using namespace std;

// 引用warming
extern double warming;

// 函数原型
void update(double dt);
void local();

void update(double dt)
{
    // 重新引用了变量warming
    extern double warming;
    warming += dt;
    cout << "Updating gloabl warming to " << warming << endl;
}

void local()
{
    // 定义了局部变量warming, 会覆盖全局变量warming
    double warming = 0.8;
    cout << "Local warming" << warming << endl;
    // 作用域解析运算符::, 放在变量名前的时候, 该运算符表示使用变量的全局版本
    cout << "But global warming" << ::warming << endl;
}

程序运行结果为:

值得注意的地方都写注释了

静态持续性和内部链接性

// file1
// 定义一个外部链接性的全局变量
int errors = 20;

// file2
// compile with file1
// 定义了一个内部链接性变量
static int errors = 5;
void fun()
{
    // 5, file2里的errors把全局的errors给覆盖掉了
    cout << errors;
}

看一个demo解释了, 外部链接性和内部链接性变量的使用方法

// file1.cpp
#include <iostream>
using namespace std;

// 定义外部链接性变量
int tom = 3;
int dick = 30;
// 定义内部链接性静态变量, 覆盖file2中的harry
static int harry = 300;

// 函数原型
void remote_access();

int main()
{
    cout << "main() reports the following addresses: " << endl;
    cout << "&tom = " << &tom << ", &dick = " << &dick << ", &harry = " << &harry << endl;
    remote_access();
    return 0;
}
// file2.cpp
#include <iostream>
using namespace std;

// 引用变量tom, 和file1中的tom相同
extern int tom;
// 定义外部链接性变量harry
int harry = 30;
// 定义内部链接性静态变量dick, 覆盖file1中的dick
static int dick = 300;


void remote_access()
{
    cout << "remote_access() reports the following addresses: " << endl;
    cout << "&tom = " << &tom << ", &dick = " << &dick << ", &harry = " << &harry << endl;
}

程序运行结果为:

 

静态存储持续性, 无连接

静态局部变量的值将保持不变, 静态变量适用于再生, 如果初始化了静态局部变量, 则程序只在启动时进行一次初始化, 以后再调用函数时, 将使用的是上次的值, 而不会重新初始化.

看个例子:

// static.cpp
// 静态无连接demo
#include <iostream>
using namespace std;

const int ArSize = 10;
void strcount(const char* str);

int main()
{
    char input[ArSize];
    char next;
    
    cout << "Enter a line: " << endl;
    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): " << endl;
        cin.get(input, ArSize);
    }
    cout << "Bye" << endl;
    return 0;
}

void strcount(const char* str)
{
    // 只会初始化一次
    static int total = 0;
    // 每次调用函数 count都是0
    int count = 0;
    cout << str << " contains : " << endl;
    while(*str++)
        count++;
    total += count;
    cout << count << " characters" << endl;
    cout << total << " characters total" << endl;
}

程序运行结果为:

可以看出来static定义的局部变量total的值是不会每次都初始化为0的, 至于为什么第一次输入了超过10个字符而只使用了9个字符, 是因为cin.get(input, ArSize); 说明只接受10个字符(包含结尾的空字符)

猜你喜欢

转载自blog.csdn.net/c1392851600/article/details/84844662