動態數組


new 和動態數組

為了讓 new 分配一個對象數組,要在類型名之后跟一對方括號,在其中指明要分配的對象的數目,返回指向第一個對象的指針,方括號中的大小必須是整型,但不必是常量:

int* pia = new int[get_size()];	//	pia 指向第一個int

也可以使用一個表示數組類型的類型別名來分配一個數組,new 表達式中就不需要方括號了:

typedef int arrT[42];	// arrT 表示42個int的數組類型
int* p = new arrT;		// 分配一個42個int的數組,p指向第一個int

分配一個數組會得到一個元素類型的指針

雖然稱 new T[] 分配的內存稱為 “動態數組”,但是使用 new 分配一個數組時,但是我們並未得到一個數組類型的對象,而是得到一個數組元素類型的類型。

由於分配的內存並不是一個數組類型,因此不能對動態數組調用 beginend 來返回指向首元素和尾后元素的指針。出於相同的原因,也不能使用范圍 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

在新標准中,可以提供一個原始初始化器的花括號列表:

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表達式失敗,不會分配任何內存。

動態分配一個空數組時合法的

size_t n = get_size();	//get_size 返回需要的元素數目
int *p = new int[n];	//分配數組保存元素
for(int *q = p;q != p + n;++q)
{//處理數組}

上面的代碼,如果 get_size() 返回的是0 程序也能正常運行。雖然不能創建一個大小為0的靜態數組對象,但是調用 new[0]是合法的:

char arr[0];	//錯誤,不能定義長度為0的靜態數組
char *cp = new char[10];	//正確,但是cp不能解引用

當用 new 分配一個大小為0的數組時,new 返回一個合法的非空指針。此指針保證與 new 返回的其它任何指針都不相同。對於零長度的數組來說,此指針就像尾后指針一樣,可以像使用尾后迭代器一樣使用這個指針。可以使用此指針進行比較操作,可以向此指針加上或者減去0,也可以從這個指針減去自從而得到0,但此指針不能解引用,因為它不指向任何元素。

釋放動態數組

為了釋放動態數組,delete 在指針前加一個空方括號對:

delete p;	//p必須指向一個動態分配的對象或為空
delete [] pa;	//pa 必須指向一個動態分配的數組或為空

第二個語句銷毀 pa 指向的數組中的元素,並釋放對應的內存,數組中的元素被按逆序銷毀,即最后一個元素首先被銷毀,然后是倒數第二個,以此類推。

當釋放一個指向數組的指針時,空方括號對是必須的:它指示編譯器此指針指向一個對象數組的第一個元素,如果在 delete 一個指向數組的指針時忽略了方括號,或者是 delete 一個指向單一對象的指針時使用了方括號,其行為是未定義的。

typedef int arrT[42];
int *p = new arrT;
delete [] p;	//即使使用類型別名來定義一個數組類型時,delete時[]也是必須的

智能指針和動態數組

標准庫提供了一個可以管理 new 分配的數組的 unique_ptr 版本,為了用一個 unique_ptr 管理動態數組,我們必須在對象類型后面跟一對空的方括號:

unique_ptr<int []> up(new int[10]); //up指向一個包含10個未初始化int的數組
up.release();	//自動用delete銷毀其指針

類型說明符中的方括號 (<int []>) 指出 up 指向一個 int 數組而不是一個 int,由於 up 指向一個數組,當 up 銷毀它管理的指針時,會自動調用 delete[]

指向數組的 unique_ptr 不能使用點和箭頭成員運算符,而應該使用下標運算符訪問數組中的元素:

for(size_t i = 10;i != 10;++i)
	up[i] = i;

unique_ptr 不同,shared_ptr 不直接支持管理動態數組,如果希望使用 shared_ptr 管理一個動態數組,必須提供自己定義的刪除器:

shared_ptr<int> sp(new int[10],[](int *p){delete [] p;});
sp.reset(); //使用提供的lambda釋放數組

如果沒有提供刪除器,這段代碼將是未定義的。默認情況下, shared_ptr 使用 delete 銷毀它所指向的對象。如果此對象是一個動態數組,對其使用 delete 所產生的問題與釋放一個動態數組指針時忘記 [] 產生的問題一樣。

shared_ptr 不直接支持動態數組管理這一特性會影響如何訪問數組中的元素:

for(size_t i = 0;i != 0;++i)
	*(sp.get() + i) = i; //使用 get 獲取一個內置指針

shared_ptr 未定義下標運算符,而且智能指針類型不支持指針算術運算,因此,為了訪問數組中的元素,必須用 get 獲取一個內置指針,然后用它來訪問數組元素。

allocator 類

new 有一些靈活性上的局限,其中一方面表現在它將內存分配和對象構造組合在一起,類似的,delete 將對象析構和內存釋放組合在一起。一般當分配單個對象時,通常希望將內存分配和對象初始化組合在一起。

當分配一大塊內存時,並且在這塊內存上按需構造對象。在此情況下,希望將內存分配和對象構造分離,這意味着可以分配大塊內存,但只在真正需要時才真正執行對象創建操作。

一般情況下,將內存分配和對象構造組合在一起可能會導致不必要的浪費:

string *const p = new string[n];	//構造n個空string
string s;
string *q = p;
while(cin >> s &&  q != p + n)
	*q++ = s;
const size_t size  = q - p;
delete [] p ;

new 表達式分配並初始化了 nstring,但是實際使用時可能不需要 nstring,少量的 string 就足夠了,這樣,我們就創建了一些可能永遠也用不到的對象。而且,對於那些確實要使用的對象,在初始化之后會立即賦予新值,每個使用的 元素都被賦值兩次。

更重要的是,那些沒有默認構造函數的類就不能動態分配數組。

allocator 類

allocator 定義在頭文件 memory 中,它幫助我們將內存分配和對象構造分離,它提供了一種類型感知的內存分配方法,它分配的內存是原始的、未構造的。

allocate 是一個模板,為了定義一個 allocator 對象,必須指明這個 allocator 可以分配的對象類型。當一個 allocator 對象分配內存時,它會根據給定對象類型來確定恰當的內存大小和對齊位置:

allocator<string> alloc;
auto const p = alloc.allocate(n);	//分配n個未初始化的 string

allocator 分配未構造的內存

allocator 分配的內存是未構造的,后期可以在此內存中按需構造對象。construct 成員函數接受一個指針和零個或多個額外參數,在給定位置構造一個元素,額外參數用來初始化構造的對象,這些額外參數必須是與構造的對象的類型相匹配的合法的初始化器:

auto q = p;
alloc.construct(q++);	//*為空字符串
alloc.construct(q++,10,'c');	//*q為cccccccccc
alloc.construct(q++,"hi");	//*q為 hi

construct 只接受兩個參數:指向創建對象位置的指針和一個元素類型的值。

還未構造對象的情況下就使用原始內存是錯誤的:

cout<< *p <<endl;	//正確,使用 string 的輸出運算符
cout<< *q <<endl;	//災難,q指向未構造的內存

注意:

為了使用 allocate 返回的內存,必須使用 construct構造對象,使用未構造的內存,其行為是未定義的。

當使用完對象后,必須對每個構造的元素調用 destroy 來銷毀它們,函數 destroy 接受一個指針,對指向的對象執行析構函數:

while (q != p)
	alloc.destroy(--q);

在循環開始處,q 指向最后構造的元素之后的位置。在調用 destroy 之前對 q 進行了遞減操作。

釋放內存通過調用 deallocate 來完成:

alloc.deallocate(p,n);

傳遞給 deallocate 的指針不能為空,它必須指向由 deallocate 分配的內存,且傳遞給 deallocate 的大小參數必須與調用 allocate 分配內存時提供的大小參數值相等。

拷貝和填充未初始化內存的算法

allocator 類定義了兩個伴隨算法,可以在未初始化內存中創建對象,它們也定義在頭文件 memory 中。

auto p = alloc.allocate(vi.size() *2);	//分配比vi中元素所占用空間大一倍的動態內存
auto q = uninitialized_copy(vi.begin(),vi.end(),p);	//拷貝vi中的元素構造從p開始的元素
uninitialized_fill_n(q,vi.size(),42);	//將剩余的元素初始化為42

uninitialized_copy 調用會返回一個指針,指向最后一個構造的元素之后的位置。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM