C++11使用4种方案来存储数据,这4种方案的区别主要是数据保留在内存的时间,即4种存储持续性(duration)

  1. 自动存储
  2. 静态存储
  3. 线程存储
  4. 动态存储

作用域(scope):描述了名称在文件的多大范围内可见
链接性(linkage): 描述了名称在不同文件种的共享的方式

  1. 外部链接性
  2. 内部链接性
  3. 无链接性

自动存储(局部变量)

自动存储的变量是最简单的,就是在代码段中int a=1类似的这种变量,也可以使用关键词register来显式表示他是自动存储的变量,register int a=1。自动存储变量的作用域是在当前代码段,对于多文件程序,一个文件中的自动变量,显然是不能在其他文件中共享的,所以它的链接性是无。
简单来说,自动变量就是我们平常所说的局部变量

在早期的C语言中,使用auto来显式指出变量是自动的,但是现在这种方案被抛弃了,因为auto现在是用来类型推断。
早期C语言中的register关键词是显式指出,变量是寄存器变量,当然这种方案也被抛弃了,现在register是显式指出变量是自动存储的。

静态存储

概念

自动存储变量(局部变量)都是无链接性的,静态存储变量是有3种链接性的。
我们知道,局部变量是存储在栈里的,而静态变量不一样,有一个固定的内存块来存储所有静态变量。

简单来说,外部静态变量,必须在代码块外面声明他,内部静态变量也必须在代码块外部声明他,但要使用static限定符,无链接性静态变量,必须在代码段内部声明,也要使用static限定符。
举个栗子:

int global;//static duration , external linkage
static int one_file =50;//static duration ,internal linkage
int main(){
...
}
void fun1(int n){
static int count=0;//statc duration,no linkage
int llama=0;//auto duration,no 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
extern int cat =20;
int dog=22;
int flea;
...

//file2.cpp
extern int cat;
extern int dog;
...

这里file1.cpp中的cat dog flea都是全局变量,而且都是定义。而在file2.cppcat dog都是引用声明。而file2.cpp中没有声明flea,所以无法使用file1.cpp中的flea

//file1.cpp
int cat =20;
int dog=22;
int flea;
...

//file2.cpp
extern int cat=20;
...

上面就会报错,因为全局变量cat被定义了2次。

但是,请认真看,我稍微做一个改动

//file1.cpp
const int cat =20;
int dog=22;
int flea;
...

//file2.cpp
const int cat=20;

上面这段代码,不会报错,很奇怪是不是?
在C++11以后,我们规定const全局变量的链接性是内部的,其实这里cat不再是全局变量了,它等价于static const int cat=20;
懂不懂这段代码的含金量啊?
我们常常把常量定义在头文件里面,如果两个源文件同时包含同一个头文件,那么就不会报重复定义错误了。
当然了,如果你就是想要外部链接性的const全局变量,你可以这样定义extern const int cat=20;显式指出它是外部链接性的。

单定义规则,并不是意味著不能有多个同名变量,它是说,同一个变量只能有一个定义,也就是说同一个变量只能有一个地址。
例如,在不同函数中定义的同名局部变量,他们都有独自的地址,而且局部变量会隐藏全局变量。这里就不细说了。

静态,内部链接性(静态全局变量)

//file1.cpp
extern int cat =20;
...

//file2.cpp
static int cat=20;
...

上面这段不会报错,因为关键字static说明cat的链接性是内部的,所以说file2.cpp中的catfile1.cpp中的cat不是同一个变量。

注意:在多文件程序中,可以且只能在一个文件中定义一个外部变量。使用该变量的其他文件只能用extern来声明它

外部变量是用来多文件程序中共享数据的;内部链接的静态变量是用来在同一个文件中的多个函数共享数据的。

静态,无链接性(静态局部变量)

静态,无连接性的变量也称为静态局部变量,顾名思义,如果想让一个局部变量变成静态的,那就加一个关键词static
值得注意的是,如果初始化了静态局部变量,则程序在启动时进行一次初始化。以后再调用函数时,将不会像自动变量那样再次被初始化。

#include<iostream>
const int ArSize=10;
void strcount(char*);
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 string:\n";
cin.get(input,ArSize);
}
}
void strcount(char* str){
using namespace std;
static int total=0;
int count=0;

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

}
enter a line:
hello
"hello" contains 5 characters
5 characters total
enter next string:
how are you
"how are y" contains 9 characters
14 characters total
enter next string:
ok
"ok" contains 2 characters
16 characters total
enter next string:

简单看一下上面这个程序,这个程序就是计算输入字符串的长度的。
这里cin.get(input,ArSize);就是一直读入,直到到达换行符或者读取ArSize-1个字符为止,他会把换行符留在输入队列里面。所以cin.get(next);就是把换行符或者余下的字符吸收掉。然后一个循环吸收掉输入队列里的所有字符。而且cin.get(char*,int)读取空行会发生错误。
不过这个程序最重要的是,这里的total变量是静态局部变量,每次调用strcount()函数,total都不会重新初始化,而是保持上一次的值。

