成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

深入理解C++20:類與對象的高級特性及運算符重載

開發 前端
就像基本的算術運算符一樣,C++20之前的六個比較運算符應該是全局函數,這樣你可以在運算符的左右兩邊的參數上使用隱式轉換。

類與對象的高級特性

1.常量靜態數據成員

在你的類中,可以聲明 const 數據成員,這意味著它們在創建和初始化后不能被改變。當常量僅適用于類時,應該使用 static const(或 const static)數據成員來代替全局常量,這也稱為類常量。整型和枚舉類型的 static const 數據成員即使不將它們作為內聯變量,也可以在類定義內部定義和初始化。例如,你可能想要為Spreadsheet指定一個最大高度和寬度。如果用戶嘗試構造一個高度或寬度超過最大值的Spreadsheet,將使用最大值代替。你可以將最大高度和寬度作為 Spreadsheet 類的 static const 成員:

export class Spreadsheet {
public:
    // 省略簡略性
    static const size_t MaxHeight { 100 };
    static const size_t MaxWidth { 100 };
};

你可以在構造函數中使用這些新常量,如下所示:

Spreadsheet::Spreadsheet(size_t width, size_t height)
    : m_id { ms_counter++ },
      m_width { min(width, MaxWidth) } // std::min() 需要 <algorithm>
      m_height { min(height, MaxHeight) }
{
    // 省略簡略性
}

注意,你也可以選擇在寬度或高度超過最大值時拋出異常,而不是自動將寬度和高度限制在其最大值內。但是,當你從構造函數中拋出異常時,析構函數將不會被調用,所以你需要小心處理這一點。這在第14章詳細討論了錯誤處理。

2.數據成員的不同種類

此類常量也可以用作參數的默認值。記住,你只能為從最右邊參數開始的一連串參數提供默認值。這里有一個例子:

export class Spreadsheet {
public:
    Spreadsheet(size_t width = MaxWidth, size_t height = MaxHeight);
    // 省略簡略性
};

3.引用數據成員

Spreadsheet和 SpreadsheetCells 很棒,但它們本身并不構成一個有用的應用程序。你需要代碼來控制整個Spreadsheet程序,你可以將其打包到一個名為 SpreadsheetApplication 的類中。假設我們希望每個 Spreadsheet 都存儲對應用程序對象的引用。SpreadsheetApplication 類的確切定義此刻并不重要,因此下面的代碼簡單地將其定義為一個空類。Spreadsheet 類被修改為包含一個新的引用數據成員,稱為 m_theApp:

export class SpreadsheetApplication {
};

export class Spreadsheet {
public:
    Spreadsheet(size_t width, size_t height, SpreadsheetApplication& theApp);
    // 省略簡略性
private:
    // 省略簡略性
    SpreadsheetApplication& m_theApp;
};

這個定義為數據成員添加了一個 SpreadsheetApplication 引用。建議在這種情況下使用引用而不是指針,因為 Spreadsheet 應該總是引用一個 SpreadsheetApplication,而指針則不能保證這一點。請注意,將應用程序的引用存儲起來僅是為了演示引用作為數據成員的用法。不建議以這種方式將 Spreadsheet 和 SpreadsheetApplication 類耦合在一起,而是使用模型-視圖-控制器(MVC)范例。

在其構造函數中,應用程序引用被賦給每個 Spreadsheet。引用不能存在而不指向某些東西,因此 m_theApp 必須在構造函數的 ctor-initializer 中被賦值:

Spreadsheet::Spreadsheet(size_t width, size_t height, SpreadsheetApplication& theApp)
    : m_id { ms_counter++ },
      m_width { std::min(width, MaxWidth) },
      m_height { std::min(height, MaxHeight) },
      m_theApp { theApp }
{
    // 省略簡略性
}

你還必須在拷貝構造函數中初始化引用成員。這是自動處理的,因為 Spreadsheet 拷貝構造函數委托給非拷貝構造函數,后者初始化了引用數據成員。記住,一旦你初始化了一個引用,你就不能改變它所引用的對象。在賦值操作符中不可能對引用進行賦值。根據你的用例,這可能意味著你的類不能為含有引用數據成員的類提供賦值操作符。如果是這種情況,賦值操作符通常被標記為刪除。

