C++動(dòng)態(tài)數(shù)組類(lèi)的封裝實(shí)例
C++中的動(dòng)態(tài)數(shù)組(Dynamic Array)是指動(dòng)態(tài)分配的、可以根據(jù)需求動(dòng)態(tài)增長(zhǎng)占用內(nèi)存的數(shù)組。為了實(shí)現(xiàn)一個(gè)動(dòng)態(tài)數(shù)組類(lèi)的封裝,我們需要考慮幾個(gè)問(wèn)題:new/delete的使用、內(nèi)存分配策略、類(lèi)的四大函數(shù)(構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、拷貝賦值運(yùn)算符、析構(gòu)函數(shù))、運(yùn)算符的重載。涉及到的知識(shí)點(diǎn)很多,對(duì)此本文只做簡(jiǎn)單的介紹。
一、內(nèi)存分配策略
當(dāng)用new為一個(gè)動(dòng)態(tài)數(shù)組申請(qǐng)一塊內(nèi)存時(shí),數(shù)組中的元素是連續(xù)存儲(chǔ)的,例如 vector和string。當(dāng)向一個(gè)動(dòng)態(tài)數(shù)組添加元素時(shí),如果沒(méi)有空間容納新元素,不可能簡(jiǎn)單地將新元素添加到內(nèi)存中的其他位置——因?yàn)樵乇仨氝B續(xù)存儲(chǔ)。所以必須重新分配一塊更大的內(nèi)存空間,將原來(lái)的元素從舊位置移動(dòng)到新空間中,然后添加新元素,釋放舊的內(nèi)存空間。如果我們每添加一個(gè)新元素,就執(zhí)行一次這樣的內(nèi)存分配和釋放操作,效率將會(huì)慢到不行。
為了避免上述的代價(jià),必須減少內(nèi)存重新分配的次數(shù)。所以我們采取的策略是:在不得不分配新的內(nèi)存空間時(shí),分配比新的空間需求更大的內(nèi)存空間(通常為2倍)。這樣,在相當(dāng)一段時(shí)間內(nèi),添加元素時(shí)就不用重新申請(qǐng)內(nèi)存空間。注意,只有當(dāng)迫不得已時(shí)才可以分配新的內(nèi)存空間。
二、類(lèi)的四大函數(shù)
一個(gè)C++類(lèi)一般至少有四大函數(shù),即構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、拷貝賦值運(yùn)算符、析構(gòu)函數(shù)。如果類(lèi)未自己定義上述函數(shù),C++編譯器將為其合成4個(gè)默認(rèn)的版本。但是往往編譯器合成的并不是我們所期望的,為此我們有必要自己定義它們。
1.構(gòu)造函數(shù)
類(lèi)的構(gòu)造函數(shù)(constructor)用來(lái)初始化類(lèi)對(duì)象的非static數(shù)據(jù)成員,無(wú)論何時(shí)只要類(lèi)的對(duì)象被創(chuàng)建,就會(huì)執(zhí)行構(gòu)造函數(shù)。
class Foo { public: Foo(); // 構(gòu)造函數(shù) Foo(string &s); // ... };
構(gòu)造函數(shù)的名字和類(lèi)名相同,沒(méi)有返回類(lèi)型。類(lèi)可以包含多個(gè)構(gòu)造函數(shù)(重載),它們之間在參數(shù)數(shù)量或類(lèi)型上需要有所區(qū)別。構(gòu)造函數(shù)有一個(gè)初始化部分和一個(gè)函數(shù)體,成員的初始化是在函數(shù)體執(zhí)行之前完成的。
2.拷貝構(gòu)造函數(shù)
如果一個(gè)構(gòu)造函數(shù)的第一個(gè)參數(shù)是自身類(lèi)類(lèi)型的引用,且任何額外參數(shù)都有默認(rèn)值,則此構(gòu)造函數(shù)是拷貝構(gòu)造函數(shù)(copy constructor)。
class Foo { public: Foo(); Foo(const Foo&); // 拷貝構(gòu)造函數(shù) // ... };
拷貝構(gòu)造函數(shù)定義了如何用一個(gè)對(duì)象初始化另一個(gè)同類(lèi)型的對(duì)象??截惓跏蓟ǔJ褂每截悩?gòu)造函數(shù)來(lái)完成??截惓跏蓟l(fā)生在下列情況中:
使用等號(hào)(=)初始化一個(gè)變量
將一個(gè)對(duì)象作為實(shí)參傳遞給一個(gè)非引用類(lèi)型的形參
從一個(gè)返回類(lèi)型為非引用類(lèi)型的函數(shù)返回一個(gè)對(duì)象
用花括號(hào)列表初始化一個(gè)數(shù)組中的元素
3.拷貝賦值運(yùn)算符
類(lèi)的拷貝賦值運(yùn)算符(copy-assignment operator)是一個(gè)名為operator=的函數(shù)。類(lèi)似于其他任何函數(shù),它也有一個(gè)返回類(lèi)型和一個(gè)參數(shù)列表。
class Foo { public: Foo(); Foo& operator=(const Foo&); // 賦值運(yùn)算符 // ... };
拷貝賦值運(yùn)算符定義了如何將一個(gè)對(duì)象賦值給另一個(gè)同類(lèi)型的對(duì)象。賦值運(yùn)算符是一個(gè)成員函數(shù)也是一個(gè)二元運(yùn)算符,其左側(cè)運(yùn)算對(duì)象就綁定到隱式的this指針,右側(cè)運(yùn)算對(duì)象作為顯式參數(shù)傳遞。注意:為了與內(nèi)置類(lèi)型的賦值保持一致,賦值運(yùn)算符通常返回一個(gè)指向其左側(cè)運(yùn)算對(duì)象的引用。
4.析構(gòu)函數(shù)
類(lèi)的析構(gòu)函數(shù)(destructor)用來(lái)釋放類(lèi)對(duì)象使用的資源并銷(xiāo)毀類(lèi)對(duì)象的非static數(shù)據(jù)成員,無(wú)論何時(shí)只要一個(gè)對(duì)象被銷(xiāo)毀,就會(huì)自動(dòng)執(zhí)行析構(gòu)函數(shù)。
class Foo { public: ~Foo(); // 析構(gòu)函數(shù) // ... };
析構(gòu)函數(shù)的名字由波浪號(hào)(~)加類(lèi)名構(gòu)成,也沒(méi)有返回類(lèi)型。由于析構(gòu)函數(shù)不接受參數(shù),因此它不能被重載。析構(gòu)函數(shù)有一個(gè)函數(shù)體和一個(gè)析構(gòu)部分,銷(xiāo)毀一個(gè)對(duì)象時(shí),首先執(zhí)行析構(gòu)函數(shù)體,然后按初始化順序的逆序銷(xiāo)毀成員。
三、運(yùn)算符的重載
重載的運(yùn)算符是具有特殊名字的函數(shù):它們的名字由關(guān)鍵字operator和其后要定義的運(yùn)算符號(hào)共同組成。和其他函數(shù)一樣,重載的運(yùn)算符也包含返回類(lèi)型、參數(shù)列表、函數(shù)體,比如拷貝賦值運(yùn)算符。
當(dāng)我們定義重載的運(yùn)算符時(shí),必須首先決定是將其聲明為類(lèi)的成員函數(shù)還是聲明為一個(gè)普通的非成員函數(shù)。有些運(yùn)算符必須作為成員,而另一些運(yùn)算符作為普通函數(shù)比作為成員更好:
賦值(=)、下標(biāo)([ ])、調(diào)用(( ))和成員訪問(wèn)箭頭(->)運(yùn)算符必須是成員。
復(fù)合賦值運(yùn)算符一般來(lái)說(shuō)應(yīng)該是成員,但并非必須,這一點(diǎn)與賦值運(yùn)算符略有不同。
改變對(duì)象狀態(tài)的運(yùn)算符或者與給定類(lèi)型密切相關(guān)的運(yùn)算符,如遞增、遞減、解引用運(yùn)算符,通常應(yīng)該是成員。
具有對(duì)稱(chēng)性的運(yùn)算符可能轉(zhuǎn)換任意一端的運(yùn)算對(duì)象,例如算術(shù)、相等性、關(guān)系和位運(yùn)算符等,因此它們通常應(yīng)該是普通的非成員函數(shù)。
當(dāng)然,除了賦值運(yùn)算符之外,我們還需要為動(dòng)態(tài)數(shù)組定義下標(biāo)運(yùn)算符operator []。下標(biāo)運(yùn)算符必須是成員函數(shù)。為了讓下標(biāo)可以出現(xiàn)在賦值運(yùn)算符的任意一端,下標(biāo)運(yùn)算符函數(shù)通常返回所訪問(wèn)元素的引用。
四、動(dòng)態(tài)數(shù)組類(lèi)的封裝
下面給出了動(dòng)態(tài)數(shù)組DArray類(lèi)的接口:
class DArray { private: double *m_Data; // 存放數(shù)組的動(dòng)態(tài)內(nèi)存指針 int m_Size; // 數(shù)組的元素個(gè)數(shù) int m_Max; // 預(yù)留給動(dòng)態(tài)數(shù)組的內(nèi)存大小 private: void Init(); // 初始化 void Free(); // 釋放動(dòng)態(tài)內(nèi)存 inline bool InvalidateIndex(int nIndex); // 判斷下標(biāo)的合法性 public: DArray(); // 默認(rèn)構(gòu)造函數(shù) DArray(int nSize, double dValue = 0); // 構(gòu)造函數(shù),設(shè)置數(shù)組大小,默認(rèn)值為dValue DArray(const DArray& arr); // 拷貝構(gòu)造函數(shù) DArray& operator=(const DArray& arr); // 拷貝賦值運(yùn)算符 ~DArray(); // 析構(gòu)函數(shù) void Print(); // 輸出顯式所有數(shù)組元素的值 int GetSize(); // 獲取數(shù)組的大?。ㄔ貍€(gè)數(shù)) void SetSize(int nSize); // 重新設(shè)置數(shù)組的大小,若nSize小于原大小,截?cái)?;否則,新元素置0 double GetAt(int nIndex); // 獲取指定位置元素 void SetAt(int nIndex,double dValue); // 重置指定元素的值 void PushBack(double dValue); // 追加一個(gè)新元素到數(shù)組末尾 void DeleteAt(int nIndex); // 刪除指定位置地元素 void InsertAt(int nIndex, double dValue); // 插入一個(gè)新的元素到數(shù)組中 double operator[](int nIndex) const; // 重載下標(biāo)運(yùn)算符[] };
下面是實(shí)現(xiàn)方法:
void DArray::Init() { m_Size = 0; // 默認(rèn)情況下數(shù)組不包含元素 m_Max = 1; m_Data = new double[m_Max]; } void DArray::Free() { delete [] m_Data; } bool DArray::InvalidateIndex(int nIndex) { if(nIndex>=0 && nIndex<m_Size) return false; else return true; } // 默認(rèn)構(gòu)造函數(shù) DArray::DArray() { Init(); } // 構(gòu)造函數(shù) DArray::DArray(int nSize, double dValue) { if(nSize == 0) Init(); else { m_Size = nSize; m_Max = nSize; m_Data = new double[m_Max]; for(int i=0; i<nSize; ++i) m_Data[i]=dValue; } } // 拷貝構(gòu)造函數(shù) DArray::DArray(const DArray& arr) { m_Size = arr.m_Size; /*復(fù)制常規(guī)成員*/ m_Max = arr.m_Max; m_Data = new double[m_Max]; /*復(fù)制指針指向的內(nèi)容*/ memcpy(m_Data, arr.m_Data, m_Size*sizeof(double)); } // 拷貝賦值運(yùn)算符 DArray& DArray::operator=(const DArray& arr) { if(this == &arr) /*自賦值*/ return *this; m_Size = arr.m_Size; m_Max = arr.m_Max; /* 先將右側(cè)對(duì)象拷貝到臨時(shí)對(duì)象中,然后再銷(xiāo)毀左側(cè)對(duì)象*/ double *m_Temp = new double[m_Max]; memcpy(m_Temp, arr.m_Data, m_Size*sizeof(double)); delete [] m_Data; m_Data = m_Temp; return *this; } // 析構(gòu)函數(shù) DArray::~DArray() { Free(); } // 打印數(shù)組 void DArray::Print() { if(m_Size == 0) { cout << "Error: The empty array can't be Printed." << endl; exit(0); } else { for(int i=0; i<m_Size; ++i) cout << m_Data[i] << " "; cout << endl; } } // 獲取數(shù)組大小 int DArray::GetSize() { return m_Size; } // 重置數(shù)組大小 void DArray::SetSize(int nSize) { if(nSize < m_Size) /*截?cái)?/ { for(int i=nSize; i<m_Size; ++i) m_Data[i] = 0; } if(m_Size<=nSize && nSize<=m_Max) /*新增元素置0*/ { for(int i=m_Size; i<nSize; ++i) m_Data[i] = 0; } if(nSize > m_Max) /*需要重新分配空間*/ { m_Max = nSize; double *temp = new double[m_Max]; memcpy(temp, m_Data, m_Size*sizeof(double)); for(int i=m_Size; i<nSize; ++i) temp[i] = 0; delete [] m_Data; m_Data = temp; } m_Size = nSize; /*設(shè)置數(shù)組大小*/ } // 獲取指定位置元素 double DArray::GetAt(int nIndex) { if(InvalidateIndex(nIndex)) { cout << "Error: the index of GetAt is invalid!" << endl; exit(0); } return m_Data[nIndex]; } // 設(shè)置指定位置元素的值 void DArray::SetAt(int nIndex, double dValue) { if(InvalidateIndex(nIndex)) { cout << "Error: the index of SetAt is invalid!" << endl; exit(0); } else { m_Data[nIndex] = dValue; } } // 追加一個(gè)新元素到數(shù)組末尾 void DArray::PushBack(double dValue) { if(m_Size < m_Max) { m_Data[m_Size] = dValue; } else { m_Max = m_Max*2; double* temp = new double[m_Max]; memcpy(temp, m_Data, m_Size*sizeof(double)); delete [] m_Data; m_Data = temp; m_Data[m_Size] = dValue; } ++m_Size; /*數(shù)組大小加1*/ } // 從數(shù)組中刪除一個(gè)元素 void DArray::DeleteAt(int nIndex) { if(InvalidateIndex(nIndex)) { cout << "Error: the index of DeleteAt is invalid." << endl; exit(0); } else { for(int i=nIndex; i<m_Size; ++i) m_Data[i] = m_Data[i+1]; m_Data[m_Size-1] = 0; --m_Size; } } // 插入一個(gè)新元素到指定位置 void DArray::InsertAt(int nIndex, double dValue) { if(nIndex<0 || nIndex>m_Size) { cout << "Error: the index of InsertAt is invalid!" << endl; exit(0); } if(m_Size < m_Max) /* 未滿(mǎn),插入 */ { for(int i=m_Size-1; i>=nIndex; --i) m_Data[i+1] = m_Data[i]; m_Data[nIndex] = dValue; } else /* 重新分配空間 */ { m_Max = m_Max*2; double* temp = new double[m_Max]; memcpy(temp, m_Data, m_Size*sizeof(double)); delete [] m_Data; m_Data = temp; for(int i=m_Size-1; i>=nIndex; --i) m_Data[i+1] = m_Data[i]; m_Data[nIndex] = dValue; } ++m_Size; /* 數(shù)組大小加1 */ } // 重載下標(biāo)運(yùn)算符[] double DArray::operator[](int nIndex) const { if(nIndex<0 || nIndex>=m_Size) { cout << "Error: the index in [] is invalid!" << endl; exit(0); } return m_Data[nIndex]; }
經(jīng)過(guò)簡(jiǎn)單的測(cè)試,暫時(shí)還沒(méi)有發(fā)現(xiàn)Bug。可能測(cè)試并不全面,感興趣的讀者可以進(jìn)一步測(cè)試并完善該程序。
附:String類(lèi)的實(shí)現(xiàn)
C++ 的一個(gè)常見(jiàn)面試題是讓你實(shí)現(xiàn)一個(gè) String 類(lèi),限于時(shí)間,不可能要求具備 std::string 的功能,但至少要求能正確管理資源。
如果你弄懂了上面DArray類(lèi)的寫(xiě)法,那么實(shí)現(xiàn)String類(lèi)應(yīng)該就不難了。因?yàn)槊嬖嚬僖话阒皇窍肟疾槟隳懿荒苷_地寫(xiě)出構(gòu)造函數(shù)、析構(gòu)函數(shù)、拷貝構(gòu)造函數(shù)、拷貝賦值運(yùn)算符以及+、[ ]、<<、>>運(yùn)算符重載等等。下面給出一個(gè)String類(lèi)的接口,你可以自己試試手實(shí)現(xiàn)一下:
class String{ friend ostream& operator<< (ostream&,String&); //重載<<運(yùn)算符 friend istream& operator>> (istream&,String&); //重載>>運(yùn)算符 public: String(); // 默認(rèn)構(gòu)造函數(shù) String(const char* str); // 帶參構(gòu)造函數(shù) String(const String& rhs); // 拷貝構(gòu)造函數(shù) String& operator=(const String& rhs); // 拷貝賦值運(yùn)算符 String operator+(const String& rhs) const; //operator+ bool operator==(const String&); //operator== bool operator!=(const String&); //operator!= char& operator[](unsigned int); //operator[] size_t size() const; const char* c_str() const; ~String(); // 析構(gòu)函數(shù) private: char *m_data; // 用于保存字符串 };
本文所述DArray類(lèi)和String類(lèi)的源碼及測(cè)試代碼可點(diǎn)擊此處本站下載。
上一篇:Windows網(wǎng)絡(luò)編程之winsock實(shí)現(xiàn)文件傳輸示例
欄 目:C語(yǔ)言
下一篇:C++深度優(yōu)先搜索的實(shí)現(xiàn)方法
本文標(biāo)題:C++動(dòng)態(tài)數(shù)組類(lèi)的封裝實(shí)例
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/3488.html
您可能感興趣的文章
- 04-02c語(yǔ)言沒(méi)有round函數(shù) round c語(yǔ)言
- 01-10求子數(shù)組最大和的解決方法詳解
- 01-10深入理解C++中常見(jiàn)的關(guān)鍵字含義
- 01-10使用C++實(shí)現(xiàn)全排列算法的方法詳解
- 01-10c++中inline的用法分析
- 01-10如何尋找數(shù)組中的第二大數(shù)
- 01-10用C++實(shí)現(xiàn)DBSCAN聚類(lèi)算法
- 01-10全排列算法的非遞歸實(shí)現(xiàn)與遞歸實(shí)現(xiàn)的方法(C++)
- 01-10C++大數(shù)模板(推薦)
- 01-10淺談C/C++中的static與extern關(guān)鍵字的使用詳解


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 04-02c語(yǔ)言函數(shù)調(diào)用后清空內(nèi)存 c語(yǔ)言調(diào)用
- 04-02func函數(shù)+在C語(yǔ)言 func函數(shù)在c語(yǔ)言中
- 04-02c語(yǔ)言的正則匹配函數(shù) c語(yǔ)言正則表達(dá)
- 04-02c語(yǔ)言用函數(shù)寫(xiě)分段 用c語(yǔ)言表示分段
- 04-02c語(yǔ)言中對(duì)數(shù)函數(shù)的表達(dá)式 c語(yǔ)言中對(duì)
- 04-02c語(yǔ)言編寫(xiě)函數(shù)冒泡排序 c語(yǔ)言冒泡排
- 04-02c語(yǔ)言沒(méi)有round函數(shù) round c語(yǔ)言
- 04-02c語(yǔ)言分段函數(shù)怎么求 用c語(yǔ)言求分段
- 04-02C語(yǔ)言中怎么打出三角函數(shù) c語(yǔ)言中怎
- 04-02c語(yǔ)言調(diào)用函數(shù)求fibo C語(yǔ)言調(diào)用函數(shù)求
隨機(jī)閱讀
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-10delphi制作wav文件的方法
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 01-10C#中split用法實(shí)例總結(jié)
- 04-02jquery與jsp,用jquery