C++PrimerPlus 学习笔记 | 第九章 内存模型和命名空间|2.2 静态存储持续型变量 & 2.3 外部链接性 & 2.4 内部链接性 & 2.5 无链接型

静态持续变量

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

  1. 想要创建链接性为外部的静态持续变量,必须要在代码块外部声明他。

  2. 想要创建链接性为外部的静态持续变量,必须要在代码块外部声明他,并添加static修饰词

  3. 要创建没有链接性的静态持续变量,必须在代码块内部声明他,并添加static修饰词

int a = 1; // 链接性为外部的静态持续变量
static int b = 1; // 链接性为内部的静态持续变量
int main(){
	static int c = 1; // 无链接性的静态持续变量
	return 0;
}

上述所有的静态持续变量(a,b,c)在整个程序运行期间都存在。但是在main函数中声明的变量为无链接性的静态持续变量,也就说只能在main内部访问和修改该值。a没有被static修饰又处于外部术语外部链接性的静态持续变量,可在所有文件的访问,而变量b处于代码块外部但是被static修饰,属于内部连续性的静态持续变量,只能在该文件中使用。

所有未初始化的静态持续变量的所有位都被设置为0,这种变量被称为0初始化的


自动和静态持续性变量总结

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

我们会发现static在不同场景的意义并不相同,有人称之为关键字重载,即关键字意义取自于上下文。


静态变量的初始化

除了默认的零初始化外,还可以对静态变量进行常量表达式初始化动态初始化

其中常量表达式初始化和零初始化统称为静态初始化,这意味者在编译器处理文件的时候初始化变量(编译时初始化),动态初始化意味者将在编译后初始化。

那么编译器是如何执行初始化的呢,分为下面几步

  1. 所有的静态变量都被分配内存和零初始化(无论是否被显示初始化)
  2. 如果静态变量使用了常量表达式初始化了变量,且编译器仅根据文件内容(包括被包含的头文件)就可以计算表达式,编译器将执行常量表达式初始化,必要时编译器将进行简单运算,如果没有足量信息,变量将被动态初始化。
#include <cmath>
int x; // 零初始化
int y = 5; // 静态初始化
long z = 13 * 13; // 静态初始化
const double pi = 4.0 * atan(1.0); // 动态初始化

pi因为设计到函数调用所以必须等到编译结束,程序链接后,程序运行时才能进行初始化。

静态持续性,外部链接性

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

1. 单定义规则

