重载运算与类型转换——基本概念,输入和输出运算符,算术和关系运算符,赋值运算符,下标运算符,递增和递减运算符,成员访问运算符...
一、基本概念
重載的運算符時具有特殊名字的函數:它們的名字由關鍵字operator和其后要定義的運算符號共同組成。和其他函數一樣,重載的運算符也包含返回類型、參數列表以及函數體。
重載運算符函數的參數數量與該運算符作用的運算對象數量一樣多。一元運算符有一個參數,二元運算符有兩個。對于二元運算符來說,左側運算對象傳遞給第一個參數,而右側運算對象傳遞給第二個參數。除了重載的函數調用運算符operator()之外,其他重載運算符不能含有默認實參。
當一個重載的運算符時成員函數時,this綁定到左側運算對象。成員運算符函數的顯式參數數量比運算對象的數量少一個。
對一個運算符來說,它或者是類的成員,或者至少含有一個類類型的參數。
可以重載的運算符:
| + | - | * | / | % | ^ |
| & | | | ~ | ! | , | = |
| < | > | <= | >= | ++ | -- |
| << | >> | == | != | && | || |
| += | -= | /= | %= | ^= | &= |
| |= | *= | <<= | >>= | [] | () |
| -> | ->* | new | new [] | delete | delete [] |
不能被重載的運算符:
| :: | .* | . | ?: |
我們只能重載已有的運算符,而無權發明新的運算符號。對于一個重載的運算符來說,其優先級和結合律與對應的內置運算符保持一致。
?1)直接調用一個重載的運算符
通用情況下,我們將運算符作用于類型正確的實參,從而以這種間接方式“調用”重載的運算符函數。然而,我們也能像調用普通函數一樣直接調用運算符函數,先指定函數名字,然后傳入數量正確、類型適當的實參:
// 一個非成員運算符函數的等價調用
data1+data2 // 普通的表達式
operator+(data1,data2) // 等價的函數調用
我們像調用其他成員函數一樣顯式地調用成員運算符函數。首先指定運算符函數的對象(或指針)的名字,然后使用點運算符(或箭頭運算符)訪問希望調用的函數:
data1+=data2 // 基于“調用”的表達式
data1.operator+=(data2) // 對成員運算符函數的等價調用
2)某些運算符不應該被重載
某些運算符指定了運算對象的求值的順序。因為使用重載的運算符本質上是一次函數調用,所以這些關于運算對象求值順序的規則無法應用到重載的運算符上。特別是,邏輯與運算符、邏輯或運算符和逗號運算符的運算對象求值順序規則無法保留下來。除此之外,&&和||運算符的重載版本也無法保留內置運算符的短路求值屬性,兩個運算對象總是會被求值。
還有一個原因使得我們一般不重載逗號運算符和取地址符:C++語言已經定義了這兩種運算符用于類類型對象時的特殊含義,這一點與大多數運算符都不相同。因為這兩種運算符已經有了內置的含義,所以一般來說它們不應該被重載,否則它們的行為將異于常態,從而導致類的用戶無法適應。
通常情況下,不應該重載逗號、取地址、邏輯與、邏輯或運算符。
3)使用與內置類型一致的含義
如果某些類操作在邏輯上與運算符相關,則它們適合定義成重載的運算符:
- 如果類執行IO操作,則定義移位運算符使其與內置類型的IO保持一致。
- 如果類的某個操作是檢查相等性,則定義operator==;如果類有了operator==,意味著它通常也應該有operator!=。
- 如果類包含一個內在的單序比較操作,則定義operator<;如果類有了operator<,則它也應該含有其他關系操作。
- 重載運算符的返回類型通常情況下應該與其內置版本的返回類型兼容:邏輯運算符和關系運算符應該返回bool,算術運算符應該返回一個類類型的值,賦值運算符和復合賦值運算符則應該返回左側運算對象的一個引用。
4)賦值和復合賦值運算符
賦值運算符的行為與復合版本的類似:賦值之后,左側運算對象和右側運算對象的值相等,并且運算符應該返回它左側運算對象的一個引用。重載的賦值運算符應該繼承而非違背其內置版本的含義。
如果類含有算術運算符或者位運算符,則最好也提供對應的復合賦值運算符。
5)選擇作為成員或者非成員
下面的準則有助于我們在將運算符定義為成員函數還是普通的非成員函數做出抉擇:
- 賦值(=)、下標([])、調用(())和成員訪問箭頭(->)運算符必須還是成員。
- 復合賦值運算符一般來說應該是成員,但并非必須。
- 改變對象狀態的運算符或者與給定類型密切相關的運算符,如遞增、遞減和解引用運算符,通常應該是成員。
- 具有對稱性的運算符可能轉換任一端的運算對象,例如算術、相等性、關系和位運算符等,通常應該是普通的非成員函數。
當把運算符定義成成員函數時,它的左側運算對象必須是運算符所屬類的一個對象。
當把運算符定義成普通的非成員函數時,唯一的要求是是至少有一個運算對象是類類型,并且兩個運算對象都能精確無誤地轉換。
?
二、輸入和輸出運算符
1、重載輸出運算符<<
通常情況下,輸出運算符的第一個形參是非常量ostream對象的引用。之所以ostream是非常量是因為向流寫入內容會改變其狀態;而該形參是引用是因為我們無法直接復制一個ostream對象。
第二個形參一般來說是一個常量的引用,該常量是我們想要打印的類類型。第二個形參是引用的原因是我們希望避免復制實參;而之所以該形參是常量是因為通常情況打印對象不會改變對象的內容。
為了與其他輸出運算符保持一致,operator<<一般要返回它的ostream形參。
1 std::ostream &operator<<(std::ostream& os, const SalesData &item) 2 { 3 os << item.m_bookno << ", " << item.m_unitssold << ", " 4 << item.m_revenue << ", " << item.avgPrice(); 5 return os; 6 } View Code1)輸入輸出運算符必須是非成員函數
與iostream標準庫兼容的輸入輸出運算符必須是普通的非成員函數,而不能是類的成員函數,因為我們無法給標準的類添加任何成員。
IO運算符通常需要讀寫類的非公有數據成員,所以IO運算符一般被聲明為友元。
?
2、重載輸入運算符>>
通常情況下,輸入運算符的第一個形參是運算符將要讀取的流的引用,第二個形參是將要讀入到的非常量對象的引用。該運算符通常會返回某個給定流的引用。第二個形參之所以必須是個非常量是因為輸入運算符本身的目的就是將數據讀入到這個對象中。
輸入運算符必須處理輸入可能失敗的情況,而輸出運算符不需要。
1 std::istream &operator>>(std::istream &is, SalesData &item) 2 { 3 double price; 4 is >> item.m_bookno >> item.m_unitssold >> price; 5 if (is) 6 item.m_revenue = item.m_unitssold*price; 7 else 8 item = SalesData(); // 輸入失敗,對象被賦予默認的狀態 9 return is; 10 } View Code1)輸入時的錯誤
在執行輸入運算符時可能發生以下錯誤:
- 當流含有錯誤類型的數據時讀取操作可能失敗。
- 當讀取到達文件末尾或者遇到輸入流的其他錯誤時也會失敗。
?
三、算術和關系運算符
? 通常情況下,我們把算術運算符定義成非成員函數以允許對左側或右側的運算對象進行轉換。因為這些運算符一般不需要改變運算對象的狀態,所以形參都是常量的引用。
算術運算通常會計算它的兩個運算對象并得到一個新值,這個值有別于任意一個運算對象,常常位于一個局部變量之內,操作完成后返回該變量的副本作為其結果。如果類定義了算術運算符,則它一般也會定義一個對應的復合賦值運算符。此時,最有效的方法是使用復合賦值運算符來定義算術運算符。
1 SalesData &SalesData::operator+=(const SalesData &rhs) 2 { 3 m_unitssold += rhs.m_unitssold; 4 m_revenue += rhs.m_revenue; 5 return *this; 6 } 7 8 SalesData operator+(const SalesData &lhs, const SalesData &rhs) 9 { 10 SalesData sum = lhs; 11 sum += rhs; 12 return sum; 13 } View Code1、相等運算符
通常情況下,C++中的類通過相等運算符來檢驗兩個對象是否相等。也就是說,它們會比較對象的每一個數據成員,只有當所有對應的成員都相等時才認為兩個對象相等。
1 bool operator==(const SalesData &lhs, const SalesData &rhs) 2 { 3 return lhs.m_bookno == rhs.m_bookno&& 4 lhs.m_unitssold == rhs.m_unitssold&& 5 lhs.m_revenue == rhs.m_revenue; 6 } 7 8 bool operator!=(const SalesData &lhs, const SalesData &rhs) 9 { 10 return !(lhs == rhs); 11 } View Code2、關系運算符
定義了相等運算符的類也常常(但不總是)包含關系運算符。特別是,因為關聯容器和一些算法要用到小于運算符,所以定義operator<會比較有用。
通常情況下關系運算符應該:定義順序關系,令其與關聯容器中對關鍵字的要求一致。如果類同時也含有==運算符的話,則定義一種關系令其與==保持一致。特別是,如果兩個對象是!=的話,那么一個對象應該<另一個。
?
四、賦值運算符
我們可以重載賦值運算符。不論形參的類型是什么,賦值運算符都必須定義為成員函數。賦值運算符返回其左側運算對象的引用。
1)復合賦值運算符
復合賦值運算符不非得是類的成員,不過我們還是傾向于把包括賦值在內的所有賦值運算都定義在類的內部。為了與內置類型的復合賦值保持一致,類中的復合賦值運算符也要返回其左側運算對象的引用。
?
五、下標運算符
表示容器的類通常可以通過元素在容器中的位置訪問元素,這些類一般會定義下標運算符operator[]。下標運算符必須是成員函數。
為了與下標的原始定義兼容,下標運算符通常以所訪問元素的引用作為返回值,這樣做的好處是下標可以出現在賦值運算符的任意一端。進一步,我們最好同時定義下標運算符的常量版本和非常量版本,當作用于一個常量對象時,下標運算符返回常量引用以確保我們不會給返回的對象賦值。
1 class StrVec 2 { 3 public: 4 StrVec():vec(){} 5 void push_back(const std::string &s) { vec.push_back(s); } 6 std::string &operator[](std::size_t n) { return vec[n]; } 7 const std::string &operator[](std::size_t n)const { return vec[n]; } 8 private: 9 std::vector<std::string> vec; 10 }; View Code?
六、遞增和遞減運算符
定義遞增和遞減運算符的類應該同時定義前置版本和后置版本。這些運算符通常應該被定義成類的成員。
1)定義前置遞增/遞減運算符
為了與內置版本保持一致,前置運算符應該返回遞增或遞減后對象的引用。
1 // 前置版本:返回遞增/遞減對象的引用 2 StrBlob &StrBlob::operator++() 3 { 4 ++curr; 5 check(curr, "increment past end"); 6 return *this; 7 } 8 9 StrBlob &StrBlob::operator--() 10 { 11 --curr; 12 check(curr, "decrement past end"); 13 return *this; 14 } View Code2)區分前置和后置運算符
要想同時定義前置和后置運算符,必須首先解決一個問題,即普通的重載形式無法區分這兩種情況。前置和后置版本使用的是同一個符號,意味著其重載版本所用的名字將是相同的,并且運算對象的數量和類型也相同。
為了解決這個問題,后置版本接受一個額外的(不被使用)int類型的形參。當我們使用后置運算符時,編譯器為這個形參提供一個值為0的實參。盡管從語法上來說后置函數可以使用這個額外的形參,但是在實際過程中通常不會這么做。這個形參的唯一作用就是區分前置和后置版本的函數,而不是真的要在實現后置版本時參與運算。
1 // 后置版本 2 StrBlob StrBlob::operator++(int) 3 { 4 StrBlob ret = *this; 5 ++*this; 6 return ret; 7 } 8 9 StrBlob StrBlob::operator--(int) 10 { 11 StrBlob ret = *this; 12 --*this; 13 return ret; 14 } View Code因為我們不會用到int形參,所以無須為其命名。
完整代碼:
1 #include <iostream> 2 #include <string> 3 #include <vector> 4 #include <algorithm> 5 #include <memory> 6 7 class StrBlob 8 { 9 public: 10 // 前置版本 11 StrBlob &operator++(); 12 StrBlob &operator--(); 13 // 后置版本 14 StrBlob operator++(int); 15 StrBlob operator--(int); 16 public: 17 typedef std::vector<std::string>::size_type size_type; 18 StrBlob(std::initializer_list<std::string> items) 19 :data(std::make_shared<std::vector<std::string>>(items)), curr(0) {} 20 size_type size()const { return data->size(); } 21 std::string now()const { return (*data)[curr]; } 22 23 private: 24 void check(size_type i, const std::string &msg) const 25 { 26 if (i >= data->size()) 27 throw std::out_of_range(msg); 28 } 29 private: 30 std::shared_ptr<std::vector<std::string>> data; // 多個對象間共享底層元素 31 std::size_t curr; // 在數組中的當前位置 32 }; 33 34 // 前置版本:返回遞增/遞減對象的引用 35 StrBlob &StrBlob::operator++() 36 { 37 ++curr; 38 check(curr, "increment past end"); 39 return *this; 40 } 41 42 StrBlob &StrBlob::operator--() 43 { 44 --curr; 45 check(curr, "decrement past end"); 46 return *this; 47 } 48 // 后置版本 49 StrBlob StrBlob::operator++(int) 50 { 51 StrBlob ret = *this; 52 ++*this; 53 return ret; 54 } 55 56 StrBlob StrBlob::operator--(int) 57 { 58 StrBlob ret = *this; 59 --*this; 60 return ret; 61 } 62 63 int main() 64 { 65 StrBlob blob({ "a", "b","c","d","e" }); 66 std::cout << (blob++).now() << std::endl; 67 return 0; 68 } View Code?
七、成員訪問運算符
在迭代器及智能指針類中常常用到解引用運算符(*)和箭頭運算符(->)。箭頭運算符必須是類的成員。解引用運算符通常也是類的成員,盡管并非必須如此。
1 class StrBlob 2 { 3 public: 4 // 前置版本 5 StrBlob &operator++(); 6 StrBlob &operator--(); 7 // 后置版本 8 StrBlob operator++(int); 9 StrBlob operator--(int); 10 11 std::string &operator*()const { 12 return (*data)[curr]; 13 } 14 std::string *operator->()const { 15 return &this->operator*(); // 將實際工作委托給解引用運算符 16 } 17 public: 18 typedef std::vector<std::string>::size_type size_type; 19 StrBlob(std::initializer_list<std::string> items) 20 :data(std::make_shared<std::vector<std::string>>(items)), curr(0) {} 21 size_type size()const { return data->size(); } 22 std::string now()const { return (*data)[curr]; } 23 24 private: 25 void check(size_type i, const std::string &msg) const 26 { 27 if (i >= data->size()) 28 throw std::out_of_range(msg); 29 } 30 private: 31 std::shared_ptr<std::vector<std::string>> data; // 多個對象間共享底層元素 32 std::size_t curr; // 在數組中的當前位置 33 }; View Code1)對箭頭運算符返回值的限定
和大多數其他運算符一樣(盡管這么做不好),我們能令operator*完成任何我們指定的操作。箭頭運算符則不是這樣,它永遠不能丟掉成員訪問這個最基本的含義。當我們重載箭頭時,可以改變的是箭頭從哪個對象中獲取成員,而箭頭獲取成員這一事實則永遠不變。
對于形如point->mem的表達式來說,point必須是指向類對象的指針或者是一個重載了operator->的類的對象。根據point類型不同,point->mem分別等價于
(*point).mem;? // point是一個內置的指針類型
point.operator()->mem; // point是類的一個對象
除此之外,代碼都將發生錯誤。point->mem的執行過程如下所示:
重載的箭頭運算符必須返回類的指針或者自定義了箭頭運算符的某個類的對象。
?
一些完整的例子:
1 #ifndef SALES_DATA 2 #define SALES_DATA 3 4 #include <iostream> 5 #include <string> 6 class SalesData 7 { 8 // 友元聲明 9 friend std::ostream &operator<<(std::ostream &os, const SalesData &item); 10 friend std::istream &operator>>(std::istream &, SalesData &); 11 friend SalesData operator+(const SalesData &, const SalesData &); 12 friend bool operator==(const SalesData &, const SalesData &); 13 friend bool operator!=(const SalesData &, const SalesData &); 14 public: 15 SalesData &operator+=(const SalesData &); 16 public: 17 SalesData(const std::string &s="") :m_bookno(s), m_unitssold(0), m_revenue(0) {} 18 SalesData(const std::string &s, unsigned n, double p) : 19 m_bookno(s), m_unitssold(n), m_revenue(p * n) {} 20 private: 21 double avgPrice() const; 22 23 std::string m_bookno; // 書名 24 unsigned m_unitssold; // 數量 25 double m_revenue; // 總價 26 }; 27 28 std::ostream &operator<<(std::ostream& os, const SalesData &item); 29 std::istream &operator>>(std::istream &, SalesData &); 30 SalesData operator+(const SalesData &, const SalesData &); 31 bool operator==(const SalesData &, const SalesData &); 32 bool operator!=(const SalesData &, const SalesData &); 33 #endif SalesData.h 1 #include "SalesData.h" 2 #include <string> 3 4 std::ostream &operator<<(std::ostream& os, const SalesData &item) 5 { 6 os << item.m_bookno << ", " << item.m_unitssold << ", " 7 << item.m_revenue << ", " << item.avgPrice(); 8 return os; 9 } 10 11 std::istream &operator>>(std::istream &is, SalesData &item) 12 { 13 double price; 14 is >> item.m_bookno >> item.m_unitssold >> price; 15 if (is) 16 item.m_revenue = item.m_unitssold*price; 17 else 18 item = SalesData(); // 輸入失敗,對象被賦予默認的狀態 19 return is; 20 } 21 22 SalesData &SalesData::operator+=(const SalesData &rhs) 23 { 24 m_unitssold += rhs.m_unitssold; 25 m_revenue += rhs.m_revenue; 26 return *this; 27 } 28 29 SalesData operator+(const SalesData &lhs, const SalesData &rhs) 30 { 31 SalesData sum = lhs; 32 sum += rhs; 33 return sum; 34 } 35 36 bool operator==(const SalesData &lhs, const SalesData &rhs) 37 { 38 return lhs.m_bookno == rhs.m_bookno&& 39 lhs.m_unitssold == rhs.m_unitssold&& 40 lhs.m_revenue == rhs.m_revenue; 41 } 42 43 bool operator!=(const SalesData &lhs, const SalesData &rhs) 44 { 45 return !(lhs == rhs); 46 } 47 48 double SalesData::avgPrice() const 49 { 50 if (m_unitssold) 51 return m_revenue / m_unitssold; 52 else 53 return 0; 54 } SalesData.cpp 1 #include <iostream> 2 #include <string> 3 #include <vector> 4 #include <algorithm> 5 #include <memory> 6 7 class StrBlob 8 { 9 public: 10 // 前置版本 11 StrBlob &operator++(); 12 StrBlob &operator--(); 13 // 后置版本 14 StrBlob operator++(int); 15 StrBlob operator--(int); 16 17 std::string &operator*()const { 18 return (*data)[curr]; 19 } 20 std::string *operator->()const { 21 return &this->operator*(); // 將實際工作委托給解引用運算符 22 } 23 public: 24 typedef std::vector<std::string>::size_type size_type; 25 StrBlob(std::initializer_list<std::string> items) 26 :data(std::make_shared<std::vector<std::string>>(items)), curr(0) {} 27 size_type size()const { return data->size(); } 28 std::string now()const { return (*data)[curr]; } 29 30 private: 31 void check(size_type i, const std::string &msg) const 32 { 33 if (i >= data->size()) 34 throw std::out_of_range(msg); 35 } 36 private: 37 std::shared_ptr<std::vector<std::string>> data; // 多個對象間共享底層元素 38 std::size_t curr; // 在數組中的當前位置 39 }; 40 41 // 前置版本:返回遞增/遞減對象的引用 42 StrBlob &StrBlob::operator++() 43 { 44 ++curr; 45 check(curr, "increment past end"); 46 return *this; 47 } 48 49 StrBlob &StrBlob::operator--() 50 { 51 --curr; 52 check(curr, "decrement past end"); 53 return *this; 54 } 55 // 后置版本 56 StrBlob StrBlob::operator++(int) 57 { 58 StrBlob ret = *this; 59 ++*this; 60 return ret; 61 } 62 63 StrBlob StrBlob::operator--(int) 64 { 65 StrBlob ret = *this; 66 --*this; 67 return ret; 68 } 69 70 int main() 71 { 72 StrBlob blob({ "a", "b","c","d","e" }); 73 std::cout << blob->size() << std::endl; 74 return 0; 75 } main.cpp?
轉載于:https://www.cnblogs.com/ACGame/p/10304257.html
總結
以上是生活随笔為你收集整理的重载运算与类型转换——基本概念,输入和输出运算符,算术和关系运算符,赋值运算符,下标运算符,递增和递减运算符,成员访问运算符...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【学习总结】GirlsInAI ML-d
- 下一篇: 一分钟了解spark的调优