C++使用三种(在C++11中是4种)不同的方案来存储数据,这些方案的区别就在于数据保留在内存中的时间。
• 自动存储持续性:在函数定义中声明的变量(包括函数参数)的存储性为自动的。它们在程序开始执行其所属的函数或代码块时被创建,在执行完函数或代码块时,它们使用的内存被释放。C++有两种存储持续性为自动的变量。
• 静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性为静态 。它们在程序整个运行过程中都存在。C++有3种存储性为静态的变量。
• 线程存储持续性(C++11):当前,多核处理器很常见,如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。
• 动态存储持续性:用new运算符分配的内存将一直存在,直到使用delete运算符将其释放或程序结束为止。这种内存的存储持续性为动态,有时被称为自由存储或堆。
5种变量存储方式
存储描述 | 持续性 | 作用域 | 链接性 | 如何声明 |
---|---|---|---|---|
自动 | 自动 | 代码块 | 无 | 在代码块中 |
寄存器 | 自动 | 代码块 | 无 | 在代码块中,使用关键字register |
静态,无链接性 | 静态 | 代码块 | 无 | 在代码块中,使用关键字static |
静态,外部链接性 | 静态 | 文件 | 外部 | 不在任何函数内 |
静态,内部链接性 | 静态 | 文件 | 外部 | 不在任何函数内,使用关键字static |
存储说明符
• auto:在c++11之前使用auto指出变量为自动变量,但在C++11中用于自动类型推断。
• register:用于在声明中指示寄存器存储,在c++11中,它只是显示的指出变量是自动的。
• static:用在作用域为整个文件的声明中时,表示内部链接性;用在局部声明中时,表示局部变量的存储持续性为静态的。
• extern:表明是引用声明,即声明引用在其它地方定义的变量。
• thread_local:指出变量的持续性与其所属线程的持续性相同。
• mutable:如果有结构或类变量为const,但是它们有成员使用了mutable,则该成员也是可以被修改的。
cv限定符
• const;
• volatile
通常编译器使用三块独立的内存:一块用于静态变量,一块用于自动变量,另一块用于动态存储。
使用new运算符初始化
int *pi = new int (6);
double *pd = new double (99.9);
int * ar = new int [4] {2, 3, 4, 5};
常规new运算符
运算符new和new[]分别调用如下函数:
void * operator new(std::size_t);
void * operator new[] (std::size_t);
int * pi = new int;将被转换如下
int * pi = new(sizeof(int));
int * pi = new int[40];将被转换如下
int * pi = new(40 * sizeof(int));
delete和delete[]与new和new[]类似,它们都是通过运算符重载实现的。
void operator delete(void *);
void operator delete[](void *);
C++将这些函数称为可替换的。这意味着如果您有足够的知识和意愿,可为new和delete提供函数,并根据需要对其进行定制。例如可定义作用域为类的替换函数,并对其进行定制,以满足该类的内存分配需求。
定位new运算符
通常,new负责在堆中找到一个足以能够满足要求的内存块。new运算符还有另一种变体,被称为定位new运算符,它让您能够指定要使用的位置。
char buffer[500];
struct chaff
{
int num;
}
class JustTesting
{
string words;
int number;
public:
JustTesting(const string & s = "Just Testing", int n = 0)
{words = s; number = n; cout << words << "constructed\n";}
~JustTesting() {cout << words << "," << number << endl;}
};
int main()
{
chaff * p0, * p1, *p2;
JustTest * p3, p4;
char * buf = new char[512];
p0 = new (buffer) chaff;
p1 = new (buffer) chaff;
p2 = new (buffer + sizeof(chaff)) chaff;
p3 = new JustTesting("Heap1", 20);
p4 = new (buf) JustTesting;
delete p3;
delete []buf;
p4->~JustTesting();
delete p4;
}
对以上代码说明几点:
1.buffer指定的内存是静态内存,而delete只能用于这样的指针:指向常规new运算符分配的堆内存。也就是说delete可与常规new运算符配合使用,但不能与定位运算符配合使用;
2.buf是使用new[]创建的,必须使用delete[]运算符而不是delete;
3.delete p4也是释放的buf,而不是p4,所以它并不会调用析构函数,也就是对象没有被销毁,正确的做法是显示的为使用定位new运算符创建的对象调用析构函数;
4.p1的内容会覆盖p0,正确的方式是p2;因为使用定位new的时候,指针p0,p1都是等于buffer的地址的。
定位new运算符的另一种用法是 ,将其与初始化结合使用,从而将信息放在特定的硬件地址处。
静态类成员
静态类成员有一个特点:无论创建了多少个对象,程序都只创建一个静态类变量副本。不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存。需要在类外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分。它在包含类方法的文件中初始化。初始化时使用作用域运算符来指出静态成员所属的类。但如果静态成员是整形或枚举型const,则可以在类声明中初始化。
注意:在构造函数中使用new来分配内存时,必须在相应的析构函数中使用delete来释放内存。如果使用new[]来分配内存,则应使用delete[]来释放内存。