C++语言和标准库提供了两种一次分配一个对象数组的方法。C++语言定义了另一种new表达语法,可以分配并初始化一个对象数组。
new和数组
为了让new分配一个对象数组,我们要在类型名之后跟一对方括号,在其中要指明分配的对象的数目。
//调用get_size确定分配多少个int
int *pia=new int[get_size()]; //pia指向第一个int
方括号中的大小必须是整型,但不必是常量。
也可以用一个表示数组类型的类型别名来分配一个数组:
typedef int arrT[42]; //arrT表示42个int的数组类型
int *p=new arrT; //分配一个42个int的数组;p指向第一个int
分配一个数组会得到一个元素类型的指针
当用new分配一个数组时,我们并未得到一个数组类型的对象,而是得到一个数组元素类型的指针。因此不能对动态数组调用begin或end,也不能使用范围for语句来处理动态数组中的元素。
初始化动态分配对象的数组
默认情况下, new分配的对象都是默认初始化的,可以对数组中的元素进行值初始化:
int *pia=new int[10]; //10个未初始化的int
int *pia2=new int[10(); //10个值初始化为0的int
string *psa=new string[10]; //10个空string
string *psa2=new string[10](); //10个空string
//10个int分别用列表中对应的初始化器初始化
int *pia3=new int[10] {0,1,2,3,4,5,6,7,8,9};
//10个string,前4个用给定的初始化器初始化,剩余的进行值初始化
string *psa3=new string[10] {"a","an","the",string(3,'x')};
动态分配一个空数组是合法的
当我们用new分配一个大小为0的数组时,new返回一个合法的非空指针。此指针保证与new返回的其他任何指针都不相同。对于零长度的数组来说,此指针就像尾后指针一样,我们可以像使用尾后迭代器一样使用这个指针。
释放动态数组
为了释放动态数组,我们使用一种特殊形式的delete——在指针前面加上一个空方括号对:
delete p; //p必须指向一个动态分配的对象或为空
delete[] pa; //pa必须指向一个动态分配的数组或为空
智能指针与动态数组
标准库提供了一个可以管理new分配的数组的unique_ptr版本。为了用一个unique_ptr管理动态数组,我们必须在对象类型后面跟一对空方括号:
//up指向一个包含10个未初始化int的数组
unique_ptr<int[]> up(new int[10]);
up.release(); //自动用delete[]销毁其指针
当一个unique_ptr指向一个数组时,我们不能使用点和箭头成员运算符,因为unique_ptr指向的是一个数组而不得单个对象,因此这些运算符是无意义的,我们可以使用下标运算符来访问数组中的元素:
for(size_t i=0;i!=10;++i)
up[i]=i; //为每个元素赋予一个新值
与unique_ptr不同,shared_ptr不直接支持管理动态数组。如果希望使用shared_ptr管理一个动态数组,必须提供自己定义的删除器。shared_ptr未定义下标运算符,而且智能指针类型不支持指针算术运算。因此,为了访问数组中的元素,必须使用get获取一个内置指针,然后用它来访问数组元素:
for(size_t i=0; i!=10;++i)
*(sp.get()+i)=i; //使用get获取一个内置指针
allocator类
标准库allocator类定义在头文件memory中,它帮助我们将内存分配和对象构造分离开来,它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。
类似vector,allocator是一个模板。为了定义一个allocator对象,我们必须指明这个allocator可以分配的对象类型:
allocator<string> alloc;
auto const p=alloc.allocate(n); //分配n个未初始化的string
这个allocate调用为n个string分配内存
allocator分配未构造的内存
allocator分配的内存是未构造的。我们按需要在此内存中构造对象,construct成员函数接受一个指针和零个或多个额外参数,在给定位置构造一个元素。额外参数用来初始化构造的对象。
auto q=p; //q指向最后构造的元素之后的位置
alloc.construct(q++); //*q为空字符串
alloc.construct(q++,10,'c'); //*q为cccccccccc
alloc.construct(q++,"hi"); //*q为hi!
当我们用完对象后,必须对每个构造的元素调用destroy来销毁它们。函数destroy接受一个指针,对指向的对象执行析构函数:
while(q!=p)
alloc.destroy(--q); //释放我们真正构造的string
一旦元素被销毁后,就可以重新使用这部分内存来保存其他string,也可以将其归还系统,释放内存通过调用deallocate来完成:
alloc.deallocate(p,n);
我们传递给deallocate的指针不能为空,它必须指向由allocate分配的内存。而且,传递给deallocate的大小参数必须与调用allocated分配的内存时提供的大小参数具有一样的值。
拷贝与填充未初始化内存的算法
标准库还为allocator类定义了两个伴随算法,可以在未初始化内存中创建对象:uninitialized_copy,unitialized_fill。
//分配比vi周年元素所占空间大一倍的动态内存
auto p=alloc.allocate(vi.size()*2);
//通过拷贝vi中的元素来构造从p开始的元素
auto q=unitialized_copy(vi.begin(),vi.end(),p);
//将剩余元素初始化为2
unitialized_fill_n(q,vi.szie(),42);