C++ Primer : 第十四章 : 重載運算與類型轉換之重載運算符

重載前須知

重載運算符是特殊的函數,它們的名字由operator和其後要重載的運算符號共同組成。 因爲重載運算符時函數, 因此它包含返回值、參數列表和函數體。
對於重載運算符是成員函數時, 它的第一個運算對象被隱式的綁定到this指針上,因此,成員函數的重載運算符的顯示參數數量比運算符的運算對象少一個

對一個運算符函數來說, 要麼它是一個類的成員函數, 或者它的參數至少包含一個類類型


某些運算符不應該被重載

對於邏輯與&&、邏輯或 || 和逗號運算符來說,重載它們會無法保留下來它們的運算對象的求值順序。 而且對於&& 和 || 來說,它們具有的短路求值屬性也無法保留。

對於取地址運算符,它又特定的內置含義,它也不該被重載。


重載運算符應該和內置類型一樣的含義
  • 如果類執行IO操作,則定義移位運算符使其與內置類的IO 一致。
  • 一般定義了相等性運算符==,那麼也應該定義!= 運算符。
  • 一個類定義了一個比較運算符,那麼它也應該定義其他比較運算符。
  • 重載運算符的返回類型應該和內置版本的返回類型一致。

選擇座位成員還是非成員函數

  • 賦值(=)、 下標([])、調用(())和成員訪問箭頭運算符必須定義爲成員函數
  • 複合賦值運算符一般定義爲成員函數,但不是必須的
  • 改變對象狀態的運算符或者與給定類型密切相關的運算符,如遞增、遞減和解引用運算符,一般定義爲成員函數
  • 具有對稱性的運算符可能轉換任意一端的運算對象,例如算術、相等性、關係和位運算符等,通常應該爲非成員函數

輸入和輸出運算符

輸入、輸出運算符必須是非成員函數, 一般被定義爲類的友元

重載輸出運算符

輸出運算符的第一個形參是一個非常量的ostream對象的引用,第二個形參一般是一個常量的引用,因爲輸出運算符不會改變參數的值。
operator << 一般返回它的ostream形參。

Sales_data的輸出運算符:

ostream& operator << (ostream& os, const Sales_data& item) {
    os << item.isbn() << " " << item.units_sold << " " <<item.revenue << " " << item.avg_price();
    return os;

}


值得注意的是,爲了和內置類型的輸出運算符保持一致,我們重載的輸出運算符應該儘量減少格式化操作, 尤其是換行符!


重載輸入運算符

輸入運算符的第一個參數應該是一個要讀的流的引用,第二個參數形參應該是一個非常量的對象的引用,它返回輸入流的引用。
輸入運算符應該處理可能輸入失敗的情況


Sales_data的輸入操作:

istream& operator >> (istream& is, Sales_data& item) {
	
	double price;
	is >> item.bookNo >> item.units_sold >> price;
	if (is) // 檢測輸入流
		item.revenue = item.units_sold * price;
	else 
		item = Sales_data(); // 輸入失敗,對象被賦予默認狀態
	
	return is;
}


正如程序中看到的,重載的輸入運算符應該要處理可能輸入失敗的情況,當讀取失敗時,輸入運算符應該負責從錯誤中恢復。


算術和關係運算符

通常情況下,我們將算術和關係運算符定義爲非成員函數,以允許向左側或右側的運算對象進行轉換。

算術運算符

一般的,如果定義了算術運算符,則它一般也需要定義一個對應的複合賦值運算符

Sales_data operator + (const Sales_data& lhs, const Sales_data& rhs) {
	
	Sales_data sum = lhs;
	sum += rhs;
	return sum;	
}

同時定義了算術運算符和相應的複合賦值運算符,則一般用複合賦值運算符實現算術運算符。


相等運算符

判斷兩個類是否相等時,我們應該比較它的所有成員:
bool operator == (const Sales_data& lhs, const Sales_data& rhs) {
	
	return lhs.isbn() == rhs.isbn() &&
		   lhs.units_sold == rhs.units_sold &&
		   lhs.revenue == rhs.revenue;	
}

