關卡一:運算符重載(operator overloading)
故事背景:也許你沒有感覺到,其實我們已經在C++中使用【運算符重載】了。事實上計算機處理整數、單精度數和雙精度數加法的操作方法是很不相同的,但由于C++已經對運算符“+”進行了重載,是以“+”就能适用于int,float,double類型的不同運算。另外,我們使用的流插入運算符(<<)與流提取運算符(>>),在C++中也已經進行了重載,這就是頭檔案中“iostream”的由來。
說了這麼多,那麼運算符重載究竟是幹什麼的?又是怎樣使用的?
前面已經提到,我們可以對标準類型的資料進行四則運算,但不能對諸如複數等類型的資料進行四則運算,這裡運算符重載函數就登上舞台了。
☆副本:
#PartⅠ 對運算符重載的方法
重載運算符的函數一般格式如下:
函數類型 operator運算符名稱(形參表)
{對運算符的重載處理}
例如我想利用“+”實作兩個複數的相加時,可以如下定義:
Complex operator+(Complex &c1,Complex &c2);
注:“operator”是關鍵字,operator和運算符組成函數名,如上面的“operator+”是函數名,在這裡的意思是“對運算符+重載的函數”。這裡需要注意的是重載的運算符應該是C++中已有的運算符。
Part Ⅱ 重載運算符的規則
1.C++不允許使用者自己定義新的運算符,隻能對已有的C++運算符進行重載。
2.C++不允許重載的運算符有5個:(1)成員運算符 “.”;(2)成員指針通路運算符(不是乘号)“*”(3)域運算符 “::”;(4)長度運算符 “sizeof”;(5)條件運算符 “?:”。是以,隻要記住這些不能重載的就可以了。
3.重載不能改變運算符運算對象(即操作數)的個數。例如“>”是進行兩個數的大小比較,是以其操作數必須為兩個。
4.重載不能改變運算符的優先級别。例如“/”優先于“-”。隻能通過加圓括号的辦法強制改變重載運算符的運算順序。
5.重載不能改變運算符的結合性。如指派運算符“=”是自左向右結合的,其重載後仍自左向右結合。
6.重載運算符的函數不能有預設的參數,否則就改變了運算符參數的個數。
7.重載的運算符必須和使用者定義的自定義類型的對象一起使用,其參數至少應有一個是類的對象或類對象的引用。即運算符重載函數的參數不能都是标準類型的資料。
8.用于類對象的運算符一般必須重載,但運算符“=”和“&”不必使用者重載。
(1)指派運算符“=”可以用于每一個類的對象,可以利用它在同類對象之間互相指派。
(2)位址運算符“&”也不必重載,它能傳回類對象在記憶體中的起始位址。
9.理論上可以将一個運算符重載為執行任意操作,但為了使程式的可讀性更高,應當将運算符重載為能實作類似于标準類型資料所能實作的功能,如“+”重載後可以實作兩個複數的相加,而不重載為兩個複數相乘。
Part Ⅲ 運算符重載函數的處理方式
類的成員函數、友元函數和普通函數
1.類的成員函數
定義:Complex operator+(Complex &c2);
這時你可能會有疑問,複數的相加不是應該有兩個操作數嗎?其實,在運算符重載函數定義為成員函數後,有一個參數是隐含的,這是因為有一個this指針隐式地通路類對象成員。編譯系統做出的了解是c1.operator(c2)(假設c1和c2已經定義),即通過對象c1調用運算符重載函數“operator+”,并以表達式中第2個參數(上式中的c2)參數作為函數實參。
2.友元函數
定義:Complex operator+(Complex &c1,Complex &c2);
我們可以清楚地看到,友元函數與上述的成員函數的差別是,友元函數有兩個形參。這時,編譯系統做出的了解是,operator+(c1,c2)。
3.普通函數
由于在類外定義的函數不能通路類的私有資料成員,是以在把運算符重載函數定義為普通函數時,需将類的資料成員定義為公有的,這破壞了資料的隐蔽性原則,是以這種方法很少使用,在此不再贅述,下面會有樣例。
讨論:究竟是使用成員函數好還是使用友元函數好呢?我們做出以下的探究
(1)如果将運算符重載函數作為成員函數,它可以通過this指針自由的通路本類的資料成員,是以可以少寫一個函數的參數。但必須要求運算表達式中第一個參數(運算符左側的操作數)是一個類的對象,而且與運算符函數的類型相同。因為必須通過類的對象去調用該類的成員函數,而且隻有運算符重載函數傳回值與該對象同類型,運算結果才有意義。
(2)将雙目運算符重載為友元函數時,由于友元函數不是該類的成員函數,是以在函數的形參表列中必須有兩個參數,不能省略。
(3)從以上的分析可以看出,無論是成員函數還是友元函數,都不适用數學上的交換律。
(4)由于友元函數的使用會破壞類的封裝,是以從原則上說,要盡量将運算符函數作為成員函數。
(5)下面給出成員函數和友元函數的一般适用範圍
①C++規定,指派運算符=、下标運算符[ ]、函數調用運算符( )、成員運算符->必須作為成員函數。
②流插入運算符“<<”和流提取運算符“>>”、類型轉換運算符不能定義為類的成員函數,隻能定義為友元函數。
③一般将單目運算符和複合運算符(+=、!=、%=等)定義為成員函數。
④一般将雙目運算符定義為友元函數。
4.運算符重載函數的三種形式之執行個體:
題目:重載“+”,使之能實作兩個複數的相加。
(1)成員函數形式:
#include <iostream>
using namespace std;
class Complex
{
private:
double real,image; //定義複數的實部與虛部
public:
Complex(double r=0,double i=0)//定義構造函數
{
real=r;
image=i;
}
void display() //輸出複數
{
cout<<"("<<real<<","<<image<<"i"<<")"<<endl;
}
Complex operator+(Complex &c2);//重載函數聲明為成員函數的形式
};
Complex Complex::operator+(Complex &c2)//定義成員函數
{
Complex c;
c.real=real+c2.real;
c.image=image+c2.image;
return c;
}
int main()
{
Complex com1(1,2),com2(1,2),com3;
com3=com1+com2; //實作兩個複數的相加
com3.display();
return 0;
}
(2)友元函數形式:
#include <iostream>
using namespace std;
class Complex
{
private:
double real,image;
public:
Complex(double r=0,double i=0)
{
real=r;
image=i;
}
void display()
{
cout<<"("<<real<<","<<image<<"i"<<")"<<endl;
}
friend Complex operator+(Complex &c1,Complex &c2);//重載函數聲明為友元函
}; //數的形式
Complex operator+(Complex &c1,Complex &c2)//本例中友元函數必須有兩個形參
{
Complex c;
c.real=c1.real+c2.real;
c.image=c1.image+c2.image;
return c;
}
int main()
{
Complex com1(1,2),com2(1,2),com3;
com3=com1+com2;
com3.display();
return 0;
}
(3)普通函數形式:
#include <iostream>
using namespace std;
class Complex
{
public: //重載函數為普通函數時,資料成員必須定義為公有的,否則
double real,image;//該普通函數将無法調用類的資料成員
Complex(double r=0,double i=0)
{
real=r;
image=i;
}
void display()
{
cout<<"("<<real<<","<<image<<"i"<<")"<<endl;
}
};
Complex operator+(Complex &c1,Complex &c2)//本例中普通函數必須有兩個形參
{
Complex c;
c.real=c1.real+c2.real;
c.image=c1.image+c2.image;
return c;
}
int main()
{
Complex com1(1,2),com2(1,2),com3;
com3=com1+com2;
com3.display();
return 0;
}
☆主線
運算符的重載可以包括雙目運算符重載和單目運算符重載,下面我将分開來介紹,介紹形式以代碼執行個體為主。
PartⅠ 雙目運算符的重載
導語:雙目運算符(或稱為二進制運算符)是C++中最常用的運算符。雙目運算符有兩個操作數(參與運算的資料),通常在運算符的左右兩側,如a+b,a<b等。
執行個體:聲明一個字元串類String,用來存放不定長的字元串,重載運算符“==”,“<”和“>”,使它們能用于兩個字元串的等于、小于和大于的比較。
#include <iostream>
#include <string>
#include <cstring> //使用strcmp函數必須使用該頭檔案
using namespace std;
class String
{
public:
String(){p=NULL;}//定義預設構造函數
String(const char *str);//聲明構造函數
void display();
friend bool operator>(String &st1,String &st2);//聲明“>”的重載函數
friend bool operator<(String &st1,String &st2);//聲明“<”的重載函數
friend bool operator==(String &st1,String &st2);//聲明“==”的重載函數
private: //此處必須為“==”,“=”為指派号
const char *p;//字元型指針,用于指向字元串
};
String::String(const char *str)//定義構造函數
{
p=str;
}
void String::display()//輸出p所指向的字元串
{
cout<<p;
}
bool operator>(String &st1,String &st2)//定義“>”的重載函數
{
if(strcmp(st1.p,st2.p)>0)
return true;
else
return false;
}
bool operator<(String &st1,String &st2)//定義“<”的重載函數
{
if(strcmp(st1.p,st2.p)<0)
return true;
else
return false;
}
bool operator==(String &st1,String &st2)//定義“==”的重載函數
{
if(strcmp(st1.p,st2.p)==0)
return true;
else
return false;
}
void Compare(String &st1,String &st2)//用于兩個字元串的比較,減輕主函數的負擔
{ //同時使程式可讀性更高
if(operator>(st1,st2)==1)
{
st1.display();cout<<">";st2.display();
}
else if(operator<(st1,st2)==1)
{
st1.display();cout<<"<";st2.display();
}
else if(operator==(st1,st2)==1)
{
st1.display();cout<<"=";st2.display();
}
cout<<endl;
}
int main()
{
String st1("welcome"),st2("come"),st3("home"),st4("welcome");//定義對象
Compare(st1,st2);//字元串比較
Compare(st2,st3);//字元串比較
Compare(st1,st4);//字元串比較
return 0;
}
通過分析上面的這個執行個體,我相信大家會對雙目運算符的了解更加深刻,并且使用更加熟練。
PartⅡ 單目運算符的重載
單目運算符中自增自減運算符應用較多,是以我們在這裡以自增運算符(自減運算符與此類似)為例。
我們知道自增分為前置自增與後置自增,那麼我們在運算符重載中應該怎樣區分呢?在C++中,我們在自增運算符重載函數中,增加一個int型形參(該形參不需确切的名稱,隻寫int即可),得到的是後置自增,這樣就可以區分前置自增與後置自增了。
導語:前置自增是先自加,傳回的是修改後的對象本身;後置自增傳回的是自加前的對象,然後對象自加。
執行個體:有一個Time類,包含資料成員min(分)和sec(秒),模拟秒表,每次走一秒,滿60秒進一分鐘,此時秒又從0開始算。要求輸出分和秒的值。
#include <iostream>
using namespace std;
class Time
{
public:
Time(){min=0;sec=0;}//定義構造函數
Time(int m,int s):min(m),sec(s){}//定義構造函數
void display();//聲明輸出時間的函數
Time operator++();//聲明前置自增運算符的重載函數
Time operator++(int);//聲明後置自增運算符的重載函數,形參中必須有“int”
private:
int min,sec;
};
void Time::display()//定義輸出時間的函數
{
cout<<"Minute: "<<min<<";"<<"Second: "<<sec<<endl;
}
Time Time::operator++()//定義前置自增運算符的重載函數
{
if(++sec>=60)
{
sec-=60;
min+=1;
}
return *this;//傳回自加後的目前對象
}
Time Time::operator++(int)//定義後置自增運算符的重載函數
{
Time temp(*this);//建立臨時對象temp
sec++;
if(sec>=60)
{
sec-=60;
min+=1;
}
return temp;//傳回的是自加前的對象
}
int main()
{
Time t1(18,30),t2;
++t1; //調用前置自增運算符的重載函數
t1.display();
t2=t1++; //t2儲存的是執行t1++之前的t1的值
t2.display();
t1.display(); //執行t1++後的t1的值
return 0;
}
當然,單目運算符也可以定義為友元函數,大家可以嘗試一下。
通過對運算符重載的分析我們可以看出,運算符重載函數的出現友善了進行不同對象間的運算。由于我們有時候在程式中輸出的不止是标準類型的資料,是以對流插入和流提取運算符的重載是非常有必要的,在續集中我将介紹這兩個運算符的重載。
了解了運算符重載後,你可能會有疑惑,程式還能執行運算符本來的含義嗎?如果能執行,那麼編譯系統又是怎樣識别的呢?關于這點,我同樣會在續集中做出詳細的解釋。
我們續集再回!