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分配的对象,不管是单个分配还是数组中的,都是默认初始化的。可以对数组中的元素进行值初始化,方法是在大小后面跟一对空括号。

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

在新标准中,还可以提供一个元素初始化器的花括号列表:

int *pia3 = new int[10]{1,2,3,4,5,6,7,8,9,0};
// 10个string,前3个用给定的初始化器初始化,剩余的进行值初始化
string *psa3 = new string[10]{"a","an",string(3,'x')};

动态分配一个空数组是合法的

char arr[0];			// 错误:不能定义长度为0的数组
char *cp = new char[0];	// 正确:但cp不能解引用

当用new分配一个大小为0的数组时,new返回一个合法的非空指针。

此指针保证与new返回的其他任何指针都不相同。此指针就像尾后指针一样,可以像使用尾后迭代器一样使用这个指针。

释放动态数组

delete p;			// p必须指向一个动态分配的对象或为空
delete [] pa;		// pa必须指向一个动态分配的数组或为空

第二条语句销毁pa指向的数组中的元素并释放对应的内存。数组中的元素按逆序销毁。

智能指针和动态数组

// up指向一个包含10个未初始化int的数组
unique_ptr<int[]> up(new int[10]);
up.release();	// 自动用delete[] 销毁其指针

由于up指向一个int数组而不是一个int,当up销毁它管理的指针时,会自动使用delete[]。

我们可以使用下标运算符来访问数组中的元素:

for(size_t i = 0; i != 10; ++i)
	up[i] = i;		// 为每个元素赋予一个新值

指向数组的unique_ptr提供的操作

与unique_ptr不同,shared_ptr不支持直接管理动态数组。如果希望使用shared_ptr管理一个动态数组,必须提供自己定义的删除器。

// 为了使用shared_ptr,必须提供一个删除器
shared_ptr<int> sp(new int[10], [](int *p) {delete []p; });
sp.reset();		// 使用提供的lambda释放数组

shared_ptr不直接支持动态数组管理这一特性会影响我们如何访问数组中的元素:

// shared_ptr为定义下标运算符,并且不支持指针的算术运算
for(size_t i = 0; i != 10; ++i)
	*(sp.get() + i) = i;		// 使用get获取一个内置指针

为了访问数组中的元素,必须用get获取一个内置指针,然后用它来访问数组元素。

allocator类

new有一些灵活性上的局限,其中一方面表现在它将内存分配和对象构造组合在一起。类似的,delete将对象析构和内存释放组合在一起。

当分配一大块内存时,我们通常计划在这块内存上按需构造对象。在此情况下,我们希望将内存分配和对象构造分离。这意味着我们可以分配大块内存,但只在真正需要时才真正执行对象创建操作。

string *const p = new string[n];	// 构造n个空string
string s;
string *q = p;						// q指向第一个string
while(cin >> s && q != p + n)
	*q++ = s;
const size_t size = q - p;			// 记住读取了多少个string
delete []p;

new表达式分配并初始化了n个string。但是,可能不需要n个string。而且对于那些确实要使用的对象,也在初始化之后立即赋予了它们新值。每个用到的元素都被赋值了两次:第一次是在默认初始化时,随后是在赋值时。

更重要的是,那些没有默认构造函数的类就不能动态分配数组了。

allocator类

标准库allocator类帮助将内存分配和对象构造分离开来。它提供一种类型感知的内存分配方法,它分配的内存是原始的、未构造的。

当一个allocator对象分配内存时,它会根据给定的对象类型来确定恰当的内存大小和对齐位置。

allocator<string> alloc;			// 可以分配string的allocator对象
auto const p = alloc.allocate(n);	// 分配n个为初始化的string

标准库allocator类及其用法

allocator分配未构造的内存

allocator分配的内存是未构造的(unconstructed)。在新标准库中,construct成员函数接受一个指针和零个或多个额外参数,在给定位置构造一个元素。

auto q = p;		// q指向最后构造的元素之后的元素
alloc.construct(q++);			// *q为空字符串,先传递q,函数结束再++
alloc.construct(q++, 10, 'c');		// *q为cccccccccc
alloc.construct(q++, "hi");			// *q为hi

还未构造对象的情况下就使用原始内存是错误的:

cout << *p << endl;		// 正确:使用string的输出运算符
cout << *q << endl;		// 灾难:q指向未构造的内存

当我们用完对象后,必须对每个构造的元素调用destroy来销毁它们。函数destroy接受一个指针,对指向的对象执行析构函数。

while(q != p)
	alloc.destroy(--q);		// 释放真正构造的string

在循环开始处,q指向最后构造的元素之后的位置。在调用destroy之前对q进行了递减操作。因此,第一次调用destroy时,q指向最后一个构造的元素。最后一步循环中我们destroy了第一个构造的元素,随后q将与p相等,循环结束。

我们只能对真正构造了的元素进行destroy操作。

一旦元素被销魂后,就可以重新使用着部分内存来保存其他string,也可以将其归还系统。释放内存通过调用deallocate来完成:

alloc.deallocate(p, n);

传递给deallocate的指针不能为空,它必须指向由allocate分配的内存。而且,传递给deallocate的大小参数必须与调用allocate分配内存时提供的大小参数具有一样的值。

拷贝和填充未初始化内存的算法

标准库还为allocate类定义了两个伴随算法,可以在未初始化内存中创建对象。
allocate算法

auto p = alloc.allocate(vi.size() * 2);
// 通过拷贝vi中的元素来构造从p开始的元素
auto q = uninitialized_copy(vi.begin(), vi.end(), p);
// 将剩余元素初始化未42
uninitialized_fill(q, v1.size(), 42);

类似拷贝算法,uninitialized_copy接受三个迭代器参数。前两个表示输入序列,第三个表示这些元素将要拷贝到的目的空间。传递给uninitialized_copy的目的位置迭代器必须指向未构造的内存。与copy不同,uninitialized_copy在给定目的位置构造元素。

uninitialized_copy返回(递增后的)目的位置迭代器。因此,一次uninitialized_copy调用会返回一个指针,指向最后一个构造的元素之后的位置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值