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);