運算符重載在c++中是經常用到的一個知識點,也是c++富有擴展性的重要支持點。
今天我準備把運算符重載在各種情況下的應用,都一一舉例,待會我們就會知道運算符重載的威力和帶來的便捷性。
運算符重載函數的兩種形式:
1、作爲類的成員函數
賦值(=)、下標([])、調用( () )、成員訪問箭頭(->) , 這四個運算符必須是成員函數,其他運算符既可以是成員函數,也可以使非成員函數。
注意點:如果我們把運算符函數定義成成員函數時,它的左側運算對象必須是所屬類的一個對象。
2、作爲非成員函數
一般情況下,只有具有對稱性的運算符纔會定義成非成員函數(比如: &&、==) , 非成員函數,一般情況下都是類的友員函數。
由此可以看出,如果一個運算符被定義爲非成員函數的形參數量比其定義爲成員函數多一個。
下面是各種運算符的重載
1、重載輸入輸出運算符
<span style="font-size:12px;">class base
{
public:
base() {}
base(int new_x , double new_y):
x(new_x) , y(new_y) {}
friend istream &operator>>(istream &in , base &chs);
friend ostream &operator<<(ostream &out , base &chs);
private:
int x;
double y;
};
istream &operator>>(istream &in , base &chs)
{
in>>chs.x>>chs.y;
return in;
}
ostream &operator<<(ostream &out , base &chs)
{
out<<chs.x<<" "<<chs.y;
return out;
}</span>
如上圖的代碼:
1、輸入輸出必須得定義成友元函數,因爲如果我們定義爲成員函數,那麼就意味着第一個參數必須是所在類的對象,則我們調用的時候就得這樣:
<span style="font-size:14px;"> base x ;
x>>cin;</span>
2、我們一般情況下都要返回流的引用,因爲這是爲了可以連續的輸入: cin>>x>>y;
2、算術和關係運算符
算數運算符主要是普通的運算符,如: + - * / && || !等運算符
其中,算術運算符和關係運算符一般都是非成員函數。
下面解釋爲什麼算術運算符和關係運算符不能是成員函數:
1、如果運算符 ‘+’ 是成員函數
class base
{
public:
base() {}
base(int new_x , double new_y):
x(new_x) , y(new_y) {}
<span style="color:#ff0000;">base operator +(const base &chs)
{
x += chs.x;
y += chs.y;
return *this;
}</span>
private:
int x;
double y;
};
base x(1 , 2.1) , y(3 , 3.4) , z;
z = x+y; //這個時候即改變了x , 也改變了 z , 但我們的本意是隻改變在 , 而不改變x;
只有當參數被列於參數列內,這個參數纔是隱式類型轉換的合格參與者
2、下面是友元函數
base operator +(const base &ch , const base &chs)
{
base bz = ch;
bz.x += chs.x;
bz.y += chs.y;
return bz;
}
<pre class="cpp" name="code">base x(1 , 2.1) , y(3 , 3.4) , z;
z = x+y; // 這個時候,我們由於在函數中用了一箇中間變量,因此只改變了 z , 而x不變
通過上面我們知道,對於一個運算符重載的函數形式很關鍵,如果用錯了函數形式,那麼可能會帶來意料不到的危險。
因此,當我們要寫一個運算符重載的函數時,我們首先要弄清這個運算符是幹什麼了 ,在內置類型上有哪些用法,我們再根據這些用法去決定,是要寫成成員函數 , 還是非成員函數。
3、下標運算符 [ i ]
下標運算符主要是用於數組、指針、容器等,下標運算符的作用有下面幾點:
1、通過下標運算符,得到數組中該位置的值
2、我們還需要通過下標運算符改變該位置的值
我們能知道,下標運算符一定是要作爲成員函數。
根絕上面的作用分析,我們可以得到
1、函數需要一個整形形參,告訴函數要去哪個位置的值
2、函數必須得返回該位置的引用值
3、我們需要重載一個常量版本的函數。
由上面,我們就得到了代碼:
char &base::operator [](int i)
{
if(i >= length) return chs[0];
return chs[i];
}
const char &base::operator [](int i) const
{
if(i >= length) return chs[0];
return chs[i];
}
4、遞增遞減運算符
在迭代器或平時的內置類型上,我們經常都會使用 (--)和(++)運算符,對於初學者來說,他們肯定特想把這兩個運算符弄死掉。下面我們就以(++)運算符,來分析其作用;
對於遞增(++)運算符的作用
1、運算符在變量前面,直接遞增變量的值
2、運算符在變量後面,這時我們就要先保存對象的狀態,然後再把 遞增的值傳給他
對於這兩個運算符,我們一般把函數寫爲成員函數。
我們先寫遞增運算符前置的函數,前置時,我們返回的是該變量自身的引用
base &base::operator ++()
{
x += 1;
return *this;
}
遞增運算符(++) 後置,這時我們就要考慮怎麼才能實現,先保存其狀態?
我們這樣,我們先返回一箇中間變量 , 那我們下次調用該變量時,就是遞增之後的變量了。
由於我們返回的是中間變量,所以我們不能返回引用了。
<span style="color:#000000;">base base::operator ++<span style="color:#cc0000;">(int)</span>
{
base bz = *this;
x += 1;
return bz;
}</span><span style="color:#990000;">
</span>
之所以加上一個整形形參,是因爲編譯器需要區分前置和後置兩個情況。因此下次調用時,我們就必須傳遞一個整形給它
5、函數調用運算符 ()
函數調用運算符必須得是成員函數。
函數調用運算符,使得我們可以像調用函數一樣,調用該類的對象
class base
{
public:
void operator ()() //沒有參數的函數調用運算符
{
cout<<x<<endl;
}
void operator() (int z) //存在一個整形形參
{
cout<<x+z<<endl;
}
private:
int x;
};
//下面是調用形式
base x;
x(); //調用第一個函數
x(4); //調用第二個函數
c++中的lambda表,就類似於在一個類中重載了函數調用運算符函數。
6、類型轉換運算符 , int double
類型轉換運算符負責把類轉換成另個需要的類型
因此,其沒有顯示的返回類型,也沒有任何參數。
class base
{
public:
operator int()
{
return x; //把類類型轉黃成一個整形
}
private:
int x;
};
//這是一個既可以隱身轉換,也能顯示轉換的
base x;
int z = x , y = static_cast<int>(x);
//如果在函數前面加上explicit就只能顯示的轉換
explicit operator int();
int x = z; //這是錯誤的 , 不能隱式轉換
int x = static_cast<int>(x);
當我們重載類型轉換運算符時,我們一定要小心二義性。
運算符重載,確實給我們帶來了很大自由和便捷。但同時它也帶來了危險,因此在使用的時候我們一定要對家注意。
我們重載運算符時,我們要記住,重載的運算符,一定要符合這個運算符本身的特性,也要儘量滿足我們再內置類型上對這個運算符的使用習慣。
共勉。