【C++】学习笔记四十五——存储持续性、作用域、链接性

存储持续性

  • 自动存储持续性:在函数定义中声明的变量,作用域为局部,没有链接性
  • 静态存储持续性:在函数外部定义的变量和使用static定义的变量
  • 线程存储持续性:使用thread_local声明的变量
  • 动态存储持续性:用new分配的内存

静态持续变量

有三种链接性:

  • 外部链接性(可在其他文件中访问):不在任何函数内声明
  • 内部链接性(只能在当前文件中访问):不在任何函数内,用static声明
  • 无链接性(只能在当前函数或代码块中访问):在代码块中,用static声明

静态持续变量默认为0。

变量只能有一次定义(单定义规则),但在每个使用外部变量的文件中都必须声明它。因此有两种变量声明:定义声明和引用声明。
引用声明使用extern,且不进行初始化;否则,声明为定义,会分配内存空间。

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

如果在函数中生命了一个与外部变量同名的变量,将被视为一个自动变量的定义。

程序9.5

#include <iostream>
using namespace std;
//external variable
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";
    system("pause");
    return 0;
}

程序9.6

#include <iostream>
extern double warming;

void update(double dt);
void local();

using std::cout;
void update(double dt)
{
    extern double warming;
    warming += dt;
    cout << "Updating global warming to " << warming;
    cout << " degrees.\n";
}

void local()
{
    double warming = 0.8;
    cout << "Local warming  " << warming << " degrees.\n";

    cout << "But global warming = " << ::warming;
    cout << " degrees.";
}

这里写图片描述

程序9.5和9.6的出输出表明,main()和update()都可访问warming,update()修改了warming。
local()函数表明定义与全局变量同名的局部变量后,局部变量将隐藏全局变量。

C++提供了作用域解析符(::),放在变量名前,表示使用变量的全局版本。

如果要在其他文件中使用相同的名称来表示其他变量,只省略extern是不够的,还需要将一个文件中的变量定义为静态外部变量(static)。

在多文件程序中,可以在一个文件(且只能在一个文件)中定义一个外部变量,使用该变量的其他文件必须使用关键字extern声明。

无链接性的局部变量:将static限定符用于代码块中定义的变量。这将导致局部变量的存储持续性为静态的,虽然该变量只在代码块中可用,但他在该代码块不处于活动状态时仍然存在。因此在两次函数调用之间,静态局部变量的值将保持不变。

程序9.9

#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";
    system("pause");
    return 0;
}

void strcount(const char * str)
{
    using namespace std;
    static int total = 0;       //static local variable
    int count = 0;

    cout << "\"" << str << "\" contains ";
    while (*str++)
        count++;
    total += count; 
    cout << count << " characters\n";
    cout << total << " characters total\n";
}

这里写图片描述

该程序演示了一种处理行输入可能长于目标数组的方法。方法cin.get(input,ArSize)将一直读取输入,直到到达行尾或读取了ArSize-1个字符为止。它把换行符留在输入队列中。该程序使用cin.get(next)读取行输入后的字符。如果next是换行符,表明cin.get(input,ArSize)读取了整行;否则说明还有字符没有被读取。随后利用一个循环来丢弃余下的字符。另外,该程序说明用get(char *,int)读取空行将导致cin为false。

每次函数被调用时,自动变量count都被重置为0;然而静态变量total只在程序运行时被设置为0,之后每次调用,其值都保持不变。

说明符和限定符

存储说明符

  • auto(在C++11中不再是说明符,而用于自动类型推断)
  • register(在声明中指示寄存器存储)
  • static
  • extern
  • thread_local(C++11新增的)
  • mutable

cv-限定符

  • const
  • volatile

volatile作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。简单地说就是防止编译器对代码进行优化。

mutable
mutable用来指出,即使结构或类变量为const,其某个成员也可以被修改。

struct data
{
    char name[30];
    mutable int accesses;
    ...
};
const data veep = {"Claybourne Clodde", 0, ...};
strcpy(veep.name,"Joye Joux"};   //不允许
veep.accesses++;         //允许

