一、前言
相对于从名称定义全局变量、静态变量、const常量去了解他们,我们不如从本质上去区分他们。也就是从另一个角度看待这些变量或常量的区别。
对于C++中的变量而言,它有三种特性,存储持续性、作用域、以及链接性。
其中存储连续性描述的是变量的生命周期,作用域和连接性描述的是变量的可见和可使用的范围,作用域一般针对于文件内部而言,链接性则是针对于文件与文件之间而言。
显然,变量的生命周期和变量的可使用范围是有关系的,所以这就是我们在区分他们名称的时候会有困惑的原因。
另外,为了更好的了解下面的内容,这里先介绍一下C++的单定义规则(One Definition Rule), 该规则指出,变量只能有一次定义,一种是定义声明(defining declaration),简称定义,他给变量分配存储空间,另外一种使引用声明(referencing declaration),简称声明,它不给变量存储空间,因为它引用已有的变量。
二、存储持续性
类型:
(1)自动存储持续性: 在函数定义中声明的变量-包括函数参数-具有自动存储持续性。 它们是在程序执行进入它们的函数或块时创建的 当执行离开函数或块时,为它们使用的内存被释放。 其中C++有两种自动存储持续性的变量。
(2)静态存储持续性: 在函数定义之外或使用关键字static定义的变量具有静态存储持续时间。 它们一直持续到程序运行的整个时间。 C++有三种静态持续性变量。
(3)线程存储持续性:如果变量使用了关键字thread_local,那么它的声明周期就跟线程的声明周期一样长。
(4)动态存储持续性:用new运算发分配的内存一直存在,知道使用delete运算符将其释放或者程序结束为止。
我们接触到最多的就是通过static关键字来定义变量的静态存储持续性,也就是使用了static关键字的变量,他们的生命周期将持续到程序结束。但值得注意的是,static关键字不仅仅改变了变量的存储持续性,也能影响链接性,详细的介绍见下。
三、作用域
作用域(scope)描述了名称在文件(也称为翻译单元,因为各文件是单独编译的)的作用范围。
类型:
(1) 局部作用域:只在定义它的代码块中可用。
(2)全局作用域,在定义位置到文件结尾都可用。
所以我们说全局变量、局部变量,描述的是作用域。一般来讲,在没有添加static关键字的情形下,全局变量具有静态存储持续性,而局部变量具有自动存储持续性。当人为添加static关键字后,无论是全局变量还是局部变量,都具有静态存储持续性。
四、链接性
我们刚刚说到,static关键字不仅影响存储持续性,此外它还跟const一起会影响链接性,它们将变量的链接性分成了三类。
类型:
(1)外部链接性,可以被其他文件使用,持有该特性的变量:不加static且不加const的的全局变量
外部链接如何使用:
// a.cpp
int global = 1; //定义声明,简称定义
//b.cpp
extern int global; //引用声明,简称声明
在a.cpp中我们定义了全局变量global,根据其链接性我们可以在b中使用它,但是要加上extern表示该变量来自外部文件,实际上global变量在内存中只有一个。
同时不能做赋值操作,只能是声明,如果b.cpp中语句为extern int global=1,那么则会再定义一个global变量,为其分配内存空间,此时,名字为global的变量在内存中有两个,一个在a中,一个在b中。
(2)内部链接性,只能在当前文件使用,持有该特性的变量:加了static的全局变量,或者加了const的全局变量
(3)无链接性, 只能带当前文件的局部使用,持有该特性的变量:加了static的局部变量
const关键字也能使全局变量变成内部链接性的变量(这是在C++中加上去的),如果我们想让const类型的全局变量链接性变成外部的,那么可以加入extern关键字,下面的例子就是a定义外部链接性的const常量states,b则是通过外部链接引用了a中的states。(static 前不能这样加extern实现这样的效果,会报错)
//a.cpp
extern const int states=50; //定义声明,简称定义
//b.cpp
extern const int states; //引用声明,简称声明
五、汇总
声明方式 | 持续性 | 作用域 | 链接性 | 存储描述 |
在代码块中 | 自动 | 局部(代码块) | - | 自动 |
在代码块中,使用static | 静态 | 局部(代码块) | 无 | 静态,无链接性 |
不在任何函数内 | 静态 | 全局(文件) | 外部 | 静态,外部链接性 |
不再任何函数内,使用static | 静态 | 全局(文件) | 内部 | 静态,内部链接性 |
不再任何函数内,使用const | 静态 | 全局(文件) | 内部 | 静态,内部链接性 |
六、其他说明符和限定符
在上面我们了解了了static说明符和extern说明符, 下面看看其他常用的说明符
说明符和限定符 | 作用 |
auto | 表示自动类型推断 auto a = 1.414;, 这时auto相当于float |
register | 指示系统尽可能在寄存器中存储该变量, register int a = 1; |
static | 指示静态存储持续性,并指示全局变量只能内部链接 |
extern | 表明是引用声明,即声明引用在其他地方定义的变量 |
thread_local | 指出变量的持续性于其所属线程的持续性相同 |
const (限定符) | 他表明内存被初始化后,程序便不能再对他进行修改。并指示全局变量只能内部链接。 |
volatile(限定符) | 跟java中一样,表明该变量可能会被两个线程修改或使用,volatile是易变和不稳定的意思。我们知道,如果程序在几条语句两次使用了某个变量的值,则编译器可能不是让程序查找这个值两次,而是将这个值缓存到寄存器(做了优化)。这种优化假设变量的值在这两次使用之间不会变化。而现实是有可能另外一个线程或者进程已经把它修改了,所以volatile就是告诉编译器不要做这种优化。 |
mutable | 它指示即使结构或者类变量为const,其某个成员可以被修改,用来修饰可以被修改的成员。 |
七、static的在类中的使用
在类中我们一般对类的成员函数加上static关键字,说明该函数时静态函数,它实际上也是指示静态存储持续性,就是这个类函数的生命周期等同于程序的生命周期,即使不创建类对象,只要加载了类,就可以对该函数进行访问。(当然,但我们对成员变量加上static的时候,也是一样的效果)。
同时需要注意的是static的函数只能访问static的变量或函数,因为其他非static的成员隶属于实例化了的类对象,当对象不存在时,这些成员也没有被分配内存,所以就设置让static的函数不能访问非static的类成员。
八、参考资料
C++ primer plus