13.3 用于数组的new和delete

在栈或堆上创建一个对象数组是同样容易的。当然,应当为数组里的每一个对象调用构造函数。但这里有一个限制条件:由于不带参数的构造函数必须被每一个对象调用,所以除了在栈上聚合初始化(见第6章)外还必须有一个默认的构造函数。

当使用new在堆上创建对象数组时,还必须多做一些操作。下面是一个创建对象数组的例子:

13.3 用于数组的new和delete - 图1

这样在堆上为100个MyType对象分配了足够的内存并为每一个对象调用了构造函数。但是现在,仅拥有一个MyType*。它和用下面的表达式创建单个对象得到的结果是一样的:

13.3 用于数组的new和delete - 图2

因为这是我们写的代码,所以我们知道fp实际上是一个数组的起始地址,所以可以使用类似于fp[3]的形式来选择数组的元素。但销毁这个数组时发生了什么呢?下面的语句看起来是完全一样的:

13.3 用于数组的new和delete - 图3

并且它们的效果也应该是一样:为所给地址指向的MyType对象调用析构函数,然后释放内存。对于fp2,这样是正确的,但对于fp,另外99个析构函数没有调用。适当数量的存储单元会被释放,但是,由于它们被分配在一个整块的内存中,而整个内存块的大小被分配程序存储在某个地方。

解决办法是给编译器一个信息,说明它实际上是一个数组的起始地址。这可以用下面的语法来实现:

13.3 用于数组的new和delete - 图4

空的方括号告诉编译器产生代码,该代码的任务是将从数组创建时存放在某处的对象数量取回,并为数组的所有对象调用析构函数。这实际上是对以前形式的改良,我们偶尔仍可以在旧版本中看到如下的代码:

13.3 用于数组的new和delete - 图5

这个语法强迫程序设计者加入数组中对象的数量,但程序设计者有可能把对象的数量弄错。而让编译器处理这件事引起的附加代价是很低的,所以只在一个地方指明对象数量要比在两个地方指明好些。

13.3.1 使指针更像数组

作为题外话,上面定义的fp可以被修改指向任何类型,但这对于一个数组的起始地址来讲没有什么意义。一般来说,把它定义为常量会更好些,因为这样任何修改指针的企图都会被认为出错。为了得到这个效果,可以试着用下面的表达式:

13.3 用于数组的new和delete - 图6

13.3 用于数组的new和delete - 图7

上面的这两种表达式都把const和被指针指向的int捆绑在一起,而不是指针本身。如果使用下面的表达式:

13.3 用于数组的new和delete - 图8

则现在q中的数组元素可以被修改了,但对q本身的修改(例如q++)是不合法的,因为它是一个普通数组标识符。