一方面,在每个使用外部变量的文件,都必须声明他(一般由头文件#include完成),另一方面C++有单定义规则该规则指出变量只能有一次定义,为了满足这种需求,C++提供了两种变量声明,一个是定义声明简称为定义,该操作将为变量分配内存空间,另外一种是引用声明简称为声明,他不给变量分配空间,因为他引用已有的变量

不难看出,单定义规则就指的是只能为同一个变量分配一次内存

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


实例1

double up; // 定义 up初始化为0
extern int blem; // 声明 不分配内存 blem将在别处分配内存
extern char gr = 'z'; // 定义 被初始化需要分配内存空间 

实例2

// file1.cpp
extern int cats = 20; // 定义 被初始化需要分配内存空间 == int cats = 20;
int dogs = 22; // 定义
int fleas; // 定义
// file2.cpp
extern int cats; // 声明从1处引用cats
extern int dogs; // 声明从1处引用dogs;
// file3
extern int cats; // 声明从1处引用cats
extern int dogs; // 声明从1处引用dogs;
extern int fleas;// 声明从1处引用fleas;

在2和3都用声明引用了cats和dogs变量,但是2没有声明fleas因此不可使用,其中1的第一句可以省略extern,效果相同


请注意单定义规则并不意味着变量名不能相同,例如在不同代码块声明的同名自动变量是彼此独立的,它们有自己的地址,另外局部变量可能隐藏同名的全局变量。程序中可包含多个同名的变量,但是每个变量都包含自己的定义。

在同一个作用域中(不包含其内部的子作用域),只能有一个变量定义

如果在函数中声明了与外部同名的变量,这种声明将会被视为一个自动变量的定义,当程序执行到自动变量所属的函数时,该变量将位于作用域中。

来看实例

// main.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
#include <iostream>
using namespace std;

// 变量声明
extern double warming;

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

void update(double bt){
    extern double warming; // 这个声明时可选的,目的是指出使用的外部的warming
    warming += bt; // ::warming
    cout << "Update global warming to " << warming << endl; // ::warming
}

void local(){
    double warming = 0.8; // 
    cout << "local warming = " << warming << endl;
    cout << "but global warming is " << ::warming << endl;
}

定义与全局变量相同的变量名后局部变量将隐藏全局变量,例如函数定义局部变量warming后,当local函数使用warming时将使用局部版本。

C++比C语言更近一步,它提供了作用域解析运算符号(:😃,放在变量名前面,该运算符号表示使用变量的全局版本。

清晰和避免的错误的角度来说相对于使用 warming并依赖于作用域规则,在函数update中使用::warming是更好更安全的选择->使用全局变量:使用 :: 表示


全局变量和局部变量

既然可以使用全局变量和局部变量,选哪种呢?-> 首先全局变量很有吸引力,因为所有的函数都可以访问全局变量,不用传递参数,但是易于访问的代价很大 — 程序不可靠,计算经验表明程序越能避免对于数据的不必要的访问,就越能保证数据的完整性。通常情况下应当使用局部变量,应当在需要知晓时传递数据,而不是不加区分的使用全局变量,后面我们可以看到OOP在数据隔离方面又迈进一步

然而全局变量并不是一无是处,例如可以让多个函数使用同一块数据,外部数据尤其适于表示常量数据,因为这样可以增加const修饰符,以防数据被修改。

总的来说,应当在合理的范围内缩小变量的作用域,为变量增加const修饰符

静态持续性,内部链接性

将static限定符用于作用域为整个文件的变量时,该变量的链接性将变为内部的,在多文件程序中内部链接性和外部链接性之间的差别很有意义,连接性为内部的变量只能在所属文件中使用,而连接性为外部的变量可以在所有文件中使用。

这边需要注意的是如果在一个文件中定义了全局变量a,在另一个文件如果没有进行声明是不能使用,也不能再声明一个全局变量a.

也就是说下述代码将不会通过编译

// file1
int errors = 20;
// file2
int errors = 5;
void froobish(){
    cout << errors;
}

这个代码将会编译失败,他违反了单定义原则,记住外部链接性的作用域是所有文件(即使可能某个文件没有声明导致无法访问,但是作用域仍然覆盖),所以即使在不同文件中声明两个同名的全局变量,仍然会导致在同一个作用域(除去子作用域)声明同名的变量导致冲突,违反单定义原则

简而言之在多文件程序中,可以在一个文件中(也仅能在一个文件中)中定义一个外部变量,使用该变量的其他文件必须(也只能)通过extern声明他

可使用外部变量在多文件程序中的不同部分共享数据,可使用链接性为内部的静态变量在同一个文件中的多个函数之间共享数据(命名空间提供另外一种方式来共享数据),另外如果将作用域为整个文件的变量修饰为静态的,就不必担心名称与其他文件中作用域为整个文件的变量发生冲突。

我们可以这样理解,在不引入命名空间的前提下,所有文件之间是最大的作用域,声明的不带static修饰的不属于任何代码块的变量就属于该作用域,而带static修饰的不属于任何代码块的属于单个文件作用域,由于带static修饰的变量的作用域属于所有文件之间作用域的子集,相对于所有文件来说属于局部变量,所以不会发现名称冲突。

静态存储持续性,无链接性

如何创建该类型的变量:使用static修饰符修饰在代码块中代码如下所示

static int a; // 不是,该代码不处于代码块中
int main(){
    int a; // 不是,该语句不被static修饰
    static char b; // 是的该语句处于代码块中且被static修饰
}

在代码块中使用static时会导致局部变量的持续性变为静态的,这就意味着虽然该变量只能在该代码块中可用,但是他在代码块不活跃的时候持续存在(他将会在整个程序进程中一直存在,无论是否活跃,且被初始化)。因此在两次函数之间,变量值可以保持不变。另外静态变量只会在程序启动时被初始化(如果程序员没有显示初始化,编译器将会自动将其零初始化),而不是每次程序调用都被初始化

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值