重載的運算符是具有特殊名字的函數,他們的名字由關鍵字operator和其後要定義的運算符號共同組成。
運算符可以重載為成員函數和非成員函數。當一個重載的運算符是成員函數時,this綁定到左側運算對象。成員運算符函數的(顯式)參數比運算對象的數量少一個。
調用重載運算符函數
//非成員函數的等價調用
data1 + data2;//normal expression
operator+(data1,data2); // equal function call
//成員函數的等價調用
data1 += data2;//normal expression
data1.operator+(data2); // equal function call
可重載的運算符:+ - * / % ˆ & | ~ ! = < > += -= = /= %= ˆ= &= |= << >> >>= <<= == != <= >= && || ++ -- , -> -> ( ) [ ]
無法被重載的運算符有: :: .* . ?:
Restrictions
選擇作為成員或非成員的依據(引用C++ Primer)
class Sales_data{
public:
Sales_data(): units_sold(0), revenue(0.0) {}
Sales_data(const std::string &s):
bookNo(s), units_sold(0), revenue(0.0) {}
Sales_data(const std::string &s, unsigned n, double p):
bookNo(s), units_sold(n), revenue(p*n) {}
std::string isbn() const{
return bookNo;
}
Sales_data& combine(const Sales_data&);
double avg_price() const;
//運算符重載函數申明
friend ostream& operator<<(ostream& os,const Sales_data& item);
friend istream& operator>>(istream& is,Sales_data& item);
private:
string bookNo;
unsigned units_sold;
double revenue;
};
inline double Sales_data:: avg_price() const{
if(units_sold)
return revenue / units_sold;
else
return 0;
}
通常情況下,第一個形參是一個非const的ostream對象的引用。ostream是非常量是因為寫入流會改變其狀態;而該形參是引用類型是因為無法直接復制一個ostream對象。
第二個形參一般是const&。因為我們希望避免復制實參,並且被打印的內用一般不會改變其內容。
為了與其他運算符保持一致,operator<<一般返回它的ostream形參。
ostream& operator<<(ostream& os,const Sales_data& item)
{
os<<item.isbn()<<" "<<item.units_sold<<" "
<<item.revenue<<" "<<item.avg_price();
return os;
}
輸出運算符盡量減少格式化操作
輸出運算符主要負責打印對象的內容而非控制格式,輸出運算符不應該打印換行符。
輸入輸出運算符必須是非成員函數,一般申明為友元
與iostream標准庫兼容。
通常,第一個形參是流的引用,第二個是將要讀到的(非const)對象引用。函數返回給定流的引用。
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(); //輸入失敗,對象被賦予默認的狀態
}
當讀取操作發生錯誤時,輸入運算符應該負責從錯誤中恢復。
通常情況下,我們把算術和關系運算符定義成非成員函數以允許對左側或者右側的運算對象進行轉換。因為這些運算符一般不需要改變運算對象的狀態,所以形參都是const&。
Sales_data operator+(const Sales_data& lhs,const Sales_data& rhs)
{
Sales_data sum = lhs;
sum += rhs;
return sum;
}
如果類同時定義了算術運算符和相關的復合賦值運算符,則通常情況下應該使用復合運算符來實現算術運算符。
用來檢驗兩個對象是否相等。
inline 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;
}
inline bool operator!=(const Sales_data &lhs, const Sales_data &rhs)
{
return !(lhs == rhs);
}
相等運算符設計准則:
定義了相等運算符的類也常常(但不總是)包含關系運算符。特別的是,因為關聯容器和一些算法要用到<,所以定義operator<會比較有用。
通常情況下關系運算符應該
如果存在唯一一種邏輯可靠的<定義,則應該考慮為類定義<運算符。如果類同時存在==運算符,當且僅當<的定義和==產生的結果一致時才定義<
此處的實例不存在邏輯可靠的關系運算,故引用其他例子。
關系運算符一般定義為非成員函數。
Typically, once operator< is provided, the other relational operators are implemented in terms of operator<。
inline bool operator< (const X& lhs, const X& rhs){ /* do actual comparison */ }
inline bool operator> (const X& lhs, const X& rhs){return rhs < lhs;}
inline bool operator<=(const X& lhs, const X& rhs){return !(lhs > rhs);}
inline bool operator>=(const X& lhs, const X& rhs){return !(lhs < rhs);}
T& operator=(const T& other) // copy assignment
{
if (this != &other) { // self-assignment check expected
if (/* storage cannot be reused (e.g. different sizes) */)
{
delete[] mArray; // destroy storage in this
/* reset size to zero and mArray to null, in case allocation throws */
mArray = new int[/*size*/]; // create storage in this
}
/* copy data from other's storage to this storage */
}
return *this;
}
復合賦值運算
一般傾向於把包括復合賦值在內的所有賦值運算都定義在類的內部。為了與內置類型的復合賦值保持一致,一般返回左側對象的引用。
Sales_data& Sales_data::operator+=(const Sales_data &rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
表示容器的類通常可以通過元素在容器中的位置訪問元素,一般會定義operator[]。
下標運算符必須是成員函數
一般會定義兩個版本,一個返回普通引用,另一個是類的常成員並返回常量引用。
class StrVec
{
public :
string& operator[](size_t n)
{ return elements[n];}
const string& operator[](size_t n) const
{ return elements[n];}
private :
string *elements;
};
遞增和遞減運算符改變的是操作對象的狀態,建議設定為成員函數。
應該同時定義前置和後置版本的遞增和遞減運算符
定義前置遞增/遞減運算符
template<class T>
class T{
public :
T& operator++();
T& operator--();
private:
int x;
};
template<class T>
T& T::operator++()
{
++x;// actual increment takes place here
return *this;
}
template<class T>
T& T::operator--()
{
--x;// actual decrement takes place here
return *this;
}
為了與內置版本保持一致,前置運算符應該返回遞增或遞減後對象的引用。
區分前置和後置運算符
後置版本接受一個額外的(不被使用)int類型的形參。
template<class T>
class T{
public :
T operator++(int);
T operator--(int);
private:
int x;
};
template<class T>
T T::operator++()
{
T tmp = *this; // copy
++(*this); // pre-increment
return *tmp; // return old value
}
template<class T>
T T::operator--()
{
T tmp = *this; // copy
--(*this); // pre-increment
return *tmp; // return old value
}
為了與內置版本保持一致,後置運算符應該返回對象的原值,返回的是一個值而非引用。