最后,引用數據成員也可以標記為 const。例如,你可能決定 Spreadsheets 只應該對應用程序對象有一個常量引用。你可以簡單地更改類定義,將 m_theApp 聲明為對常量的引用:

export class Spreadsheet {
public:
    Spreadsheet(size_t width, size_t height, const SpreadsheetApplication& theApp);
    // 省略簡略性
private:
    // 省略簡略性
    const SpreadsheetApplication& m_theApp;
};

3.嵌套類

類定義不僅可以包含成員函數和數據成員,還可以編寫嵌套類和結構體,聲明類型別名或創建枚舉類型。在類內部聲明的任何內容都在該類的作用域內。如果它是公開的,你可以通過使用類名加上作用域解析運算符(ClassName::)來在類外部訪問它。

例如,你可能會決定 SpreadsheetCell 類實際上是 Spreadsheet 類的一部分。由于它成為 Spreadsheet 類的一部分,你可能會將其重命名為 Cell。你可以像這樣定義它們:

export class Spreadsheet {
public:
    class Cell {
    public:
        Cell() = default;
        Cell(double initialValue);
        // 省略簡略性
    };
    
    Spreadsheet(size_t width, size_t height, const SpreadsheetApplication& theApp);
    // 省略其他 Spreadsheet 聲明
};

現在,Cell 類在 Spreadsheet 類內部定義,所以在 Spreadsheet 類外部引用 Cell 時,你必須使用 Spreadsheet:: 作用域來限定名稱。這甚至適用于方法定義。例如,Cell 的雙精度構造函數現在看起來像這樣:

Spreadsheet::Cell::Cell(double initialValue)
    : m_value { initialValue } {
}

即使是在 Spreadsheet 類本身的方法的返回類型(但不是參數)中,也必須使用此語法:

Spreadsheet::Cell& Spreadsheet::getCellAt(size_t x, size_t y) {
    verifyCoordinate(x, y);
    return m_cells[x][y];
}

直接在 Spreadsheet 類內部完全定義嵌套的 Cell 類會使 Spreadsheet 類的定義變得臃腫。你可以通過僅在 Spreadsheet 類中包含 Cell 的前向聲明,然后分別定義 Cell 類來緩解這種情況:

export class Spreadsheet {
public:
    class Cell;
    Spreadsheet(size_t width, size_t height, const SpreadsheetApplication& theApp);
    // 省略其他 Spreadsheet 聲明
};

class Spreadsheet::Cell {
public:
    Cell() = default;
    Cell(double initialValue);
    // 省略簡略性
};

普通的訪問控制適用于嵌套類定義。如果你聲明了一個私有或受保護的嵌套類,你只能從外部類內部使用它。嵌套類可以訪問外部類的所有受保護和私有成員。而外部類只能訪問嵌套類的公共成員。

4.類內部的枚舉類型

枚舉類型也可以是類的數據成員。例如,你可以添加對 SpreadsheetCell 類的單元格著色支持,如下所示:

export class SpreadsheetCell {
public:
    // 省略簡略性
    
    enum class Color {
        Red = 1, Green, Blue, Yellow
    };
    
    void setColor(Color color);
    Color getColor() const;
    
private:
    // 省略簡略性
    Color m_color { Color::Red };
};

setColor() 和 getColor() 方法的實現很直接:

void SpreadsheetCell::setColor(Color color) {
    m_color = color;
}

SpreadsheetCell::Color SpreadsheetCell::getColor() const {
    return m_color;
}

新方法的使用方式如下:

SpreadsheetCell myCell { 5 };
myCell.setColor(SpreadsheetCell::Color::Blue);
auto color { myCell.getColor() };

運算符重載

你經常需要對對象執行操作,例如添加它們、比較它們,或將它們流入流出文件。例如,Spreadsheet只有在你可以對其執行算術操作時才有用,比如求一整行單元格的和。

1.重載比較運算符

