1. 存储持续性
- 自动存储持续性:在函数中定义或声明的变量存储持续性为自动的,它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,使用的内存被释放;
- 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量存储持续性为静态,在程序整个运行过程中都存在
- 动态存储持续性:用new运算符分配的内存,直到delete运算符释放或程序结束为止一直存在。
2. 作用域和链接
作用域描述文件在多大范围内可见。
- 作用域为局部的变量只在定义它的代码块(花括号括起来的一系列语句,如函数体)中可用。
- 作用域为全局(文件作用域)的变量在定义位置到文件结尾之间可用
- 在类中声明的变量作用域为整个类,在名称空间中声明的变量作用域为整个名称空间。
C++函数的作用域可以是整个类或整个名称空间,但不能是局部的(因为不能在代码块中定义函数)
3. 不同存储方式
3.1 自动存储持续性变量
默认情况下,函数中声明的函数参数和变量的存储持续性为自动,作用域为局部,没有链接性,即只能在函数(代码块)内访问。
自动变量的数量随着函数的运行或结束而有所增减,因此函数需要栈对自动变量进行管理。
3.2 静态存储持续性变量
C++为静态存储持续性变量提供了三种链接性:
- 外部链接性(可在其他文件中访问),创建需要在代码块外面声明
- 内部链接性(只能在当前文件中访问),需要在代码块外面声明并使用static
- 无链接性(只能在当前函数或代码块中访问),需要在代码块内声明并使用static
静态变量的数目在程序运行期间保持不变,编译器分配固定内存块存储所有静态变量,初始化为0,在程序执行期间一直存在。
3.2.1 外部链接性
变量只能有一次定义,使用引用声明extern引用已有的变量,不分配存储空间。如果要在多个文件中使用外部变量,只需在一个文件中包含该变量的定义,但在使用该变量的其他所有文件中使用extern关键字进行声明。
同名的局部变量会隐藏全局变量。
在变量前使用“::”运算符可用使用该变量的全局版本。
3.2.2 内部链接性
在代码块外部使用static限定符定义变量,该变量的链接性为内部,只能在当前文件中使用。
文件中的静态变量与另一个文件中的常规外部变量同名,静态变量会隐藏常规外部变量。如下代码展示了内部链接性与外部链接性。代码位于两个文件中,通过编译链接后运行。
main.cpp:
// main.cpp
#include <iostream>
int tom = 3;
int dick = 30;
static int harry = 300;void remote_access();int main()
{using namespace std;cout << "main() reports the following addresses:" << endl;cout << &tom << " = &tom, "<<&dick <<" = &dick, ";cout << &harry << " = &harry"<<endl;remote_access();return 0;
}
twofile.cpp:
#include <iostream>
extern int tom;
static int dick = 10;
int harry = 200;void remote_access()
{using namespace std;cout <<"remote_access() reports the following addresses:"<<endl;cout << &tom << " = &tom, "<<&dick <<" = &dick, ";cout << &harry << " = &harry"<<endl;
}
使用g++在linux下进行编译,生成可执行文件:
g++ main.cpp twofile.cpp -o main
运行结果如下:
可见两个函数中使用了同样的tom变量,但使用了不同的dick变量和harry变量。
3.3.3 无链接性
在代码块中用static限定符定义变量,则变量没有链接性,只能在代码块中使用,但在该代码块不运行时依然存在,因此在两次调用函数之间变量的值保持不变。
如果初始化了静态局部变量,则只在启动时进行一次初始化,再次调用时不再会像自动变量一样进行初始化。
以下代码中展示了自动变量与静态局部变量。
#include <iostream>
const int ArSize = 10;void strcount(const char* str);int main()
{using namespace std;char input[ArSize];char next;cout <<"Enter a line"<<endl;cin.get(input, ArSize);while(cin){cin.get(next);while(next != '\n')cin.get(next);strcount(input);cout << "Enter next line (empty line to quit)"<<endl;cin.get(input, ArSize);}cout<<"Bye"<<endl;return 0;
}woid strcount(const char * str)
{using namespace std;static int total = 0;int count = 0;cout <<"\""<<str<<"\"contains";while(*str++)count ++;total += count;cout << count << " characters"<<endl;cout << total << " characters total"<<endl;
}
运行结果如下:
可见count变量每次都变化,而total变量在原有的基础上累加。
3.4 限定符
- volatile限定符
表示即使程序代码没有对内存单元进行修改,其值也可能发生变化。
编译器可能进行某种优化:假设编译器发现程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查找这个变量两次,而是将这个值缓存到寄存器中。这种优化假设两次使用之间变量的值不会变化。如果不将变量声明为volatile,则编译器将进行这种优化,声明为volatile相当于禁用这种优化。
- mutable限定符
可用用来指出,即使结构(或类)变量为const,其某个成员也可能被修改。
如下的例子:
struct data
{char name[30];mutable int accesses;……
};
const data veep = {"Claybourne Clodde", 0, ……};
strcpy(veep.name, "Joye Joux");// 不允许
veep.accesses++;//允许
veep结构被声明为const,禁止修改其成员;但声明为mutable的accesses成员可以被修改。
- const限定符
const限定符定义的全局变量具有内部链接性,因此可以包含在头文件中。被多个文件包含也不会产生冲突。
3.5 使用new分配的内存
使用new分配的动态内存不受作用域与链接性规则控制,其值取决于使用new和delete的位置。虽然存储方案概念不适用于动态内存,但适用于用来跟踪动态内存的自动和静态变量指针变量。
如下面的例子:
float * p_fees = new float[20];
由new分配的80个字节(假设float为4个字节)的内存将一直保留在内存中,直到使用delete进行释放。但当包含该声明的代码块运行完毕后,p_fees指针将消失。如果还需要使用这80个字节的内容,则需要将其地址传递或者返回。如果将p_fees声明为外部链接,则在代码块外部也可以使用该指针。