C++11使用4种方案来存储数据,这4种方案的区别主要是数据保留在内存的时间,即4种存储持续性(duration)
- 自动存储
- 静态存储
- 线程存储
- 动态存储
作用域(scope):描述了名称在文件的多大范围内可见
链接性(linkage): 描述了名称在不同文件种的共享的方式
- 外部链接性
- 内部链接性
- 无链接性
自动存储(局部变量)
自动存储的变量是最简单的,就是在代码段中
int a=1
类似的这种变量,也可以使用关键词register
来显式表示他是自动存储的变量,register int a=1
。自动存储变量的作用域是在当前代码段,对于多文件程序,一个文件中的自动变量,显然是不能在其他文件中共享的,所以它的链接性是无。
简单来说,自动变量就是我们平常所说的局部变量。在早期的C语言中,使用
auto
来显式指出变量是自动的,但是现在这种方案被抛弃了,因为auto
现在是用来类型推断。
早期C语言中的register
关键词是显式指出,变量是寄存器变量,当然这种方案也被抛弃了,现在register
是显式指出变量是自动存储的。
静态存储
概念
自动存储变量(局部变量)都是无链接性的,静态存储变量是有3种链接性的。
我们知道,局部变量是存储在栈里的,而静态变量不一样,有一个固定的内存块来存储所有静态变量。简单来说,外部静态变量,必须在代码块外面声明他,内部静态变量也必须在代码块外部声明他,但要使用
static
限定符,无链接性静态变量,必须在代码段内部声明,也要使用static
限定符。
举个栗子:
int global;//static duration , external linkage |
这里
global
是外部静态变量,它的作用域是从声明位置开始到文件末尾,它的链接性是外部链接性,也就是说,其他文件(没有包含上述代码的文件)也可以使用global
变量。
这里one_file
是内部静态变量,它的作用域是从声明位置开始到文件末尾,它的链接性是内部链接性,也就是说,只有当前文件(包含上述代码的文件)可以使用one_file
变量。
这里count
是无链接性静态变量,他的作用域是fun1函数,没有链接性,也就是说只有在fun1中能使用这个变量,这里特别说一点,无链接性静态变量和自动变量的区别是,自动变量llama
只有在执行fun1时才会加载到内存,而count
就算fun1没有执行,count
也会存在内存中,(在编译阶段,已经把count
加载到内存中了。)静态变量的零初始化:未被初始化的静态变量的所有位都被设置成0,这里变量
global
实际上会被初始化成0。这里额外说一点,有趣的是,在代码块外部,
static
用来表示内部链接性,而在代码块内部,static
用来表示变量的存储持续性是静态的。这也可以套用重载的概念,我们称之为关键字重载。
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
---|---|---|---|---|
自动 | 自动 | 代码块 | 无 | 在代码块中 |
静态,无链接性 | 静态 | 代码块 | 无 | 在代码块中,使用static |
静态,外部链接性 | 静态 | 文件 | 外部 | 不在任何函数内 |
静态,内部链接性 | 静态 | 文件 | 内部 | 不在任何函数内,使用static |
静态,外部链接性(全局变量)
由于只有静态变量才有外部链接性,所以静态外部变量简称外部变量,也就是我们常说的全局变量。
记住,全局变量特指,静态的,外部链接性的变量。
…局部变量特指 自动变量。
- 单定义规则(ODR):变量只能有一次定义。
这里,我必须区分一下定义和声明的概念了。
定义:给变量分配存储空间。
声明:或者叫做引用声明:不给变量分配存储空间,因为它引用的是已有的变量。引用声明使用关键词
extern
,他不进行初始化;如果给extern声明的变量进行初始化,则我们定义了一个全局变量 。一般来说,我们不会区分定义和声明,只是涉及全局变量时,我们指的声明特指引用声明。
//file1.cpp |
这里
file1.cpp
中的cat dog flea
都是全局变量,而且都是定义。而在file2.cpp
中cat dog
都是引用声明。而file2.cpp
中没有声明flea
,所以无法使用file1.cpp
中的flea
。
//file1.cpp |
上面就会报错,因为全局变量
cat
被定义了2次。
但是,请认真看,我稍微做一个改动
//file1.cpp |
上面这段代码,不会报错,很奇怪是不是?
在C++11以后,我们规定const全局变量的链接性是内部的,其实这里cat
不再是全局变量了,它等价于static const int cat=20;
懂不懂这段代码的含金量啊?
我们常常把常量定义在头文件里面,如果两个源文件同时包含同一个头文件,那么就不会报重复定义错误了。
当然了,如果你就是想要外部链接性的const全局变量,你可以这样定义extern const int cat=20;
显式指出它是外部链接性的。
单定义规则,并不是意味著不能有多个同名变量,它是说,同一个变量只能有一个定义,也就是说同一个变量只能有一个地址。
例如,在不同函数中定义的同名局部变量,他们都有独自的地址,而且局部变量会隐藏全局变量。这里就不细说了。
静态,内部链接性(静态全局变量)
//file1.cpp |
上面这段不会报错,因为关键字
static
说明cat
的链接性是内部的,所以说file2.cpp
中的cat
和file1.cpp
中的cat
不是同一个变量。
注意:在多文件程序中,可以且只能在一个文件中定义一个外部变量。使用该变量的其他文件只能用extern
来声明它
外部变量是用来多文件程序中共享数据的;内部链接的静态变量是用来在同一个文件中的多个函数共享数据的。
静态,无链接性(静态局部变量)
静态,无连接性的变量也称为静态局部变量,顾名思义,如果想让一个局部变量变成静态的,那就加一个关键词
static
。
值得注意的是,如果初始化了静态局部变量,则程序在启动时进行一次初始化。以后再调用函数时,将不会像自动变量那样再次被初始化。
|
enter a line: |
简单看一下上面这个程序,这个程序就是计算输入字符串的长度的。
这里cin.get(input,ArSize);
就是一直读入,直到到达换行符或者读取ArSize-1
个字符为止,他会把换行符留在输入队列里面。所以cin.get(next);
就是把换行符或者余下的字符吸收掉。然后一个循环吸收掉输入队列里的所有字符。而且cin.get(char*,int)
读取空行会发生错误。
不过这个程序最重要的是,这里的total
变量是静态局部变量,每次调用strcount()
函数,total
都不会重新初始化,而是保持上一次的值。
函数的链接性
所有函数的持续性都是静态的,函数默认的链接性是外部的,但是也可以使用关键词
static
将函数的链接性设置成内部的,使之只能在一个文件中使用。
static int private(int); |
这就意味着这个函数只能在包含这段代码的文件中使用,那么你在其他文件中可以定义同名的特征相同的函数。
类似的,和变量一样,静态内部函数会覆盖外部函数。
单定义规则也适用于非内联函数,对于每个非内联函数,程序只能包含一个定义。
由于内联函数不受单定义规则,所以我们可以把内联函数的定义放在头文件中,这一点和const全局变量是相似的。
动态存储
实际上,如果你学过C语言,前面这些内容都应当知道,但是C++中关于动态内存的操作不推荐使用C语言的malloc
和free
。
动态内存由
delete
和new
控制,我们不会去谈他的作用域,链接性,和持续性。
虽然作用域,链接性,和持续性不再适合描述动态内存,但是适用于用来追踪动态内存的指针。
编译器使用三块独立的内存:栈(stack)存局部变量,堆(heap)存动态分配内存,静态内存区用来存静态变量。当然了,进程的在内存中的模型的详细知识这边不多说了。
//file1.cpp |
可以看出变量
p
是个全局变量,所以它的链接性是外部的。
new的使用
实际上,可以直接用new
初始化动态分配变量。
int *p=new int (6); |
可以看出来,这里在分配内存的同时,还进行了初始化,这里小括号可以换成大括号。
struct where{double x;double y;double z;}; |
对于结构体和数组的初始化用大括号。如果是对动态数组的内存释放得使用
delete[]
。
当new失败时,会返回空指针。
定位new的使用
|
the address of buffer: 0x408040 |
我们使用定位
new
的目的是,有时候我们想要指定某个地址,分配一个内存。正如上面这段代码一样,我们开辟了一个512B的buffer
,然后我们希望在其中存储数据。
上面那段代码没啥好说的,注意的是,我们不能用delete[] pd2
或delete[] pd3
,因为这个buffer
是全局变量,所以pd2 pd3
是静态内存区的,而delete
只能适用于这样的指针: 指向常规new
运算符分配的堆内存。
线程存储
关键字是thread_local
它也可以与static
和extern
结合,线程变量的持续性和与其所属线程的持续性相同。
- cv限定符
cv限定符是指:
const
和volatile
,const
就不说了。
Volatile关键字的作用主要有如下两个:
- 线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
- 顺序一致性:禁止指令重排序。
volatile
修饰的变量会直接从主存中找他的值,而不是利用缓存技术,读取缓存的值。
mutable
这个限定符的作用是:即使在
const
结构体中的,其某个成员也可以被修改。
struct apple{ |
上述代码是没有问题的,这里虽然
x
是const
变量,但是其成员x.b
是nutable
修饰的,所以可以更改,但是x.a
是不允许修改的。