在C++中,const对默认存储类型有影响:在默认情况下全局变量的链接性为外部的,但const全局变量的链接性为内部的。就像使用了static一样。
如果出于某种原因,希望某个常量的链接性为外部的,则可以使用extern来覆盖默认的内部链接性:

extern const int states = 50;

函数的链接性

函数也有链接性,默认的链接性是外部的,即可以在文件件共享。实际上,可以在函数原型中使用extern来指出函数实在另一个文件中定义的,不过这是可选的。

还可以使用static将函数的链接性设置为内部的,使之只能在一个文件中使用,必须同时在原型和定义中使用static。

不允许在一个函数中定义另一个函数,因此所有函数的存储持续性都自动为静态的,即在整个程序执行期间都一直存在。

动态分配

使用new来分配动态内存,使用delete来释放内存,而不由作用域和链接性规则控制。因此,可以在一个函数中分配动态内存,而在另一个函数中将其释放。
使用new初始化
C++98:

int *pi = new int (6);    //*pi为6
double *pd = new double (99.99);   // *pd为99.99

C++11:
列表初始化

struct where {double x; double y;double z;};
where * one =new where {2.5, 5.3, 7.2};
int * ar = new int [4] {2, 4, 6, 7};

int *pin = new int {6};
double *pdo = new double {99.99};

“定位new”(placement new)运算符
通常new在堆(heap)中寻找一个足以满足要求的内存块。
定位new运算符可以指定要使用的位置,使用定位new,需要包含头文件new。使用定位new时,变量后边可以有方括号,也可以没有。

#include <new>
struct chaff
{
    char dross[20];
    int slag;
};
char buffer1[50];
char buffer2[500];
int main()
{
    chaff *p1, *p2;
    int *p3, *p4;
    //常规new
    p1 = new chaff;
    p3 = new int [20];
    //定位new
    p2 = new (buffer1) chaff;    //将结构放置于buffer1
    p4 = new (buffer2) int [20]; //将int 数组放置于buffer2
    ...
}

程序9.10

#include <iostream>
#include <new>
const int BUF = 512;
const int N = 5;
char buffer[BUF];
int main()
{
    using namespace std;
    double *pd1, *pd2;
    int i;
    cout << "Calling new and placement new:\n";
    pd1 = new double[N];
    pd2 = new (buffer) double[N];
    for (i = 0; i < N; i++)
        pd2[i] = pd1[i] = 1000 + 20.0*i;
    cout << "Memory addresses:\n" << " heap: " << pd1
        << " static: " << (void *)buffer << endl;
    cout << "Memory contents:\n";
    for (i = 0; i < N; i++)
    {
        cout << pd1[i] << " at " << &pd1[i] << ";";
        cout << pd2[i] << " at " << &pd2[i] << endl;
    }

    cout << "\nCalling new and placement new a second time:\n";
    double *pd3, *pd4;
    pd3 = new double[N];      //find new address
    pd4 = new (buffer) double[N];  //overwrite old data
    for (i = 0; i < N; i++)
        pd4[i] = pd3[i] = 1000 + 40.0*i;
    cout << "Memory contents:\n";
    for (i = 0; i < N; i++)
    {
        cout << pd3[i] << " at " << &pd3[i] << ";";
        cout << pd4[i] << " at " << &pd4[i] << endl;
    }

    cout << "\nCalling new and placement new a third time:\n";
    delete[] pd1;
    pd1 = new double[N];
    pd2 = new (buffer + N * sizeof(double)) double[N];
    for (i = 0; i < N; i++)
        pd2[i] = pd1[i] = 1000 + 60.0*i;
    cout << "Memory contents:\n";
    for (i = 0; i < N; i++)
    {
        cout << pd1[i] << " at " << &pd1[i] << ";";
        cout << pd2[i] << " at " << &pd2[i] << endl;
    }
    delete[] pd1;
    delete[] pd3;
    system("pause");
    return 0;
}

这里写图片描述

该程序没有使用delete来释放定位new运算符分配的内存,事实上,这个例子不能这样做。因为buffer指定的内存是静态内存,而delete只能用于指向常规new运算符分配的堆内存。也就是说,buffer位于delete的管辖范围之外。
如果buffer是使用常规new创建的,则可以使用delete。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值