C++靜態數組類的封裝實例。本站提示廣大學習愛好者:(C++靜態數組類的封裝實例)文章只能為提供參考,不一定能成為您想要的結果。以下是C++靜態數組類的封裝實例正文
C++中的靜態數組(Dynamic Array)是指靜態分派的、可以依據需求靜態增加占用內存的數組。為了完成一個靜態數組類的封裝,我們須要斟酌幾個成績:new/delete的應用、內存分派戰略、類的四年夜函數(結構函數、拷貝結構函數、拷貝賦值運算符、析構函數)、運算符的重載。觸及到的常識點許多,對此本文只做簡略的引見。
1、內存分派戰略
當用new為一個靜態數組請求一塊內存時,數組中的元素是持續存儲的,例如 vector和string。當向一個靜態數組添加元素時,假如沒有空間包容新元素,弗成能簡略地將新元素添加到內存中的其他地位——由於元素必需持續存儲。所以必需從新分派一塊更年夜的內存空間,將本來的元素從舊地位挪動到新空間中,然後添加新元素,釋放舊的內存空間。假如我們每添加一個新元素,就履行一次如許的內存分派和釋放操作,效力將會慢到不可。
為了不上述的價值,必需削減內存從新分派的次數。所以我們采用的戰略是:在不能不分派新的內存空間時,分派比新的空間需求更年夜的內存空間(平日為2倍)。如許,在相當一段時光內,添加元素時就不消從新請求內存空間。留意,只要當必不得已時才可以分派新的內存空間。
2、類的四年夜函數
一個C++類普通至多有四年夜函數,即結構函數、拷貝結構函數、拷貝賦值運算符、析構函數。假如類未本身界說上述函數,C++編譯器將為其分解4個默許的版本。然則常常編譯器分解的其實不是我們所希冀的,為此我們有需要本身界說它們。
1.結構函數
類的結構函數(constructor)用來初始化類對象的非static數據成員,不管什麼時候只需類的對象被創立,就會履行結構函數。
class Foo { public: Foo(); // 結構函數 Foo(string &s); // ... };
結構函數的名字和類名雷同,沒有前往類型。類可以包括多個結構函數(重載),它們之間在參數數目或類型上須要有所差別。結構函數有一個初始化部門和一個函數體,成員的初始化是在函數體履行之前完成的。
2.拷貝結構函數
假如一個結構函數的第一個參數是本身類類型的援用,且任何額定參數都有默許值,則此結構函數是拷貝結構函數(copy constructor)。
class Foo { public: Foo(); Foo(const Foo&); // 拷貝結構函數 // ... };
拷貝結構函數界說了若何用一個對象初始化另外一個同類型的對象。拷貝初始化平日應用拷貝結構函數來完成。拷貝初始化產生鄙人列情形中:
應用等號(=)初始化一個變量
將一個對象作為實參傳遞給一個非援用類型的形參
從一個前往類型為非援用類型的函數前往一個對象
用花括號列表初始化一個數組中的元素
3.拷貝賦值運算符
類的拷貝賦值運算符(copy-assignment operator)是一個名為operator=的函數。相似於其他任何函數,它也有一個前往類型和一個參數列表。
class Foo { public: Foo(); Foo& operator=(const Foo&); // 賦值運算符 // ... };
拷貝賦值運算符界說了若何將一個對象賦值給另外一個同類型的對象。賦值運算符是一個成員函數也是一個二元運算符,其左邊運算對象就綁定到隱式的this指針,右邊運算對象作為顯式參數傳遞。留意:為了與內置類型的賦值堅持分歧,賦值運算符平日前往一個指向其左邊運算對象的援用。
4.析構函數
類的析構函數(destructor)用來釋放類對象應用的資本並燒毀類對象的非static數據成員,不管什麼時候只需一個對象被燒毀,就會主動履行析構函數。
class Foo { public: ~Foo(); // 析構函數 // ... };
析構函數的名字由海浪號(~)加類名組成,也沒有前往類型。因為析構函數不接收參數,是以它不克不及被重載。析構函數有一個函數體和一個析構部門,燒毀一個對象時,起首履行析構函數體,然後按初始化次序的逆序燒毀成員。
3、運算符的重載
重載的運算符是具有特別名字的函數:它們的名字由症結字operator和厥後要界說的運算符號配合構成。和其他函數一樣,重載的運算符也包括前往類型、參數列表、函數體,好比拷貝賦值運算符。
當我們界說重載的運算符時,必需起首決議是將其聲明為類的成員函數照樣聲明為一個通俗的非成員函數。有些運算符必需作為成員,而另外一些運算符作為通俗函數比作為成員更好:
賦值(=)、下標([ ])、挪用(( ))和成員拜訪箭頭(->)運算符必需是成員。
復合賦值運算符普通來講應當是成員,但並不是必需,這一點與賦值運算符略有分歧。
轉變對象狀況的運算符或許與給定類型親密相干的運算符,如遞增、遞加、解援用運算符,平日應當是成員。
具有對稱性的運算符能夠轉換隨意率性一真個運算對象,例如算術、相等性、關系和位運算符等,是以它們平日應當是通俗的非成員函數。
固然,除賦值運算符以外,我們還須要為靜態數組界說下標運算符operator []。下標運算符必需是成員函數。為了讓下標可以湧現在賦值運算符的隨意率性一端,下標運算符函數平日前往所拜訪元素的援用。
4、靜態數組類的封裝
上面給出了靜態數組DArray類的接口:
class DArray { private: double *m_Data; // 寄存數組的靜態內存指針 int m_Size; // 數組的元素個數 int m_Max; // 預留給靜態數組的內存年夜小 private: void Init(); // 初始化 void Free(); // 釋放靜態內存 inline bool InvalidateIndex(int nIndex); // 斷定下標的正當性 public: DArray(); // 默許結構函數 DArray(int nSize, double dValue = 0); // 結構函數,設置數組年夜小,默許值為dValue DArray(const DArray& arr); // 拷貝結構函數 DArray& operator=(const DArray& arr); // 拷貝賦值運算符 ~DArray(); // 析構函數 void Print(); // 輸入顯式一切數組元素的值 int GetSize(); // 獲得數組的年夜小(元素個數) void SetSize(int nSize); // 從新設置數組的年夜小,若nSize小於原年夜小,截斷;不然,新元素置0 double GetAt(int nIndex); // 獲得指定地位元素 void SetAt(int nIndex,double dValue); // 重置指定元素的值 void PushBack(double dValue); // 追加一個新元素到數組末尾 void DeleteAt(int nIndex); // 刪除指定地位地元素 void InsertAt(int nIndex, double dValue); // 拔出一個新的元素到數組中 double operator[](int nIndex) const; // 重載下標運算符[] };
上面是完成辦法:
void DArray::Init() { m_Size = 0; // 默許情形下數組不包括元素 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; } // 默許結構函數 DArray::DArray() { Init(); } // 結構函數 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; } } // 拷貝結構函數 DArray::DArray(const DArray& arr) { m_Size = arr.m_Size; /*復制慣例成員*/ m_Max = arr.m_Max; m_Data = new double[m_Max]; /*復制指針指向的內容*/ memcpy(m_Data, arr.m_Data, m_Size*sizeof(double)); } // 拷貝賦值運算符 DArray& DArray::operator=(const DArray& arr) { if(this == &arr) /*自賦值*/ return *this; m_Size = arr.m_Size; m_Max = arr.m_Max; /* 先將右邊對象拷貝莅臨時對象中,然後再燒毀左邊對象*/ 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; } // 析構函數 DArray::~DArray() { Free(); } // 打印數組 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; } } // 獲得數組年夜小 int DArray::GetSize() { return m_Size; } // 重置數組年夜小 void DArray::SetSize(int nSize) { if(nSize < m_Size) /*截斷*/ { 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; /*設置數組年夜小*/ } // 獲得指定地位元素 double DArray::GetAt(int nIndex) { if(InvalidateIndex(nIndex)) { cout << "Error: the index of GetAt is invalid!" << endl; exit(0); } return m_Data[nIndex]; } // 設置指定地位元素的值 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; } } // 追加一個新元素到數組末尾 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; /*數組年夜小加1*/ } // 從數組中刪除一個元素 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; } } // 拔出一個新元素到指定地位 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) /* 未滿,拔出 */ { 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; /* 數組年夜小加1 */ } // 重載下標運算符[] 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]; }
經由簡略的測試,臨時還沒有發明Bug。能夠測試其實不周全,感興致的讀者可以進一步測試並完美該法式。
附:String類的完成
C++ 的一個罕見面試題是讓你完成一個 String 類,限於時光,弗成能請求具有 std::string 的功效,但至多請求能准確治理資本。
假如你弄懂了下面DArray類的寫法,那末完成String類應當就不難了。由於面試官普通只是想考核你能不克不及准確地寫出結構函數、析構函數、拷貝結構函數、拷貝賦值運算符和+、[ ]、<<、>>運算符重載等等。上面給出一個String類的接口,你可以本身嘗嘗手完成一下:
class String{ friend ostream& operator<< (ostream&,String&); //重載<<運算符 friend istream& operator>> (istream&,String&); //重載>>運算符 public: String(); // 默許結構函數 String(const char* str); // 帶參結構函數 String(const String& rhs); // 拷貝結構函數 String& operator=(const String& rhs); // 拷貝賦值運算符 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(); // 析構函數 private: char *m_data; // 用於保留字符串 };
本文所述DArray類和String類的源碼及測試代碼可點擊此處本站下載。