VC动态数组实现
VC動態數組實現
數組類型的變量有三個重要的限制:數組長度固定不變,在編譯時必須知道其長度,數組只在定義它的塊語句內存在.
實際的程序往往不能忍受這樣的限制-------它們需要在運行時 動態地分配數組.雖然數組長度是固定的,但動態分配的數組不必在編譯時知道其長度,可以(通常也是)在運行時才確定數組長度.與數組變量不同,動態分配的數組將一直存在,知道程序顯式釋放它為止.
每一個程序在執行時都占用一款可用的內存空間,用于存放動態分配的對象,此內存空間稱為程序的自由存儲區(free store)或堆(heap).C語言程序使用一對標準庫函數malloc和free在自由存儲區中分配存儲空間,而C++語言則使用new和delete表達式實現相同的功能。
1.動態數組的定義
數組變量通過指定類型、數組名和維數來定義.而動態分配數組時,只需指定類型和數組長度,不必為數組對象命名,new表達式返回指向新分配數組的第一個元素的指針:
int *pia=new int[10]; //array of 10 uninitialized ints
此new表達式分配了一個含有10個int型元素的數組,并返回指向該數組第一個元素的指針,此返回值初始化了之怎pia.
new表達式需要指定指針類型以及在方括號中給出的數組維數,該維數可以是任意的復雜表達式.創建數組后,new將返回指向數組第一個元素的指針.在自由存儲區中創建的數組對象是沒有名字的,程序員只能通過其地址間接地訪問堆中的對象.
2.初始化動態分配的數組
動態分配數組時,如果數組元素具有類類型,將使用該類的默認構造函數實現初始化;如果數組元素是內置類型,則無初始化:
string *psa=new string[10]; //array of 10 empty strings
int *pia=new int[10];?? //array of 10 ninitialized ints
這兩個new表達式都分配了含有10個對象的數組.其中第一個數組是string類型,分配了保存對象的內存空間后,將調用string類型的默認構造函數依次初始化數組中的每個元素(實際上string對象都是空串,結尾無‘\0’).第二個數組則具有內置類型的元素,分配了存儲10個int對象的內存空間,但這些元素沒有初始化.
也可使用跟在數組長度后面的一對空圓括號,對數組元素做值初始化:
int *pia2=new int[10](); //array of 10 uninitialized ints
圓括號要求編譯器對數組做值初始化,在本例中即把數組元素都設置為0.
注解:對于動態分配的數組,其元素只能初始化為元素類型的默認值,而不能像數組變量一樣,用初始化列表為數組元素提供各不相同的初值.
3.const對象的動態數組
如果我們在自由存儲區中創建的數組存儲了內置類型的const對象,則必須為這個數組提供初始化:因為數組元素都是const對象,無法賦值.實現這個要求的唯一方法是對數組做值的初始化:
//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數組,但該類類型必須提供默認構造函數:
//ok:array of 100 empty strings
const string *pcs=new const string[100];
在這里,將使用string類的默認構造函數初始化數組元素.
當然,已創建的常量元素不允許修改------因此這樣的數組實際上用處不大.
4.允許動態分配空數組
之所以要動態分配數組,往往是由于編譯時并不知道數組的長度.我們可以編寫如下代碼
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 */;
計算數組長度,然后創建和處理該數組.
有趣的是,如果get_size返回0會怎么樣?答案是:代碼仍然正確執行.C++雖然不允許定義長度為0的數組變量,但明確指出,調用new動態創建長度為0的數組是合法的:
char arr[0];?? //error:cannot define zero-length array
char *cp=new char[0]; //ok:but cp can't be dereferenced
用new動態創建長度為0的數組時,new返回有效的非零指針.該指針與new返回的其他指針不同,不能進行解引用操作,因為它畢竟沒有指向任何元素.而允許的操作包括:比較運算,因此該指針能在循環中使用;在該指針上加(減)0,或者減去本身值,得0值.
在上述例題中,如果get_size返回0,則仍然可以成功調用new,但是p并沒有指向任何對象,數組是空的.因為n為0,所以for循環實際比較的是p和q,而q是用p初始化的,兩者具有相等的值,因此for循環條件不成立,循環體一次都沒有執行
5.動態空間的釋放
動態分配的內存最后必須進行釋放,否則,內存最終將會逐漸耗盡.如果不再需要使用動態創建的數組,程序員必須顯式地將其占用的存儲空間返還給程序的自由存儲區.C++語言為指針提供delete[]表達式釋放指針所指向的數組空間:
delete[] pia;
該語句回收了pia所指向的數組,把相應的內存返還給自由存儲區.在關鍵字delete和指針之間的空方括號對是必不可少的:它告訴編譯器該指針指向的是自由存儲區中的數組,而并非單個對象.
小心:如果遺漏了空方括號對,這是一個編譯器無法發現的錯誤,將導致程序在運行時出錯.
理論上,回收數組時缺少空方括號對,至少會導致運行時少釋放了內存空間,從而產生內存泄漏(memory leak).對于某些系統和/或元素類型,有可能會帶來更嚴重的運行時錯誤.因此,在釋放動態數組時千萬別忘了方括號對.
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.動態數組的使用
通常是因為在編譯時無法知道數組的維數,所以才需要動態創建該數組.例如,在程序執行過程中,常常使用char*指針指向多個C風格字符串,于是必須根據每個字符串的長度實時地動態分配存儲空間.采用這種技術要比建立固定大小的數組安全.如果程序員能夠準確計算出運行時需要的數組長度,就不必再擔心因數組變量具有固定的長度而造成的溢出問題.
假設有以下C風格字符串:
const char *noerr="success";
//...
const char *err189="Error: a function declaration must "
?????????????????? "specify a function return type!";
我們想在運行時把這兩個字符串中的一個復制給新的字符數組,于是可以用以下程序在運行時計算維數:
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);
別忘記標準庫函數strlen返回的是字符串的長度,并不包括字符串結束符,在獲得的字符串長度上必須加1以便在動態分配時預留結束符的存儲空間.
總結
- 上一篇: django mysql 过滤所有id_
- 下一篇: 如何在VC中创建动态数组