在你的類中定義比較運算符,如>、<、<=、>=、==和!=,是非常有用的。C++20標準為這些運算符帶來了很多變化,并增加了三元比較運算符,即太空船運算符<=>,在第1章中有介紹。為了更好地理解C++20所提供的內容,讓我們先來看看在C++20之前你需要做些什么,以及在你的編譯器還不支持三元比較運算符時你仍需要做些什么。

就像基本的算術運算符一樣,C++20之前的六個比較運算符應該是全局函數,這樣你可以在運算符的左右兩邊的參數上使用隱式轉換。比較運算符都返回一個布爾值。當然,你可以更改返回類型,但這并不推薦。這里是聲明,你需要用==、<、>、!=、<=和>=替換<op>,從而產生六個函數:

export class SpreadsheetCell { /* 省略以便簡潔 */ };
export bool operator<op>(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs);

以下是operator==的定義。其他的定義類似。

bool operator==(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs) {
    return (lhs.getValue() == rhs.getValue());
}

注意:前述重載的比較運算符正在比較雙精度值。大多數時候,對浮點值進行等于或不等于測試并不是一個好主意。你應該使用所謂的epsilon測試,但這超出了本書的范圍。在具有更多數據成員的類中,比較每個數據成員可能很痛苦。然而,一旦你實現了==和<,你就可以用這兩個運算符來寫其它的比較運算符。例如,這里是一個使用operator<的operator>=定義:

bool operator>=(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs) {
    return !(lhs < rhs);
}

你可以使用這些運算符來比較SpreadsheetCells與其他SpreadsheetCells,也可以與雙精度和整型比較:

if (myCell > aThirdCell || myCell < 10) {
    cout << myCell.getValue() << endl;
}

正如你所見,你需要編寫六個不同的函數來支持六個比較運算符,這只是為了比較兩個SpreadsheetCells。隨著當前六個實現的比較函數,可以將SpreadsheetCell與一個雙精度值進行比較,因為雙精度參數被隱式轉換為SpreadsheetCell。如前所述,這種隱式轉換可能效率低下,因為需要創建臨時對象。就像之前的operator+一樣,你可以通過實現顯式函數來避免與雙精度的比較。對于每個運算符<op>,你將需要以下三個重載:

bool operator<op>(const SpreadsheetCell& lhs, const SpreadsheetCell& rhs);
bool operator<op>(double lhs, const SpreadsheetCell& rhs);
bool operator<op>(const SpreadsheetCell& lhs, double rhs);

如果你想支持所有比較運算符,那么需要編寫很多重復的代碼!

2.C++20

現在讓我們轉換一下思路,看看C++20帶來了什么。C++20極大地簡化了為你的類添加比較運算符的支持。首先,使用C++20,實際上建議將operator==實現為類的成員函數,而不是全局函數。還要注意,添加[[nodiscard]]屬性是個好主意,這樣運算符的結果就不能被忽略了。這里是一個例子:

[[nodiscard]] bool operator==(const SpreadsheetCell& rhs) const;

使用C++20,這一個operator==重載就可以使以下比較工作:

if (myCell == 10) {
    cout << "myCell == 10\n";
}
if (10 == myCell) {
    cout << "10 == myCell\n";
}

例如10==myCell這樣的表達式會被C++20編譯器重寫為myCell==10,可以調用operator==成員函數。此外,通過實現operator==,C++20會自動增加對!=的支持。

接下來,為了實現對完整套比較運算符的支持,在C++20中你只需要實現一個額外的重載運算符,即operator<=>。一旦你的類有了operator==和<=>的重載,C++20會自動為所有六個比較運算符提供支持!對于SpreadsheetCell類,operator<=>如下所示:

[[nodiscard]] std::partial_ordering operator<=>(const SpreadsheetCell& rhs) const;

注意:C++20編譯器不會用<=>重寫==或!=比較,這是為了避免性能問題,因為顯式實現operator==通常比使用<=>更高效。

SpreadsheetCell中存儲的值是一個雙精度值。請記住,從第1章開始,浮點類型只有部分排序,這就是為什么重載返回std::partial_ordering。實現很簡單:

std::partial_ordering SpreadsheetCell::operator<=>(const SpreadsheetCell& rhs) const {
    return getValue() <=> rhs.getValue();
}