函数的链接性

所有函数的持续性都是静态的,函数默认的链接性是外部的,但是也可以使用关键词static将函数的链接性设置成内部的,使之只能在一个文件中使用。

static int private(int);
static int private(int x){
...
}

这就意味着这个函数只能在包含这段代码的文件中使用,那么你在其他文件中可以定义同名的特征相同的函数。
类似的,和变量一样,静态内部函数会覆盖外部函数。
单定义规则也适用于非内联函数,对于每个非内联函数,程序只能包含一个定义。
由于内联函数不受单定义规则,所以我们可以把内联函数的定义放在头文件中,这一点和const全局变量是相似的。

动态存储

实际上,如果你学过C语言,前面这些内容都应当知道,但是C++中关于动态内存的操作不推荐使用C语言的mallocfree

动态内存由deletenew控制,我们不会去谈他的作用域,链接性,和持续性。
虽然作用域,链接性,和持续性不再适合描述动态内存,但是适用于用来追踪动态内存的指针。
编译器使用三块独立的内存:栈(stack)存局部变量,堆(heap)存动态分配内存,静态内存区用来存静态变量。当然了,进程的在内存中的模型的详细知识这边不多说了。

//file1.cpp
double* p=new double[20];

//file2.cpp
extern double* p;

可以看出变量p是个全局变量,所以它的链接性是外部的。

new的使用

实际上,可以直接用new初始化动态分配变量。

int *p=new int (6);
double *d=new double (9.99);//*d的值是9.99
delete p;
delete d;

可以看出来,这里在分配内存的同时,还进行了初始化,这里小括号可以换成大括号。

struct where{double x;double y;double z;};
where * one=new where {2.3,4.5,6.7};
int * array=new int [4] {2,3,4,5};
int *p=new int {6};
double *d=new double {9.99};//*d的值是9.99
delete one;
delete[] array;
delete p;
delete d;

对于结构体和数组的初始化用大括号。如果是对动态数组的内存释放得使用delete[]

当new失败时,会返回空指针。

定位new的使用

#include<iostream>
using namespace std;
#define N 512
char buffer[N];
int main(){
double *pd1 = new double[5] {0,1,2,3,4};
double *pd2 = new(buffer) double[5] {0,1,2,3,4};
cout<<"the address of buffer: "<<(void*)buffer<<endl;
for(int i=0;i<5;i++){
cout<<"pd1"<<"["<<i<<"]: "<<pd1[i]<<" the address: "<<&pd1[i]<<"; ";
cout<<"pd2"<<"["<<i<<"]: "<<pd2[i]<<" the address: "<<&pd2[i]<<endl;
}
double*pd3 = new(buffer+5*sizeof(double)) double[5] {5,6,7,8,9};
for(int i=0;i<5;i++){
cout<<"pd3"<<"["<<i<<"]: "<<pd3[i]<<" the address: "<<&pd3[i]<<endl;
}
delete[]pd1;

}
the address of buffer: 0x408040
pd1[0]: 0 the address: 0xd64090; pd2[0]: 0 the address: 0x408040
pd1[1]: 1 the address: 0xd64098; pd2[1]: 1 the address: 0x408048
pd1[2]: 2 the address: 0xd640a0; pd2[2]: 2 the address: 0x408050
pd1[3]: 3 the address: 0xd640a8; pd2[3]: 3 the address: 0x408058
pd1[4]: 4 the address: 0xd640b0; pd2[4]: 4 the address: 0x408060
pd3[0]: 5 the address: 0x408068
pd3[1]: 6 the address: 0x408070
pd3[2]: 7 the address: 0x408078
pd3[3]: 8 the address: 0x408080
pd3[4]: 9 the address: 0x408088

我们使用定位new的目的是,有时候我们想要指定某个地址,分配一个内存。正如上面这段代码一样,我们开辟了一个512B的buffer,然后我们希望在其中存储数据。
上面那段代码没啥好说的,注意的是,我们不能用delete[] pd2delete[] pd3,因为这个buffer是全局变量,所以pd2 pd3是静态内存区的,而delete只能适用于这样的指针: 指向常规new运算符分配的堆内存。

线程存储

关键字是thread_local它也可以与staticextern结合,线程变量的持续性和与其所属线程的持续性相同。

  • cv限定符

cv限定符是指:constvolatileconst就不说了。
Volatile关键字的作用主要有如下两个:

  1. 线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
  2. 顺序一致性:禁止指令重排序。

volatile修饰的变量会直接从主存中找他的值,而不是利用缓存技术,读取缓存的值。

  • mutable

这个限定符的作用是:即使在const结构体中的,其某个成员也可以被修改。

struct apple{
int a;
mutable int b;
};
const apple x={1,2};
x.b++;

上述代码是没有问题的,这里虽然xconst变量,但是其成员x.bnutable修饰的,所以可以更改,但是x.a是不允许修改的。