c++常见问题总结_动态数组

动态数组

c++语言和标准库提供了两种一次分配一个对象数组的方法。1、一种new表达式,可以分配并初始化一个对象数组。2、提供的allocator类,允许我们将分配和初始化分离。
使用容器的类可以使用默认版本的拷贝、赋值和析构操作。分配动态数组的类则必须定义自己版本的操作,在拷贝、复制以及销毁对象时管理关联的内存。

new和数组
为了让new分配一个对象数组,我们要在类型名之后跟一对方括号,在其中指明要分配对象的数目(必须是整型,不要必是常量)。

int *pia=new int[getsize()];//pia指向第一个int

typedef int art[42];
int *p=new art;

分配一个数组会得到一个元素类型的指针。由于分配的内存并不是一个数组类型,因此不能对动态数组调用begin或end。这些函数使用数组维度(维度是数组类型的一部分)来返回指向首元素和尾元素的指针。出于相同的原因,也不能使用范围for语句来处理(所谓)动态数组中的元素。

  • 初始化动态分配对象的数组

new分配的对象,不管是单个分配的还是数组中的都是默认初始化的,可以采用值初始化,也可以提供一个元素初始化器的花括号列表:

int*pia=new int[10];//内置类型 默认初始化 未定义的,未初始化的
int*pia2=new int[10]();//值初始化
string *psa=new string[10];//空的string
string *psa2=new string[10]();

int *pia3 =new int[10]{1,2,3,4,5,6,7,8,9,10};

string *psa3=new string[10]{"a","an","the",string(3,'x')};
//10个string,前四个用给定的初始化器初始化,剩余的进行值初始化。

NOTE:我们可以用空括号对数组中的元素进行值初始化,但不能在括号中给出初始化器,所以我们不能像动态内存与智能指针中讲述直接管理内存时用auto分配动态数组。

特殊:动态分配一个空数组是合法的。
虽然我们不能创建一个大小为0的静态数组对象,但当n为0时,我们调用new[n]是合法的。

char arr[0];//错误
char *cp=new char[0];//正确,但是不能解引用
  • 释放动态数组
delete []pa;//pa必须指向一个动态分配的数组或为空。

销毁pa指向的数组中的元素,并且释放对应的内存。数组中的元素按照逆序销毁。如果我们在delete一个指向数组的指针时忽略了方括号(或者在delete一个指向单一对象的指针使用了方括号),其行为是未定义的。

  • 智能指针和动态数组
    标准库提供了一个可以管理new分配的数组的unique_ptr版本。
unique_ptr<int[]> up(new int[10]);
up.release();//自动用delete[]销毁其指针。

指向数组的unique_ptr的操作

当一个unique_ptr指向一个数组时可以使用下标运算符访问数组中的元素

unique_ptr<T[]>u;
unique_ptr<T[]>u(p);  //u指向内置指针p所指向的动态分配的数组。
u[i];

NOTE:shared_ptr不支持管理动态数组,如果要用必须提供自己定义的删除器,并且不支持下标运算,通常通过get()来访问元素。

shared_ptr<int> sp(new int[10],[](int *p){delete[] p;});
sp.reset();
//使用我们提供的lambda释放数组,使用delete[]
//如果未提供删除器此段代码是未定义的,我们的shared_ptr默认使用delete,这与我们delete一个指向动态内存的数组时忘记加[]一样。

for(size_t i=0;i!=0;++i)
    *(sp.get()+i)=i;//未定义下标运算符,而且智能指针类型不支持算数运算。因此,使用get()来获取一个内置指针。

allocator类
new有一些灵活上的局限,其中一方面表现在它将内存分配和对象构造组合在一起了,类似的,delete将对象析构和内存释放组合在了一起。当我们分配一大块内存时,我们通常计划在这块内存上按需构造对象。在此情况下,我们希望将内存分配和对象构造分离。即我们可以分配大块内存,只在真正需要时才真正执行对象创建操作。并且更重要的是没有默认构造函数(不能进行默认初始化和值初始化)的类不能动态分配数组了。
allocator类帮助我们将内存分配和对象构造分离开来,它分配的内存是原始的未构造的。

allocator类是一种模板类,当一个allocator对象分配内存时,它会根据给定的对象类型来确定恰当的内存大小和对其位置。它支持的操作:

#include <memory>
//定义一个名为a的allocator对象,它可以为类型为T的对象分配内存
allocator<T> a;

//分配一段原始的、未构造的内存,保存n个类型为T的对象。
a.allocate(n)

/*释放从指针p中地址开始的内存,这块内存保存了n个类型为T的对象;
p必须是一个先前由allocate返回的指针,且n必须是p创建时所要求的大小。
在调用deallocate之前,用户必须对每个在这块内存中创建的对象调用destroy*/
a.deallocate(p,n);

//在p指向的内存中构造对象,arg被传给对应的构造函数
a.construct(p,args);

//对p指向的对象执行析构函数
a.destroy(p);

example:

allocator<string> alloc;
auto const p=alloc.allocate(n);

auto q=p;//指向最后的构造元素之后的位置
alloc.construct(q++);//*q为空字符串
alloc.construct(q++,10,'c');
alloc.construct(q++,"hi");
cout<<*p<<endl;//
cout<<*q<<endl;//错误 使用了未构造的内存
while(q!=p){
    alloc.destroy(--q);//释放真正构造的string 一旦元素被销毁后我们可以使用这块内存保存其他的string
}
alloc.deallocate(p,n);//归还内存给系统

为了使用allocate返回的内存,我们必须使用construct构造对象。使用未构造的内存行为是未定义的。当我们用完元素对象之后,必须对每个构造的元素调用destroy来销毁它们(只能对真正构造了的元素进行destroy操作)。一旦元素被销毁后,就可以重新使用这部分内存保存同类型的元素,也可以将其归还给系统。

  • allocator算法
    这一部分将要介绍allocator类的两个伴随算法,用于拷贝和初始化内存。
/*这些函数在给定目的位置创建元素,而不是由系统分配内存给他们*/


/*从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。
b2指向的内存必须足够大,能够容纳输入序列中元素的拷贝。*/
uninitialized_copy(b,e,b2);

//从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中
uninitialized_copy_n(b,n,b2);

//在迭代器b和e指定的原始内存范围中创建对象,对象的值均为t的拷贝
uninitialized_fill(b,e,t);

//从迭代器b指向的内存地址开始创建n个对象。b必须指向足够大的未构造的原始内存,能够容纳给定数量的对象
uninitialized_fill_n(b,n,t);


/*例如将一个vector中的内容拷贝到动态内存*/

    allocator<int> alloc;
    auto p=alloc.allocate(v.size()*2);
    //返回一个迭代器,指向最后一个构造元素之后的位置
    auto q=uninitialized_copy(v.begin(),v.end(),p);
    //剩余元素初始化为42
    uninitialized_fill_n(q,v.size(),42);

猜你喜欢

转载自blog.csdn.net/xc13212777631/article/details/80514952