相关概念:
1.自动存储持续性:声明的变量的存储持续性是自动的,在开始执行其函数或代码块时被创建,执行完毕后他们使用的内存被释放。2种存储持续性为自动变量
2.静态存储持续性:在函数外定义变量或使用关键字static定义的变量。在程序整个运行过程中都存在。3种存储持续性为静态的变量。
3.线程存储持续性:多核处理器,CPU同时执行多个任务。暂时不学。
4.动态存储持续性:用new运算符分配的内存将一直存在,直到delete将其删除。存储在heap中。
5.作用域:简单来说就是valid的范围,分为局部作用域和全局作用域。
6.链接性(linkage):名称如何在不同单元间共享。链接性为外部的名称可在文件间共享,为内部的名称只能有一个文件中的函数共享。自动变量名称无linkage。
1. 自动存储持续性:
1.默认情况下,函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有linkage。若有相同的名称且作用域范围并不全不相同,则有最近代码块优先原则。新定义的名称将隐藏以前的定义。但是跳出新定义名称的作用域之后,以前定义的名称又可以使用。两个定义互相独立
2.自动变量和栈
栈的默认长度取决于实现,程序使用两个指针来跟踪栈,一个指针指向栈底,一个指向栈顶(下一个可用单元)。当函数被调用时,自动变量加入到栈,栈顶指针指向变量后面的下一个可用内存单元。函数结束时,栈顶指针被重置为函数被调用前的值。注意新值没有被删除,只是不再被标记。下次使用这个空间的时候他们会被覆盖。
3.寄存器变量
关键字register,建议编译器使用cpu寄存器来存储自动变量,旨在提高访问变量的速度。
//关于自动变量相同名称和作用域
int num=10;
{
cout<<"1. num is "<<num<<endl;
int num=5, num2=9;
cout<<"2. num is "<<num<<endl
<<" num2 is "<<num2<<endl;
}
cout<<"3. num is "<<num<<endl;
//此时若打印cout<<num2<<endl;将发生错误
return 0;
}
输出结果为:
在代码块内部,新定义的变量覆盖了原来定义的变量,但是执行完代码块跳出,原来定义的变量又上位了,而且不能使用num2了,已经被销毁了。
2. 静态持续变量:
3种链接性:
- 外部链接性(可在其他文件访问),
- 内部链接性(只能在当前文件访问)
- 无链接性(只能在当前函数或代码块访问)。
由于静态变量的数目在程序运行期间是不变的,所以不需要特殊装置如栈来管理。如果没有显示初始化静态变量,系统默认设置为0.
声明方式:
1.外部链接性变量:在代码块外面声明。
2.内部链接性变量:代码块外部用static声明。
3.无链接性变量:代码块内部static声明。
4.静态变量初始化:分为常量表达式初始化(静态)和动态初始化。
①. 静态持续性、外部链接性:
1.单定义规则(One Definition Rule, ODR):变量只能有一次定义,但可能多个文件要使用同一个变量,所以C++提供了2种变量声明:一是定义声明,给变量分配内存空间;另一个是引用声明,不给变量分配内存空间。referencing declaration使用关键字extern,且不进行初始化,否则将声明为定义。
2.全局变量写出来的程序并不总是可靠。正常情况下应使用局部变量,在知晓的情况下才传递数据。但全局变量也有advantage,尤其适用于表示常量数据如十二个月等,使用const来防止篡改。
下面演示声明外部链接全局变量、动态初始化、常量表达式初始化、引用声明、外部链接性设置为内部链接性
// text1.cpp
#include<iostream>
using namespace std;
int x=3;
int mul(int num1,int num2);
int y = 10; // const-expression initialization
void show();
int main()
{
const int z = mul(x,y); // dynamic-initialization
cout<<"x is "<<x<<endl
<<"y is "<<y<<endl
<<"z is "<<z<<endl;
show();
return 0;
}
#include<iostream>
using namespace std;
extern int x; //引用声明
static int y=4; //设置为内部链接
int mul(int num1,int num2);
void show();
int mul(int num1,int num2)
{
return x*(num1+num2);
}
void show()
{
cout<<"internal y is "<<y<<endl;
}
运行结果如下:PS 函数原型在两个源代码文件中都要有,因为没有head file。
如果报错说编译不成功,试着在项目中移除文件并重新添加。
而且要注意尽量编写完成再编译。
②静态持续性、内部链接性:
将static限定符用于作用于为整个文件的变量时,该变量的链接性为内部。如果要在不同文件中使用相同的名称来表示变量(这个名称变量在其他文件已经定义为外部链接变量),则声明方法为static,指明该标识符的链接性为内部。
(声明方法上文图已写出)
③静态持续性、无链接性:
在代码块中用static限定符定义变量,将导致局部变量的存储持续性为静态。当代码块不处于活动状态时,该变量也存在。非常适用于两次函数调用之间,其值是不会变的。初始化过一次该变量,以后将不会像自动变量一样被初始化了。
#include<iostream>
using namespace std;
void func();
int main()
{
for(int i=0;i<10;i++)
func();
return 0;
}
void func()
{
static int times=0; //静态持续变量 ,无链接性
cout<<"times is now: "<<times++<<endl;
}
运行结果为3. 说明符和限定符
①存储说明符(C++11)
register、 static、 extern、 thread_local(线程)、 mutable
②cv限定符:const和volatile
volatile表示即使程序代码没有对内存单元进行修改,其值也可能发生变化【暂时不学】
③mutable(可变的)
用来指出即使结构或类的变量为const,某个成员也可以被修改,定义在被修改成员之前。
④const
全局const int x=10 equal to static int x=10;
因为假设在头文件中有如上声明,然后每个源代码文件预处理器都将头文件的内容包含到源代码文件中,如果const声明的链接性是外部的,则违反了ODR。
如果程序员希望某个常量的链接性为external,则用extern关键字来覆盖默认的内部linkage,
extern const int y=50;
4.函数和链接性:
所有函数的存储持续性都自动为静态。默认情况下,函数的链接性为外部。也可以使用static将函数的链接性设置为内部,但必须同时在原型和定义中使用该关键字。非内联函数也适用于ODR,但内联函数不受这项规则约束。
5.语言链接性(重载函数的原理)
concept:同一个名称可能对应多个函数,必须将这些函数翻译成不同的符号名称。如C++可能将spiff(int)翻译成_spiff_i,而将spiff(double,double)翻译成_spiff_d_d,这就叫做语言链接性。要使用C语言链接来查找函数或者是C++(两种链接性不同),有如下约定:
extern “C” void spiff(int);
extern void spiff(int) = extern “C++” void spiff(int) (显示指出)
6.存储方案和动态分配
由new分配适当的字节的内存将一直保存在内存中,直到使用delete运算符将其释放。但当包含该声明的语句块执行完毕时,指向该内存的指针将消失。如果希望下一个函数能使用到该内容,则必须将其地址传递或返回给该函数。但如果声明的指针为外部的,则可以一直使用。
关于new的初始化:
int *pi = new int (6); //初始化*pi为6
int *pt = new int [4] {1,2,3,4}; //初始化数组
struct where{double x, double y, double z};
where * pr = new where {1.0,2.0,3.0}; //初始化结构
delete pi;
delete [] pt; //删除数组内存
delete pr;
定位运算符:
new负责在堆heap中找到一个足以满足要求的内存块,也可以让你能够指定要使用的内存块(定位new运算符)。
要使用此特性首先要包含new头文件。
使用定位new,指定的内存是静态内存,而delete只能用于指向常规new运算符分配的heap memory,所以不能够delete定位new运算符
下面写一段代码,通过比较地址来认识常规new运算符和定位new运算符:
#include<iostream>
#include<new>
using namespace std;
const int BUF=600;
char buff [BUF]; //这是定位new运算符将要指定的地址
int main()
{
const int n=5;
int * pt1, *pt2,i;
pt1 = new int[n];
pt2 = new (buff) int[n]; //指定地址用这样的形式声明
cout<<"内存地址:\n"<<"heap address: "<<pt1
<<"; static address: "<<(void *)buff<<endl;
for(i=0;i<n;i++)
{
pt1[i] = pt2[i]= i;
cout<<pt1[i]<<" at "<<&pt1[i]<<"; ";
cout<<pt2[i]<<" at "<<&pt2[i]<<"; \n";
}
delete []pt1;
int *pt3, *pt4;
pt3 = new int[n];
pt4 = new (buff+1) int[n];
cout<<"内存地址:\n"<<"heap address: "<<pt3
<<"; static address: "<<(void *)(buff+1)<<endl;
for(i=0;i<n;i++)
{
pt3[i] = pt4[i]= i;
cout<<pt3[i]<<" at "<<&pt3[i]<<"; ";
cout<<pt4[i]<<" at "<<&pt4[i]<<"; \n";
}
delete [] pt3;
return 0;
}
运行结果如下:
可以看到heap的地址为1520结尾,指定的地址为7040结尾,每个int占4字节。再第一次分配完之后delete pt1之后,堆同样的地址又被占领。(下面是第一次分配完之后不释放pt1内存单元)
发现没有释放内存单元,heap将继续往下堆砌pile。
总结:
- 存储持续性就是表示变量在程序生存的状态,
要么执行完一个范围(代码块或函数)就见光死(Auto)
要么在此程序运行期间都坚强地活着或多个文件间自由穿梭(Static)
要么我要你有你才有,我要你亡say 拜拜(dynamic)- 作用域就是变量可以发挥作用的区域
小明星就在小区域(局部,代码块和函数)
大明星就在大区域(多个文件)- 链接性就是你能否有足够的权利去走到更远的地方
无链接性那你只能在代码块或函数内活动
内部链接性那你可以在整个程序内活动
外部链接性则可以穿梭于多个文件