动态内存

到目前为止,在我们所有的程序中,我们拥有的内存仅限于我们为变量声明的内存,所有变量的大小都在程序执行之前在源代码中确定。但是,如果我们需要一个可变数量的内存,而这个内存只能在运行时确定呢?例如,我们需要一些用户输入来确定必要的内存空间。

答案是动态内存,为此 C++ 集成了运算符newdelete.

运算符 new 和 new[]

为了请求动态内存,我们使用运算符new. new之后跟一个数据类型说明符,如果需要一个以上的元素的序列,则用方括号括起来表示元素的数量[]。它返回指向新分配的内存块的开始位置的指针。它的形式是

pointer = new type
pointer = new type [number_of_elements]

第一个表达式用于分配内存来包含一个类型为类型的单个元素。第二个表达式用于分配一个类型为类型,其中的元素块(数组),其中是一个整数值,表示元素的数量。例如

1
2
int * bobby;
bobby = new int [5];

在这种情况下,系统动态分配空间给五个int类型的元素,并返回指向序列中第一个元素的指针,该指针被赋值给bobby。因此,现在,bobby指向一个有效的内存块,其中包含五个int.



类型的元素的空间。通过表达式bobby[0]或表达式*bobby都可以访问 bobby 指向的第一个元素。正如指针部分所解释的那样,两者是等价的。可以通过bobby[1]*(bobby+1)访问第二个元素,以此类推...

您可能想知道声明一个普通数组和给指针分配动态内存之间的区别,正如我们刚才所做的那样。最重要的区别是数组的大小必须是一个常量值,这限制了它的大小为我们在设计程序时所决定的值,在程序执行之前,而动态内存分配允许我们在程序执行期间(运行时)使用任何变量或常量值作为其大小来分配内存。

我们的程序请求的动态内存由系统从内存堆中分配。然而,计算机内存是一种有限的资源,它可能会被耗尽。因此,重要的是要有一些机制来检查我们分配内存的请求是否成功。

C++ 提供了两种标准方法来检查分配是否成功

一种是通过处理异常。使用此方法,当分配失败时,会抛出一个bad_alloc类型的异常。异常是 C++ 的一个强大的特性,将在本教程后面进行解释。但现在您应该知道,如果抛出了这个异常,并且没有被特定的处理程序处理,程序的执行将会终止。

这种异常方法是 new 使用的默认方法,也是像这样的声明中使用的方法

1
bobby = new int [5];  // if it fails an exception is thrown 

另一种方法被称为nothrow,使用它时,当内存分配失败时,不会抛出bad_alloc异常或终止程序,而是new返回一个空指针,程序继续执行。

可以通过使用一个称为nothrow的特殊对象来指定此方法,该对象在头文件<new>中声明,作为new:

1
bobby = new (nothrow) int [5];

的参数。在这种情况下,如果此内存块的分配失败,可以通过检查bobby是否具有空指针值来检测失败

1
2
3
4
5
int * bobby;
bobby = new (nothrow) int [5];
if (bobby == 0) {
  // error assigning memory. Take measures.
  };

这个nothrow方法比异常方法需要更多的工作,因为每次内存分配后都必须检查返回值,但由于它的简单性,我将在我们的示例中使用它。无论如何,对于更大的项目,这种方法可能会变得乏味,在这种情况下,通常首选异常方法。异常方法将在本教程后面详细解释。

运算符 delete 和 delete[]

由于对动态内存的需求通常仅限于程序中的特定时刻,一旦不再需要它,就应该释放它,以便该内存再次可用于其他动态内存请求。这是运算符delete的目的,其格式为

1
2
delete pointer;
delete [] pointer;

第一个表达式应用于删除为单个元素分配的内存,第二个表达式应用于删除为元素数组分配的内存。

作为参数传递给 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 == 0)
    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;
}
How many numbers would you like to type? 5
Enter number : 75
Enter number : 436
Enter number : 1067
Enter number : 8
Enter number : 32
You have entered: 75, 436, 1067, 8, 32,

请注意new语句中方括号内的值是用户输入的变量值 (i),而不是常量值。

1
p= new (nothrow) int[i];

但是用户可能输入了一个 i 的值,这个值太大,我们的系统无法处理。例如,当我试图给“有多少个数字”这个问题赋值 10 亿时,我的系统无法为该程序分配那么多的内存,我得到了我们为此情况准备的文本消息(错误:无法分配内存)。请记住,如果我们尝试在 new 表达式中不指定 nothrow 参数来分配内存,则会抛出一个异常,如果没有处理该异常,程序将终止。

始终检查动态内存块是否成功分配是一个好习惯。因此,如果您使用nothrow方法,您应该始终检查返回指针的值。否则,使用异常方法,即使您不处理该异常。这样,程序将在该点终止,而不会导致继续执行一段假设已分配内存块的代码而实际上未分配内存的意外结果。

ANSI-C 中的动态内存


运算符newdelete是 C++ 独有的。它们在 C 语言中不可用。但是,使用纯 C 语言及其库,也可以通过函数 malloccallocreallocfree 来使用动态内存,这些函数也可在 C++ 中使用,包括<cstdlib>头文件(有关更多信息,请参见 cstdlib)。

这些函数分配的内存块不一定与 new 返回的内存块兼容,因此每个内存块都应使用其自身的一组函数或运算符进行操作。
Index
目录