重要的事情说三遍:
在前几章的程序中,所有的内存需求都是在程序执行之前通过定义所需的变量来确定的。但是,有些情况下,程序的内存需求只能在运行时确定。例如,当所需内存依赖于用户输入时。在这些情况下,程序需要动态分配内存,C++语言通过 new
和 delete
运算符来实现这一点。
运算符 new
和 new[]
使用 new
运算符分配动态内存。new
后面跟一个数据类型说明符,如果需要一个以上的元素序列,则在方括号 []
中指定元素的数量。它返回一个指向新分配的内存块起始位置的指针。其语法如下:
pointer = new type
pointer = new type [number_of_elements]
第一个表达式用于分配一个 type
类型的单个元素的内存。第二个用于分配一个 type
类型的元素块(数组),其中 number_of_elements
是表示这些元素数量的整数值。例如:
int * foo;
foo = new int [5];
在这种情况下,系统动态分配空间以容纳五个 int
类型的元素,并返回指向序列第一个元素的指针,该指针被分配给 foo
(一个指针)。因此,foo
现在指向一个有效的内存块,该内存块有五个 int
类型的元素空间。
这里,foo
是一个指针,因此,可以通过表达式 foo[0]
或表达式 *foo
(两者等价)访问 foo
指向的第一个元素。可以通过 foo[1]
或 *(foo+1)
访问第二个元素,依此类推……
声明一个普通数组和使用 new
为内存块分配动态内存之间有显著区别。最重要的区别是,普通数组的大小需要是一个常量表达式,因此其大小必须在设计程序时(在运行之前)确定,而使用 new
执行的动态内存分配允许在运行时使用任何变量值作为大小分配内存。
我们的程序请求的动态内存由系统从内存堆中分配。然而,计算机内存是一种有限的资源,它可能会耗尽。因此,不能保证系统会批准所有使用 new
运算符分配内存的请求。
C++ 提供了两种标准机制来检查分配是否成功:
一种是通过处理异常。在这种方法中,当分配失败时,会抛出 bad_alloc
类型的异常。异常是 C++ 的一个强大功能,会在后续教程中详细解释。但现在,您只需知道如果抛出此异常并且没有特定的处理程序来处理它,程序执行将会终止。
这种异常方法是 new
默认使用的方法,用于如下声明:
foo = new int [5]; // 如果分配失败,会抛出异常
另一种方法称为 nothrow
,当使用该方法时,如果内存分配失败,不会抛出 bad_alloc
异常或终止程序,而是 new
返回一个空指针,程序正常继续执行。
可以通过在头文件 <new>
中声明的一个名为 nothrow
的特殊对象作为 new
的参数来指定此方法:
foo = new (nothrow) int [5];
在这种情况下,如果该内存块的分配失败,可以通过检查 foo
是否为空指针来检测失败:
int * foo;
foo = new (nothrow) int [5];
if (foo == nullptr) {
// 分配内存出错,采取措施
}
由于这种 nothrow
方法需要在每次分配后显式检查指针值,因此可能会生成效率较低的代码。因此,异常机制通常是首选的,至少对于关键的分配来说是如此。不过,由于其简单性,接下来的大多数示例将使用 nothrow
机制。
运算符 delete
和 delete[]
在大多数情况下,动态分配的内存仅在程序的特定时间段内需要;一旦不再需要,可以释放该内存,以便其他动态内存请求可以使用。这就是 delete
运算符的用途,其语法如下:
delete pointer;
delete[] pointer;
第一条语句释放使用 new
分配的单个元素的内存,第二条语句释放使用 new
和方括号([]
)中的大小分配的元素数组的内存。
传递给 delete
的值应是先前用 new
分配的内存块的指针,或者是一个空指针(在空指针的情况下,delete
不产生任何效果)。
// rememb-o-matic
#include <iostream>
#include <new>
using namespace std;
int main ()
{
int i, n;
int * p;
cout << "How many numbers would you like to type? ";
cin >> i;
p = new (nothrow) int[i];
if (p == nullptr)
cout << "Error: memory could not be allocated";
else
{
for (n = 0; n < i; n++)
{
cout << "Enter number: ";
cin >> p[n];
}
cout << "You have entered: ";
for (n = 0; n < i; n++)
cout << p[n] << ", ";
delete[] p;
}
return 0;
}
注意 new
语句中的方括号内的值是用户输入的变量值(i
),而不是常量表达式:
p = new (nothrow) int[i];
用户可能会输入一个非常大的值以至于系统无法为其分配足够的内存。例如,当我尝试将 “How many numbers” 问题的值设为 10 亿时,我的系统无法为程序分配那么多内存,我得到了为这种情况准备的文本消息(Error: memory could not be allocated
)。
良好的编程实践是,程序应始终能够处理分配内存失败的情况,无论是通过检查指针值(如果使用 nothrow
)还是通过捕获适当的异常。
C 语言中的动态内存
C++ 集成了 new
和 delete
运算符来分配动态内存。但这些在 C 语言中不可用;相反,C 语言使用库函数来实现动态内存分配,例如 malloc
、calloc
、realloc
和 free
,它们在头文件 <cstdlib>
(在 C 中称为 <stdlib.h>
)中定义。这些函数在 C++ 中也可用,可以用于分配和释放动态内存。
注意,由这些函数分配的内存块不一定与 new
返回的内存块兼容,因此不应混用;每种内存分配方式都应使用其对应的函数或运算符来处理。