通過實現operator<=>,C++20會自動為>、`<、<=和>=提供支持,通過將使用這些運算符的表達式重寫為使用<=>的表達式。例如,類似于myCell<aThirdCell的表達式會自動重寫為類似于std::is_ lt(myCell<=>aThirdCell)的東西,其中is_lt()是一個命名比較函數;所以,通過只實現operator==和operator<=>`,SpreadsheetCell類支持完整的比較運算符集:

if (myCell < aThirdCell) {
    // ...
}
if (aThirdCell < myCell) {
    // ...
}
if (myCell <= aThirdCell) {
    // ...
}
if (aThirdCell <= myCell) {
    // ...
}
if (myCell > aThirdCell) {
    // ...
}
if (aThirdCell > myCell) {
    // ...
}
if (myCell >= aThirdCell) {
    // ...
}
if (aThirdCell >= myCell) {
    // ...
}
if (myCell == aThirdCell) {
    // ...
}
if (aThirdCell == myCell) {
    // ...
}
if (myCell != aThirdCell) {
    // ...
}
if (aThirdCell != myCell) {
    // ...
}

由于SpreadsheetCell類支持從雙精度到SpreadsheetCell的隱式轉換,因此也支持以下比較:

if (myCell < 10) {
}
if (10 < myCell) {
}
if (10 != myCell) {
}

就像比較兩個SpreadsheetCell對象一樣,編譯器會將這些表達式重寫為使用operator==和<=>的形式,并根據需要交換參數的順序。例如,10<myCell首先被重寫為類似于is_lt(10<=>myCell)的東西,這不會起作用,因為我們只有<=>作為成員的重載,這意味著左側參數必須是SpreadsheetCell。注意到這一點后,編譯器再嘗試將表達式重寫為類似于is_gt(myCell<=>10)的東西,這就可以工作了。與以前一樣,如果你想避免隱式轉換的輕微性能影響,你可以為雙精度提供特定的重載。而這現在,多虧了C++20,甚至不是很多工作。你只需要提供以下兩個額外的重載運算符作為方法:

[[nodiscard]] bool operator==(double rhs) const;
[[nodiscard]] std::partial_ordering operator<=>(double rhs) const;

這些實現如下:

bool SpreadsheetCell::operator==(double rhs) const {
    return getValue() == rhs;
}
std::partial_ordering SpreadsheetCell::operator<=>(double rhs) const {
    return getValue() <=> rhs;
}

2.編譯器生成的比較運算符

在查看SpreadsheetCell的operator和<=>的實現時,可以看到它們只是簡單地比較所有數據成員。在這種情況下,我們可以進一步減少編寫代碼的行數,因為C++20可以為我們完成這些工作。就像可以顯式默認化拷貝構造函數一樣,operator和<=>也可以被默認化,這種情況下編譯器將為你編寫它們,并通過依次比較每個數據成員來實現它們。此外,如果你只顯式默認化operator<=>,編譯器還會自動包含一個默認的operator。因此,對于沒有顯式operator和<=>用于雙精度的SpreadsheetCell版本,我們可以簡單地編寫以下單行代碼,為比較兩個SpreadsheetCell添加對所有六個比較運算符的完全支持:

[[nodiscard]] std::partial_ordering operator<=>(const SpreadsheetCell&) const = default;

此外,你可以將operator<=>的返回類型使用auto,這種情況下編譯器會基于數據成員的<=>運算符的返回類型來推斷返回類型。如果你的類有不支持operator<=>的數據成員,那么返回類型推斷將不起作用,你需要顯式指定返回類型為strong_ordering、partial_ordering或weak_ordering。為了讓編譯器能夠編寫默認的<=>運算符,類的所有數據成員都需要支持operator<=>,這種情況下返回類型可以是auto,或者是operator<和==,這種情況下返回類型不能是auto。由于SpreadsheetCell有一個雙精度數據成員,編譯器推斷返回類型為partial_ordering。

[[nodiscard]] auto operator<=>(const SpreadsheetCell&) const = default;

