數(shù)組類型的變量有三個重要的限制:數(shù)組長度固定不變,在編譯時必須知道其長度,數(shù)組只在定義它的塊語句內(nèi)存在.
實際的程序往往不能忍受這樣的限制-------它們需要在運行時 動態(tài)地分配數(shù)組.雖然數(shù)組長度是固定的,但動態(tài)分配的數(shù)組不必在編譯時知道其長度,可以(通常也是)在運行時才確定數(shù)組長度.與數(shù)組變量不同,動態(tài)分配的數(shù)組將一直存在,知道程序顯式釋放它為止.
每一個程序在執(zhí)行時都占用一款可用的內(nèi)存空間,用于存放動態(tài)分配的對象,此內(nèi)存空間稱為程序的自由存儲區(qū)(free store)或堆(heap).C語言程序使用一對標準庫函數(shù)malloc和free在自由存儲區(qū)中分配存儲空間,而C++語言則使用new和delete表達式實現(xiàn)相同的功能.
1.動態(tài)數(shù)組的定義
數(shù)組變量通過指定類型、數(shù)組名和維數(shù)來定義.而動態(tài)分配數(shù)組時,只需指定類型和數(shù)組長度,不必為數(shù)組對象命名,new表達式返回指向新分配數(shù)組的第一個元素的指針:
int *pia=new int[10]; //array of 10 uninitialized ints
此new表達式分配了一個含有10個int型元素的數(shù)組,并返回指向該數(shù)組第一個元素的指針,此返回值初始化了之怎pia.
new表達式需要指定指針類型以及在方括號中給出的數(shù)組維數(shù),該維數(shù)可以是任意的復雜表達式.創(chuàng)建數(shù)組后,new將返回指向數(shù)組第一個元素的指針.在自由存儲區(qū)中創(chuàng)建的數(shù)組對象是沒有名字的,程序員只能通過其地址間接地訪問堆中的對象.
2.初始化動態(tài)分配的數(shù)組
動態(tài)分配數(shù)組時,如果數(shù)組元素具有類類型,將使用該類的默認構(gòu)造函數(shù)實現(xiàn)初始化;如果數(shù)組元素是內(nèi)置類型,則無初始化:
string *psa=new string[10]; //array of 10 empty strings
int *pia=new int[10]; //array of 10 ninitialized ints
這兩個new表達式都分配了含有10個對象的數(shù)組.其中第一個數(shù)組是string類型,分配了保存對象的內(nèi)存空間后,將調(diào)用string類型的默認構(gòu)造函數(shù)依次初始化數(shù)組中的每個元素.第二個數(shù)組則具有內(nèi)置類型的元素,分配了存儲10個int對象的內(nèi)存空間,但這些元素沒有初始化.
也可使用跟在數(shù)組長度后面的一對空圓括號,對數(shù)組元素做值初始化:
int *pia2=new int[10](); //array of 10 uninitialized ints
圓括號要求編譯器對數(shù)組做值初始化,在本例中即把數(shù)組元素都設置為0.
注解:對于動態(tài)分配的數(shù)組,其元素只能初始化為元素類型的默認值,而不能像數(shù)組變量一樣,用初始化列表為數(shù)組元素提供各不相同的初值.
3.const對象的動態(tài)數(shù)組
如果我們在自由存儲區(qū)中創(chuàng)建的數(shù)組存儲了內(nèi)置類型的const對象,則必須為這個數(shù)組提供初始化:因為數(shù)組元素都是const對象,無法賦值.實現(xiàn)這個要求的唯一方法是對數(shù)組做值的初始化:
//error:uninitialized const array
const int *pci_bad=new const int[100];
//ok:value-initialized const array
const int *pci_ok=new const int[100]();
C++允許定義類類型的const數(shù)組,但該類類型必須提供默認構(gòu)造函數(shù):
//ok:array of 100 empty strings
const string *pcs=new const string[100];
在這里,將使用string類的默認構(gòu)造函數(shù)初始化數(shù)組元素.
當然,已創(chuàng)建的常量元素不允許修改------因此這樣的數(shù)組實際上用處不大.
4.允許動態(tài)分配空數(shù)組
之所以要動態(tài)分配數(shù)組,往往是由于編譯時并不知道數(shù)組的長度.我們可以編寫如下代碼
size_t n=get_size(); //get_size returns number of elements needed
int *p=new int[n];
for(int *q=p;q!=p+n;++q)
/* process the array */;
計算數(shù)組長度,然后創(chuàng)建和處理該數(shù)組.
有趣的是,如果get_size返回0會怎么樣?答案是:代碼仍然正確執(zhí)行.C++雖然不允許定義長度為0的數(shù)組變量,但明確指出,調(diào)用new動態(tài)創(chuàng)建長度為0的數(shù)組是合法的:
char arr[0]; //error:cannot define zero-length array
char *cp=new char[0]; //ok:but cp can't be dereferenced
用new動態(tài)創(chuàng)建長度為0的數(shù)組時,new返回有效的非零指針.該指針與new返回的其他指針不同,不能進行解引用操作,因為它畢竟沒有指向任何元素.而允許的操作包括:比較運算,因此該指針能在循環(huán)中使用;在該指針上加(減)0,或者減去本身值,得0值.
在上述例題中,如果get_size返回0,則仍然可以成功調(diào)用new,但是p并沒有指向任何對象,數(shù)組是空的.因為n為0,所以for循環(huán)實際比較的是p和q,而q是用p初始化的,兩者具有相等的值,因此for循環(huán)條件不成立,循環(huán)體一次都沒有執(zhí)行
5.動態(tài)空間的釋放
動態(tài)分配的內(nèi)存最后必須進行釋放,否則,內(nèi)存最終將會逐漸耗盡.如果不再需要使用動態(tài)創(chuàng)建的數(shù)組,程序員必須顯式地將其占用的存儲空間返還給程序的自由存儲區(qū).C++語言為指針提供delete[]表達式釋放指針所指向的數(shù)組空間:
delete[] pia;
該語句回收了pia所指向的數(shù)組,把相應的內(nèi)存返還給自由存儲區(qū).在關鍵字delete和指針之間的空方括號對是必不可少的:它告訴編譯器該指針指向的是自由存儲區(qū)中的數(shù)組,而并非單個對象.
小心:如果遺漏了空方括號對,這是一個編譯器無法發(fā)現(xiàn)的錯誤,將導致程序在運行時出錯.
理論上,回收數(shù)組時缺少空方括號對,至少會導致運行時少釋放了內(nèi)存空間,從而產(chǎn)生內(nèi)存泄漏(memory leak).對于某些系統(tǒng)和/或元素類型,有可能會帶來更嚴重的運行時錯誤.因此,在釋放動態(tài)數(shù)組時千萬別忘了方括號對.
C風格字符串與C++的標準庫類型string的比較:
以下兩段程序反映了使用C風格字符串與C++的標準庫類型string的不同之處.使用string類型的版本更短、更容易理解,而且出錯的可能性更小:
//C-style character string implementation
const char *pc="a very long literal string";
const size_t len=strlen(pc +1); //space to allocate
//performance test on string allocation and copy
for(size_t ix=0;ix!=1000000;++ix){
char *pc2=new char[len+1]; //allocate the space
strcpy(pc2,pc); //do the copy
if(strcmp(pc2,pc)) //use the nuw string
; //do nothing
delete[] pc2; //free the memory
}
//string implementation
string str("a very long literal string");
//performance test on string allocation and copy
for(int ix=0;ix!=1000000;++ix){
string str2=str; //do the copy,automatically allocated
if(str!=str2) //use the new string
; //do nothing
} //str2 is automatically freed
6.動態(tài)數(shù)組的使用
通常是因為在編譯時無法知道數(shù)組的維數(shù),所以才需要動態(tài)創(chuàng)建該數(shù)組.例如,在程序執(zhí)行過程中,常常使用char*指針指向多個C風格字符串,于是必須根據(jù)每個字符串的長度實時地動態(tài)分配存儲空間.采用這種技術要比建立固定大小的數(shù)組安全.如果程序員能夠準確計算出運行時需要的數(shù)組長度,就不必再擔心因數(shù)組變量具有固定的長度而造成的溢出問題.
假設有以下C風格字符串:
const char *noerr="success";
//...
const char *err189="Error: a function declaration must "
"specify a function return type!";
我們想在運行時把這兩個字符串中的一個復制給新的字符數(shù)組,于是可以用以下程序在運行時計算維數(shù):
const char *errorTxt;
if(errorFound)
errorTxt=err189;
else
errorTxt=noerr;
//remember the 1 for the terminating null
int dimension=strlen(errorTxt)+1;
char *errMsg=new char[dimension];
//copy the text for the error into errMsg
strncpy (errMsg,errorTxt,dimension);
別忘記標準庫函數(shù)strlen返回的是字符串的長度,并不包括字符串結(jié)束符,在獲得的字符串長度上必須加1以便在動態(tài)分配時預留結(jié)束符的存儲空間.