1. 程式人生 > >C++類設計原則

C++類設計原則

面向物件和與面向過程的比較

l 物件使資料和成員函式之間的結合更加緊密,更加有意義;

l 物件更便於查詢錯誤,因為操作都只侷限於它們的物件;

l 物件可以對其他物件隱藏某些操作細節,從而使得這些操作不會受到其他物件的影響。

解構函式設定為virtual

作用:

虛構函式執行時先呼叫派生類的解構函式,其次才呼叫基類的解構函式。如果解構函式不是虛擬函式,而程式執行時又要通過基類的指標去銷燬派生類的動態物件,那麼用delete銷燬物件時,只調用了基類的解構函式,未呼叫派生類的解構函式。這樣會造成銷燬物件不完全。

解構函式為虛擬函式,當用一個基類的指標刪除一個派生類的物件時,派生類的解構函式會被呼叫。

自定義拷貝建構函式和賦值建構函式

場景:動態多維陣列......

拷貝建構函式是一種特殊的建構函式,也叫複製建構函式,它在建立物件時,是使用同一類中之前建立的物件來初始化新建立的物件。

注意:函式名稱必須和類名稱一致;雖然編譯器自行會定義一個,但如果類中有動態記憶體分配,必須有一個自定義拷貝建構函式,以實現深層拷貝。

// 例子:一個表(Spreadsheet)中含有許多單元格(SpreadsheetCell.)。

#pragma once

class SpreadsheetCell;

class Spreadsheet

{

public:

    Spreadsheet(int inWidth, int

inHeight);

    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

upper);

    // 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,讓編譯器自動檢查函式是否與基類函式完全匹配,從而正確實現方法重寫。不加的話:如果發現函式返回值或者引數型別不同,則是在派生類中建立了新的方法,而不是重寫。