實(shí)現真正意義上的二維動(dòng)態(tài)數組模板
作者:zyq654321
我們可以通過(guò)動(dòng)態(tài)數組的反例來(lái)確定動(dòng)態(tài)數組應該具有哪些特性。大家都知道以下的方式是定義一個(gè)靜態(tài)數組。
int iCount[10];int iCount[10][10];
從上面可以看出,定義了靜態(tài)數組之后,無(wú)論程序如果使這個(gè)數組,該數組在內存中所占空間的大小,位置是確定不變的。
我們可以得出結論,對于編譯器,靜態(tài)數組的大小和空間是已知的,因此編譯器可以自動(dòng)為該數組分配空間。具體情況是:如果你定義了一個(gè)全局數組,編譯器將在數據區為你的數組分配一個(gè)空間;如果是個(gè)局部數組(比如定義在某一個(gè)局數中),編譯器為你的數組分配一個(gè)棧(Stack)空間。
從靜態(tài)數組的討論中我們得出動(dòng)態(tài)數組應具有的特性:在程序的運行中,動(dòng)態(tài)數組是大小應該是可變的。因些動(dòng)態(tài)組數的實(shí)現應該是基于動(dòng)態(tài)的分配內存基礎上。下面看這個(gè)例子:
假設我們建立一個(gè)工廠(chǎng)工人的數據庫,數據庫中有多個(gè)表各代表不同的車(chē)間。每個(gè)表中保存該車(chē)間職工的信息,為了代碼簡(jiǎn)單,可以只讓數據庫保存職工的姓名。
下面是一個(gè)InputWorkers函數,以車(chē)間為單位輸入全車(chē)間職工姓名,然后一次性將這些數據存入數據庫中。
void InputWorkers(){ int iCountOfWorkers, int iNo; …… 用戶(hù)輸入獲得車(chē)間的人數和車(chē)間號 …… string* iArray = new string[iCountOfWorkers]; …… 用戶(hù)輸入車(chē)間所有職工的信息,并存在iArray數組中 …… StoreInDatabase(iArray, iNo ); //存入數據庫 delete [] iArray;}在程序中iArray是個(gè)string指針,并不是數組。但是數組的原理和指針是一樣的,比如p[1]是指數組p中的第二個(gè)元素,但在實(shí)際尋址中是以p+1進(jìn)行的。所以我們可以這樣使用iArray[1]。InputWorkers中的iArray根據車(chē)間的總人數來(lái)分配不同大小的空間。從這種意義上,可以認為iArray實(shí)現了動(dòng)態(tài)數組的功能。
如果iArray定義為一個(gè)靜態(tài)數組,那么iArray的大小是固定的,因此我們必須估計車(chē)間人數的一個(gè)上限。
string iArray[100];
靜態(tài)數組的速度是快于動(dòng)態(tài)數組。因為從理論上,棧在速度上是快于堆的。但是我們如果決定使用動(dòng)態(tài)數組在是因為節省空間的考慮。另外要注意靜態(tài)數組上限變化帶來(lái)的成本。我們必須重新設定上限以解決這個(gè)bug,然后重新編譯程序。如果你能控制程序的編譯,這沒(méi)問(wèn)題。但是,你要做是的為每一個(gè)用戶(hù)更新程序。沒(méi)有更新的用戶(hù)就可以遇到這個(gè)bug。想到這一點(diǎn),你就快樂(lè )不起來(lái)。
你可能會(huì )說(shuō),我設一下大一點(diǎn)的上限,超出它的可能性會(huì )非常小,而且內存的浪費也不會(huì )多大。比如最多一個(gè)車(chē)間200人,最少一個(gè)車(chē)間100人,那也只浪費了100空間?,F在機器的內存根本不在乎這么一個(gè)空間浪費。是的,你可以這么做,但是請繼續向下討論。
現在我們要將所有職工的姓名存入一個(gè)二維數組,數組的每一行表示一個(gè)車(chē)間,每行中的元素是職工的姓名。想想看,如果用靜態(tài)數組,你會(huì )浪費多空間。而且你還要為車(chē)間數加一個(gè)上限。這個(gè)例子并不好,因為工廠(chǎng)中的車(chē)間數應該是可以確定的。但是我可以換個(gè)角度說(shuō),我只要某幾個(gè)車(chē)間,也可能是所有車(chē)間,那么你是否還堅持呢?
說(shuō)了上面這些,只是少少的討論了一下動(dòng)態(tài)數組可能是使用情況?,F實(shí)中,尤其是大型軟件系統中動(dòng)態(tài)數組的使用其實(shí)很普遍。而且在C++的各種庫中也有數組的實(shí)現的類(lèi),通過(guò)調用相應的類(lèi)函數就可以對數組中的元素實(shí)現增/刪。而且也可以通過(guò)嵌套實(shí)現二維的動(dòng)態(tài)。這些類(lèi)或類(lèi)模板使用起來(lái)很容易。比如:
CAtlArray<int> iArray;iArray[0] = 1; // 出錯,iArray中并沒(méi)有元素iArray.Add(1); // element 2iArray[0] = 1; // 可以,iArray中并有1個(gè)元素iArray[0] = 1; // 出錯,iArray中并只有1個(gè)元素看了上面,你會(huì )覺(jué)得很煩,每當數組擴大時(shí)必須通過(guò)一個(gè)函數Add。但程序員們都會(huì )習慣的,我們會(huì )想這是應該為動(dòng)態(tài)數組付出的代價(jià)。再想一想二維數組,Add動(dòng)作的工作會(huì )讓你很是不爽。你會(huì )懷念靜態(tài)數組的操作方法,直接使用iArray[10] = 10,只要你定義的上限是大于是10的。而下面,我就是要討論這一種方法的實(shí)現。
首先我們希望有這樣的一維數組:
CDynamic1Dim<int> m_dim; // m_dim的大小是1然后執行下面的語(yǔ)句:
m_dim[4] = 710;此時(shí)m_dim的大小是5。
如何使m_dim[4] = 710在數組只有一個(gè)元素的情況下不會(huì )出錯而且增加數組的大小以使該語(yǔ)句成功?最為簡(jiǎn)單的方法是重載operator[]操作符。下面我們討論實(shí)現的細節。
template<typename T>class Dynamic1Dim{public: Dynamic1Dim(); ~Dynamic1Dim(); T& operator[](int index);protected: bool EnlargeDim(int iSize);public: T* m_pBuf; int m_iSize;};上面定義一個(gè)模板類(lèi)Dynamic1Dim<T>。其構造函數如下://--------------------------------------------------// 構造函數template<typename T>Dynamic1Dim<T>::Dynamic1Dim(){ //數組的初始大小的1個(gè)T類(lèi)型對象 //分配一塊內存其大小為T(mén)型類(lèi)所占的空間 m_pBuf = (T*)malloc(sizeof(T)); //在內存空間中建立一個(gè)T型對象 new(m_pBuf) T(); m_iSize = 1;}在初始函數中我們設定數組的默認長(cháng)度為1。當用戶(hù)使用語(yǔ)句m_dim[4] = 710時(shí),重載的操作符被調用。//--------------------------------------------------// operator [] template<typename T>T& Dynamic1Dim<T>::operator [](int index) { // 如果下標是負值,拋出一個(gè)異常 if( index < 0 ) throw std::out_of_range(" Index shouldn''t be negative"); //檢查下標是否超來(lái)數組大小,如果超過(guò)則調用EnlargeDim擴大數組 if(index > m_iSize - 1) EnlargeDim(index + 1); Return m_pBuf [index];}//--------------------------------------------------// Enlargetemplate<typename T>bool Dynamic1Dim<T>::EnlargeDim(int iSize) { // 重新分配一塊內存,其大小為擴大后T類(lèi)型數組的大小 m_pBuf = (string*) realloc(m_pBuf, sizeof(T) * iSize); // 在擴大部分的內存空間上建立T類(lèi)型的數組對象,并調用其默認構造函數 for(int i = m_iSize; i < iSize; i++) { new(&m_pBuf[i]) T(); } m_iSize = iSize; return true;}上面的代碼已基本實(shí)現了動(dòng)態(tài)一維數組的要求。但有一個(gè)點(diǎn)必須當心,就是數組空間的釋放問(wèn)題。在Dynamic1Dim的析構函數中必須釋放動(dòng)態(tài)分配的空間。//--------------------------------------------------// Destructortemplate<typename T>Dynamic1Dim<T>::~Dynamic1Dim(){ // 調用T類(lèi)的析構函數 for(int i = 0; i < m_iSize; i++) { m_pBuf [i].~T(); } // 釋放內存空間 free(m_pBuf);}注意,m_pElem[i].~T()是必要的,因為T(mén)對象中也可能有內存的分配。如果沒(méi)有這句,T對象中分配的內存就無(wú)法釋放,其實(shí)這也是很多內存泄露的原因。上面的代碼實(shí)現了動(dòng)態(tài)一維數組的模板。我們最后就要討論動(dòng)態(tài)二維數組的實(shí)現。
我們會(huì )希望有這樣的二維數組:
CDynamic2Dim<int> m_dim; // m_dim的大小是1*1然后執行下面的語(yǔ)句:
m_dim[1][3] = 33;m_dim[4][10] = 710;此時(shí)m_dim的大小是:0、2、3行都只有一個(gè)元素,1行有4個(gè)元素,4行有11個(gè)元素。 可以這樣設想,動(dòng)態(tài)二維數組是由數目不定的動(dòng)態(tài)一維數組組成的?;谶@種想法,我們看一下動(dòng)態(tài)二維數組的實(shí)現。
template<typename T>class Dynamic2Dim{public: Dynamic2Dim(); ~Dynamic2Dim(); Dynamic1Dim<T>& operator[] (int index);protected: bool EnlargeY(int nYSize);private: int m_iYSize; Dynamic1Dim<T>* m_pYBuf; Dynamic1Dim<T> m;};初始的二維數組應該是1*1大小的,因此Dynamic2Dim的構造函數應該如下// Constructortemplate<typename T>Dynamic2Dim<T>::Dynamic2Dim(){ m_iYSize = 1; m_pYBuf = (Dynamic1Dim<T>*) malloc(sizeof(Dynamic1Dim<T>)); m_pYElem = new(m_pYBuf) Dynamic1Dim<T>();}在析構函數中釋放分配的內存空間:// Desctructortemplate<typename T>Dynamic2Dim<T>::~Dynamic2Dim(){ for(int i = 0; i < m_iYSize; i++) { m_pYElem[i].~Dynamic1Dim(); } free(m_pYBuf);}需要為動(dòng)態(tài)二維數組重載操作符[],其實(shí)現如下// operator[] overloadtemplate<typename T>Dynamic1Dim<T>& Dynamic2Dim<T>::operator[] (int index){ if(index < 0) throw std::out_of_range("negative index!"); if(index > m_iYSize - 1) EnlargeY(index + 1); return m_pYElem[index];}從上我們可以知道,這里實(shí)現的是二維數組的縱向擴大,即根據二維數組的第一下標在決定是否擴大二維數組。這里須要注意的是返回值是一個(gè)一維動(dòng)態(tài)數組,由于一維動(dòng)態(tài)數組也重載了[]操作符,所以用戶(hù)可以最終得到一個(gè)指定的二維數組元素的引用(其類(lèi)型為T(mén))。以上就是一個(gè)動(dòng)態(tài)二維數組的基本實(shí)現,說(shuō)它是基本實(shí)現,我是指它可以工作,但實(shí)際使用應該注意下而幾個(gè)問(wèn)題。
1.數組的動(dòng)態(tài)擴張是否在我們所期望的情況下進(jìn)行的??聪旅娴睦樱?/p>
Dynamic2Dim<string> arrString;arrString[3][4] = "34";string str = arrString[6][6];根據動(dòng)態(tài)數組的定義,可以確定動(dòng)態(tài)二維數組進(jìn)行了二次擴張,第一次數組空間為4*n,這是我們期望的;第二次為7*n,在大多數情況下這不是我們期望的。(這里使用n是因為二維數組的行元素數目是不同的。)
在這里我給出一個(gè)解決的方法??梢允褂么眍?lèi)(proxy class)來(lái)區別上面二種情況,在第二種情況下可以?huà)伋鲆粋€(gè)異常。
2.動(dòng)態(tài)分配空間的大小。malloc須要調用操作系統的低級操作,我們不希望頻繁調用它,因此可以預先分配較大一些的空間。例如:用戶(hù)使用下標5時(shí),我們分配5*2的空間。
3.realloc的問(wèn)題。在已經(jīng)分配了較大內存空間時(shí),realloc會(huì )引起很大的開(kāi)銷(xiāo)(它必須進(jìn)行內存的拷貝以保持原有數據)。此時(shí)我們可以考慮使用malloc只分配所須的新的空間,盡管這樣有點(diǎn)復雜,但相比大塊的內存拷貝帶來(lái)的開(kāi)銷(xiāo)還是值得的。
4.因為動(dòng)態(tài)二維數組操作符[]返回的是一個(gè)動(dòng)態(tài)一維數組的引用,所以與普通二維數組相比,它有一些限制。
Dynamic2Dim<string> dim1;string dim2[10][10];string *p;p = dim2[3]; //Okp = dim1[3]; //Error. 因為dim1[3]返回的是Dynamic1Dim<string>類(lèi)型,而不是string類(lèi)型。5.在實(shí)際使用時(shí),可以增加一個(gè)函數,返回當前數組的大小??梢允褂胕nline來(lái)減小引入其帶來(lái)的開(kāi)銷(xiāo)。
6.從二維動(dòng)態(tài)對象(不是指針)數組的角度,以上代碼并不適用于指針。
聯(lián)系客服