1、基本概念
1、重載運算符是具有特殊名字的函數:它們的名字由關鍵字 operator 和其後要定義的運算符号共同組成。和其他函數一樣,重載的運算符也包含傳回類型、參數清單以及函數體。
2、 如果一個運算符函數是成員函數,則它的第一個(左側)運算對象綁定到隐式的this指針上,是以,成員運算符函數的(顯式)參數數量比運算對象的數量少一個。
注:
如MyClass類重載運算符+為成員函數。
MyClass::MyClass operator+(const MyClass& c1)
{
MyClass c2;
// 執行相應的操作
return c2;
}
調用:C1 = C2 + C3;
// 僞代碼,用于說明調用成員函數的實際執行過程
C1 = C2.operator+(&C2, C3);
是以,成員運算符函數的(顯式)參數數量比運算對象的數量少一個
3、 當我們定義重載的運算符時,必須首先決定是将其聲明為類的成員函數還是聲明為一個普通的非成員函數。
4.、當我們把運算符定義成成員函數時,它的左側運算對象必須是運算符所屬的第一個對象。
注:
string s = “world”;
string t = s + “!”; // 正确:我們能把一個const char加到一個string對象中
string u = “hi” + s; // 如果+是string的成員,則産生錯誤
如果operator+是string類的成員,則上面的第一個加法等價于s.operator+("!")。同樣的,“hi”+s等價于"hi"+operator+(s)。顯然"hi"的類型是const char,這是一種内置類型,根本沒有成員函數。
2、輸入和輸出運算符
1.、重載輸出運算符<<
通常情況下,輸出運算符的第一個形參是一個非常量ostream對象的引用。之是以ostream是非常量是因為向流寫入内容會改變其狀态:而該形參是引用是因為我們無法直接複制一個ostream對象。
第二個參數一般來說事一個常量的引用,該常量是我們想要列印的類類型。第二個形參是引用的原因是我們希望避免複制實參;而之是以該形參可以是常量是因為(通常情況下)列印對象不會改變對象的内容。
//Time的輸出運算符(友元函數)
friend std::ostream& operator<<(std::ostream& os, const Time& t)
{
os << t.miHour << ":" << t.miMinute << ":" << t.miSecond;
return os;
}
調用:std::cout << t1; // 等價于:operator+(std::cout, t1);
std::cout << t1 << t2; // 實作連續輸出,等價于:operator+(operator+(std::cout, t1), t2);
2、輸入輸出運算符必須是非成員函數
與iostream标準庫相容的輸入輸出運算符必須是普通的非成員函數,而不能是類的成員函數。否則,它們的左側運算對象将是我們的類的一個對象。
Time的輸出運算符(成員函數)
std::ostream& operator<<(std::ostream& os)
{
os << miHour << ":" << miMinute << ":" << miSecond << std::endl;
return os;
}
調用:
t1 << std::cout; // 不好:與大家習慣吧std::cout放在左邊的習慣不符
t1 << (t2 << std::cout) // 若要實作連續輸出,寫法與大家習慣不符
假設輸入輸出運算符是某個類的成員,則它們必須是istream或ostream的成員,然而,這兩個類屬于标準庫,并且我們無法給标準庫中的類添加任何成員。當然,IO運算符通常需要讀寫類的非公有資料成員,是以IO運算符一般被聲明為友元。
3、重載輸入運算符>>
通常情況下,輸入運算符的第一個形參是運算符将要讀取的流的引用,第二個形參是将要讀入到的(非常量)對象的引用。該運算符通常傳回某個給定流的引用。第二個形參之是以必須是個非常量是因為輸入運算符本身的目的就是将資料讀入到這個對象中。
friend std::istream& operator>>(std::istream& is, Time& t)
{
is >> t.miHour >> t.miMinute >> t.miSecond;
if (is.fail())
{
// 避免第一次錯誤的狀态影響第二次輸入,
is.clear(); // 先将cin的錯誤狀态清除
is.ignore(); // 再将輸入緩沖區的内容忽略掉
t = Time(); // 輸入失敗:對象被賦予預設狀态
}
}
調用:std::cin >> t1; // 等價于:operator>>(std::cin, t1)
std::cin >> t1 >> t2; // 等價于:operator>>(operator>>(std::cin, t1), t2)
是以,輸入運算符必須處理輸入可能失敗的情況,而輸出運算符不需要。
3、算術和關系運算符
1、相等運算符(operator==)
operator==定義為類成員
bool Time::operator==(const Time& t)
{
bool bRet = false;
if ((miHour == t.miHour) && \
(miMinute == t.miMinute) && \
(miSecond == t.miSecond))
{
bRet = true;
}
return bRet;
}
operator==定義為非類成員
bool operator==(const Time& t1, const Time& t2)
{
bool bRet = false;
if ((t1.miGetHour() == t2.miGetHour()) && \
(t1.miGetMinute() == t2.miGetMinute()) && \
(t1.miGetSecond() == t2.miGetSecond()))
{
bRet = true;
}
return bRet;
}
2、關系運算符(operator<)
operator<定義為類成員
bool Time::operator<(const Time& t)
{
if ((miHour == t.miHour) && \
(miMinute == t.miMinute) && \
(miSecond == t.miSecond))
{
return true;
}
else
{
return false;
}
}
operator<定義為非類成員
bool operator<(const Time& t1, const Time& t2)
{
int iSecond1 = t1.miGetHour() * 3600 + t1.miGetMinute() * 60 + t1.miGetSecond();
int iSecond2 = t2.miGetHour() * 3600 + t2.miGetMinute() * 60 + t2.miGetSecond();
bool bRet = false;
if (iSecond1 < iSecond2)
{
bRet = true;
}
return bRet;
}
3、指派運算符(operator=)
不論形參的類型是什麼,指派運算符都必須定義為成員函數,指派運算符要傳回其左側運算對象的引用
operator=定義為類成員
Time& operator=(const Time& t)
{
if (this != &t)
{
miHour = t.miHour;
miMinute = t.miMinute;
miSecond = t.miSecond;
}
return *this;
}
調用:t1 = t2 = t3; // 等價于:operator=(t1, operator=(t2, t3))
4、複合指派運算符(operatoe+=)
複合運算符不非得是類的成員,不過我們還是傾向于把包括複合指派運算符在内的所有指派運算符都定義在類的内部。為了與内置類型的複合指派保持一緻,類中的複合指派運算符也要傳回其左側運算對象的引用。
operatoe+=定義為成員函數
Time& Time::operator+=(const Time& t)
{
miHour += t.miHour;
miMinute += t.miMinute;
miSecond += t.miSecond;
return *this;
}
operatoe+=定義為非成員函數
Time& operator+=(Time& t1, const Time& t2)
{
int iHour = t1.miGetHour() + t2.miGetHour();
int mMinute = t1.miGetMinute() + t2.miGetMinute();
int iSecond = t1.miGetSecond() + t2.miGetSecond();
t1.mvSettHour(iHour);
t1.mvSetMinute(mMinute);
t1.mvSetSecond(iSecond);
return t1;
}
5、下标運算符
下标運算符必須是成員函數,下标運算符通常以通路元素的引用作為傳回值,這樣做的好處是下标可以出現在指派運算符的任意一端。進一步,我們最好同時定義下标運算符的常量版本和非常量版本,當作用于一個常量對象時下标運算符傳回常量引用以確定我們不會給傳回的對象指派。
class StrVec
{
public:
std::string& operator[](std::size_t n)
{
return elements[n];
}
const std::string& operator[](std::size_t n)
{
return elements[n];
}
private:
std::string *elements;
};
6、遞增和遞減運算符(operator++(int)、operator++()、operator–(int)、operator–())
前置遞增/遞減運算符
前置運算符應該傳回遞增或遞減後對象的引用。
前置遞增運算符:
StrBlobPtr& StrBlobPtr::operator++()
{
check(curr, "increment past end of StrBlobPtr");
++curr;
return *this;
}
前置遞減運算符:
StrBlobPtr& StrBlobPtr::operator--()
{
--curr;
check(curr, "increment past end of StrBlobPtr");
return *this;
}
後置遞增/遞減運算符
後置運算符應該傳回對象的原值,傳回的形式是一個值而非引用。
後置遞增運算符
StrBlobPtr StrBlobPtr::operaor++(int)
{
StrBlobPtr ret = *this;
++(*this)
return ret;
}
後置遞減運算符
StrBlobPtr StrBlobPtr::operator--(int)
{
StrBlobPtr ret = *this;
--(*this);
return ret;
}
顯示調用後置類型:
StrBlobPtr p(a1); // p指向a1中vector
p.operator++(0); // 調用前置版本的operator++,傳入的值通
常被運算符函數忽略,但卻是必不可少,因為編譯器隻有通過它才能知道應該使用前置版本
7、成員通路運算符(operaot->()、operator())*
class StrBlobPtr
{
public:
std::string& operator*() const
{
autp p = cheack(curr, "dereference past end");
return (*p)[curr]; // (*p)是對象指向vector
}
std::string& operator->() const
{
// 将實際工作委托給解引用運算符
return & this->operator*();
}
};
箭頭運算符不執行任何自己的操作,而是調用解引用運算符并傳回解引用結果元素的位址。
箭頭運算符必須是類的成員,解引用運算符通常也是類的成員,盡管并非必要如此。
重載的箭頭運算符必須傳回類的指針或者自定義了箭頭運算符的某個類的對象。
8、函數調用運算符(operator()())
struct sbsInt {
int operator()(int val) const {
return (val < 0) ? -val : val;
}
};
函數調用運算符必須是成員函數。一個類可以定義多個不同版本的調用運算符,互相之間應該在參數數量或類型上有所差別。
如果類定義了調用運算符,則該類的對象稱作函數對象。因為可以調用這種對象,是以我們說這些對象的“行為像函數一樣”。