bool operator != (const Sales_data& lhs, const Sales_data& rhs) {
	
	return !(lhs == rhs);
}


如果定義了==, 則運算符應該判斷給定的兩個對象是否含有重複數據。
==應該具有傳遞性,如果 a == b, b == c, 則 a == c。
如果定義了==,則我們也應該定義 !=
相等運算符和不等運算符中的一個應該把工作委託給另一個。


關係運算符

一般定義了相等運算符的類,也應該定義關係運算符,特別的是,關聯容器和一些算法需要用到小於運算符,因此定義operator < 比較有用。
關係運算符應該:1. 定義順序關係,令其與關聯容器對關鍵字的要求一樣,嚴格弱序。 2. 如果類同時有 == 運算符,則定義一種關係令其與==保持一致,如果兩個對象是!= 的,那麼一個對象應該 < 另一個。

存在唯一的邏輯可靠的 < 定義,才考慮爲一個類定義 < 運算符。如果類同時定義了==, 當且僅當<的定義與==產生的結果一致時才定義<運算符。


賦值運算符

賦值運算符必須定義爲成員函數
重載賦值運算符應該與內置類型的賦值運算符保持一致,應該返回左則運算對象的引用:

StrVec& StrVec::operator = (initializer_list<string> il) {
	
	auto data = alloc_n_copy(il.begin(), il.end());
	free();
	first = data.first;
	last_end =  cap = data.last_end;
	return *this;	
}

賦值運算符應該先釋放左則對象的內存空間。

複合賦值運算符

複合賦值運算符不一定是類的成員函數,不過我們應該把包括複合賦值在內的所有賦值運算都定義在類的內部。複合賦值運算符也要返回左側運算對象的引用:
Sales_data& Sales_data::operator += (const Sales_data& rhs) {
	
	units_sold += rhs.units_sold;
	revenue += rhs.revenue;
	return *this;
}



下標運算符

下表運算符必須是成員函數

下標運算符應該以所訪問的元素的引用作爲返回值,而且我們最好定義常量版本和非常量版本。、

class StrVec {
public:
	std::string& operator [] (size_t n) { return first[n]; }
	const std::string& operator [] (size_t n) const { return first[n]; }
private:
	std::string* first; // 指向數組的首元素
};

遞增和遞減運算符

遞增和遞減運算符通常應該定義爲類的成員函數

前置版本:

class StrBlobPtr {
public:
	StrBlobPtr& operator++();
	StrBlobPtr& operator--();
	//
};

前置版本的遞增/遞減運算符應該返回遞增或遞減後的對象的引用


後置版本:
class StrBlobPtr {
public:
	StrBlobPtr operator++(int);
	StrBlobPtr operator--(int);
	//
};

爲了前置版本和後置版本,將後置版本中的參數列表中添加一個int型參數,但是這個int型只是用來區分前置和後置版本的運算符,並不使用它。
後置版本的遞增/遞減運算符應該返回遞增/遞減前的對象的值。


成員訪問運算符

箭頭運算符必須是類的成員, 解引用也應該是類的成員,儘管並非如此


class StrBlobPtr {
public:
	std::string& operator*() const {
		auto p = check(curr, "dreference past end");
		return (*p)[curr];
	}
	
	std::string* operator->()const {
		return & this->operator*();		
	}
	//
};


對箭頭運算符返回值的限定

箭頭運算符永遠不能丟掉獲取成員這一事實。

對於形如point->mem的表達式,point必須是指向類對象的指針或者是一個重載了operator->的類的對象,根據point類型不同,可分爲兩種情況:
(*point).mem; // (1)
point.operator()->mem; // (2)

如果point是指針,則應該使用內置的箭頭運算符,表達式等價於上面的第一條。
如果point是定義了operator->的類的一個對象,則使用operator->的結果來獲取mem。如果該結果是一個指針,則執行第一步,如果該結果本身含有重載的operator->(), 則重複調用當前步驟。


重載的箭頭運算符必須返回類的指針或自定義了箭頭運算符的某個類的對象



發佈了72 篇原創文章 · 獲贊 15 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章