單獨的顯式默認化的operator<=>適用于沒有顯式operator==和<=>用于雙精度的SpreadsheetCell版本。如果你添加了這些顯式的雙精度版本,你就添加了一個用戶聲明的operator==(double)。因為這個原因,編譯器將不再自動生成operator==(const SpreadsheetCell&),所以你必須自己顯式默認化一個,如下所示:

export class SpreadsheetCell {
public:
    // Omitted for brevity
    [[nodiscard]] auto operator<=>(const SpreadsheetCell&) const = default;
    [[nodiscard]] bool operator==(const SpreadsheetCell&) const = default;
    [[nodiscard]] bool operator==(double rhs) const;
    [[nodiscard]] std::partial_ordering operator<=>(double rhs) const;
    // Omitted for brevity
};

如果你的類可以顯式默認化operator<=>,我建議這樣做,而不是自己實現它。通過讓編譯器為你編寫,它將隨著新添加或修改的數據成員保持最新狀態。如果你自己實現了運算符,那么每當你添加數據成員或更改現有數據成員時,你都需要記得更新你的operator<=>實現。如果operator==沒有被編譯器自動生成,同樣的規則也適用于它。只有當它們作為參數有對類類型的引用時,才能顯式默認化operator==和<=>。例如,以下不起作用:

[[nodiscard]] auto operator<=>(double) const = default; // 不起作用!

注意:要在C++20中向類添加對所有六個比較運算符的支持: ? 如果默認化的operator<=>適用于你的類,那么只需要一行代碼顯式默認化operator<=>作為方法即可。在某些情況下,你可能需要顯式默認化operator==。 ? 否則,只需重載并實現operator==和<=>作為方法。無需手動實現其他比較運算符。

責任編輯:趙寧寧 來源: coding日記
相關推薦

2023-11-22 13:40:17

C++函數

2024-07-12 15:46:58

2009-08-12 10:47:03

C#運算符重載

2009-08-12 12:46:11

C#運算符重載

2009-09-04 13:18:10

C#允許運算符重載

2009-08-14 10:16:57

C#運算符重載

2009-08-12 10:27:12

C#運算符重載運算符重載實例

2009-08-12 10:56:47

C#運算符重載C#運算符重載實例

2023-11-20 22:19:10

C++static

2020-11-26 14:05:39

C ++運算符數據

2024-04-10 12:14:36

C++指針算術運算

2021-12-15 10:25:57

C++運算符重載

2011-07-15 01:34:36

C++重載運算符

2024-01-26 16:37:47

C++運算符開發

2009-08-12 11:20:51

C#運算符重載

2009-08-12 10:37:13

C#運算符重載

2021-12-16 10:40:11

C++運算符重載

2020-09-30 14:04:25

C++運算符重載

2024-12-18 11:30:00

C++20比較運算符

2020-12-26 16:51:12

Python操作符開發
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 中文字幕一区二区三区日韩精品 | 亚洲日韩中文字幕一区 | 黑人巨大精品欧美黑白配亚洲 | 精品一区二区三区在线观看国产 | 亚洲精品久久久蜜桃网站 | 91在线看网站 | 祝你幸福电影在线观看 | 91免费看片 | 香蕉久久av | 久久久www成人免费精品 | 97伦理电影| 欧美一级片中文字幕 | 国产在线麻豆精品入口 | 中文字幕精品一区 | 中文字幕乱码一区二区三区 | 久久久久久久久久久国产 | 国产一区二区在线免费视频 | 成人免费视频久久 | 亚洲精品一 | 日韩欧美亚洲综合 | 中文字幕视频在线观看 | 日韩欧美在线视频 | 亚洲日韩中文字幕一区 | 亚洲精品一区二区在线 | 国产精品久久久久久久午夜片 | www.一级片| 免费三级黄 | 国产91在线精品 | 性高朝久久久久久久3小时 av一区二区三区四区 | 亚洲综合一区二区三区 | 久久久一区二区三区 | 亚洲精品区| 亚洲精品视频在线播放 | 亚洲精品电影网在线观看 | 免费1区2区3区 | 91精品国产综合久久小仙女图片 | 亚洲综合久久久 | 国产成人福利 | 日本二区在线观看 | 国产一区影院 | 国产精品国产a级 |