C++類設計原則
面向物件和與面向過程的比較
l 物件使資料和成員函式之間的結合更加緊密,更加有意義;
l 物件更便於查詢錯誤,因為操作都只侷限於它們的物件;
l 物件可以對其他物件隱藏某些操作細節,從而使得這些操作不會受到其他物件的影響。
解構函式設定為virtual
作用:
虛構函式執行時先呼叫派生類的解構函式,其次才呼叫基類的解構函式。如果解構函式不是虛擬函式,而程式執行時又要通過基類的指標去銷燬派生類的動態物件,那麼用delete銷燬物件時,只調用了基類的解構函式,未呼叫派生類的解構函式。這樣會造成銷燬物件不完全。
解構函式為虛擬函式,當用一個基類的指標刪除一個派生類的物件時,派生類的解構函式會被呼叫。
自定義拷貝建構函式和賦值建構函式
場景:動態多維陣列......
拷貝建構函式是一種特殊的建構函式,也叫複製建構函式,它在建立物件時,是使用同一類中之前建立的物件來初始化新建立的物件。
注意:函式名稱必須和類名稱一致;雖然編譯器自行會定義一個,但如果類中有動態記憶體分配,必須有一個自定義拷貝建構函式,以實現深層拷貝。
// 例子:一個表(Spreadsheet)中含有許多單元格(SpreadsheetCell.)。 #pragma once class SpreadsheetCell; class Spreadsheet { public: Spreadsheet(int inWidth, int virtual ~Spreadsheet(); Spreadsheet(const Spreadsheet& src); Spreadsheet& operator=(const Spreadsheet& rhs); void setCellAt(int x, int y, const SpreadsheetCell& cell); SpreadsheetCell& getCellAt(int x, int y); private: bool inRange(int val, int // void copyFrom(const Spreadsheet& src); int mWidth, mHeight; SpreadsheetCell** mCells; }; ////////////////////////////////////////////////////////// // 建構函式與解構函式: Spreadsheet::Spreadsheet(int inWidth, int inHeight) : mWidth(inWidth), mHeight(inHeight) { mCells = new SpreadsheetCell*[mWidth]; for (int i = 0; i < mWidth; i++) { mCells[i] = new SpreadsheetCell[mHeight]; } } Spreadsheet::~Spreadsheet() { for (int i = 0; i < mWidth; i++) { delete[] mCells[i]; } delete[] mCells; mCells = nullptr; } ////////////////////////////////////////////////////////// // 複製建構函式和賦值運算子 (初版) Spreadsheet::Spreadsheet(const Spreadsheet& src) { mWidth = src.mWidth; mHeight = src.mHeight; mCells = new SpreadsheetCell*[mWidth]; for (int i = 0; i < mWidth; i++) { mCells[i] = new SpreadsheetCell[mHeight]; } for (int i = 0; i < mWidth; i++) { for (int j = 0; j < mHeight; j++) { mCells[i][j] = src.mCells[i][j]; } } } // 注意:自賦值檢測不僅是為了效率,更是為了正確性。賦值運算子首先刪除左邊物件的mCells,再給左邊物件分配一個新的mCells。 Spreadsheet& Spreadsheet::operator=(const Spreadsheet& rhs) { // check for self-assignment if (this == &rhs) { return *this; } // free the old memory for (int i = 0; i < mWidth; i++) { delete[] mCells[i]; } delete[] mCells; mCells = nullptr; // copy the new memory mWidth = rhs.mWidth; mHeight = rhs.mHeight; mCells = new SpreadsheetCell*[mWidth]; for (int i = 0; i < mWidth; i++) { mCells[i] = new SpreadsheetCell[mHeight]; } for (int i = 0; i < mWidth; i++) { for (int j = 0; j < mHeight; j++) { mCells[i][j] = rhs.mCells[i][j]; } } return *this; } // 這裡的複製建構函式和賦值運算子十分相似,可以使用通用輔助方法實現簡約。 void Spreadsheet::copyFrom(const Spreadsheet& src) { mWidth = src.mWidth; mHeight = src.mHeight; mCells = new SpreadsheetCell*[mWidth]; for (int i = 0; i < mWidth; i++) { mCells[i] = new SpreadsheetCell[mHeight]; } for (int i = 0; i < mWidth; i++) { for (int j = 0; j < mHeight; j++) { mCells[i][j] = src.mCells[i][j]; } } } // 複製建構函式和賦值運算子 (終版) Spreadsheet::Spreadsheet(const Spreadsheet &src) { copyFrom(src); } Spreadsheet& Spreadsheet::operator=(const Spreadsheet& rhs) { // check for self-assignment if (this == &rhs) { return *this; } // free the old memory for (int i = 0; i < mWidth; i++) { delete[] mCells[i]; } delete[] mCells; mCells = nullptr; // copy the new memory copyFrom(rhs); return *this; } |
委託建構函式
#include <iostream> #include <string> using namespace std; class A { private: int i = 5; string str = "初始值"; public: A(){ str = "委託建構函式"; i = 99; } A(int ii) :A(){ // 不能寫成AA(int ii):A(),i(ii) // 委託建構函式不能再利用初始化器初始化其他資料成員 i = ii; } void show(){ cout << "i=" << i << ",str=" << str << endl; } }; int main() { A a(10); a.show(); system("pause"); return 0; } |
注意:委託建構函式必須放在建構函式初始化器中,且必須是列表中唯一的成員初始化器。要注意避免出現建構函式的遞迴。
Getters and Setters
Getters : 會改變的直接返回該型別,不應改變的返回const T&。
Setters : 會改變的直接引數為該型別,不應改變的引數為const T&。
類比:不變的成員都應設定為const。
#include <string> class Employee { public: Employee(); virtual ~Employee(); void promote(int raiseAmount = 1000); void demote(int demeritAmount = 1000); void hire(); // Hires or rehires the employee void fire(); // Dismisses the employee void display() const;// Outputs employee info to console // Getters and setters void setFirstName(const std::string& firstName); const std::string& getFirstName() const; void setLastName(const std::string& lastName); const std::string& getLastName() const; void setEmployeeNumber(int employeeNumber); int getEmployeeNumber() const; void setSalary(int newSalary); int getSalary() const; bool getIsHired() const; private: std::string mFirstName; std::string mLastName; int mEmployeeNumber; int mSalary; bool mHired; }; |
對於const還有一個重要的用法,對於常物件,只能訪問常函式。
Void A::func() const
Const A a;
a.func();
static設計原則
static 方法不屬於特定物件,沒有this指標。
注意:不能將static宣告為const,這也是多餘的,因為靜態方法沒有類的例項,因此不會改變類內部的值。
Override
子類的重寫基類(以介面類居多)的虛擬函式時,建議在函式後面加入override,讓編譯器自動檢查函式是否與基類函式完全匹配。
原因:在函式後面加入override,讓編譯器自動檢查函式是否與基類函式完全匹配,從而正確實現方法重寫。不加的話:如果發現函式返回值或者引數型別不同,則是在派生類中建立了新的方法,而不是重寫。