1. 程式人生 > >C++ 面向物件- -運算子的過載(三)

C++ 面向物件- -運算子的過載(三)

目錄

不同型別資料間的轉換

1、標準型別資料間的轉換

2、轉換建構函式

3、型別轉換函式

關於運算子過載的歸納


 

不同型別資料間的轉換

1、標準型別資料間的轉換

在以前我們對一個變數初始化或者不同資料間進行運算時,都會涉及到資料型別的轉換,只是有顯式和隱式的區別。隱式型別轉換是編譯系統自動完成的,不需要使用者干預,例:

int i=6;
i=7.5+i;

編譯系統對 7.5 是作為 double 型數處理的,在求解表示式時,先將 i 的值 6 轉換成 double 型,然後與 7.5 相加,得到和為 13.5 ,在向整型變數 i 賦值時,將 13.5 轉換成整數 13 ,然後賦給 i 。

顯式型別轉換是人為地指定將一種指定的資料轉換成另一指定的型別,其形式為: 型別名(資料),或者 (型別名)資料,但提倡用後邊那種形式,看著比較清晰。

 

2、轉換建構函式

轉換建構函式的作用是將一個其型別的資料轉換成一個類的物件。在此之前回顧一下之前學的建構函式,以現有一個複數類為例,預設建構函式(Complex(),無形參)、用於初始化的建構函式(Complex(double r,double i),為類物件進行初始化)、複製建構函式(Complex(Complex c),形參是本類物件)。

轉換建構函式只有一個形參,如 

Complex (double r){
    real=r;
    imag=0;
}

作用就是將 double 型引數 r 轉換成 Complex 類的物件,將 r 作為複數的實部,虛部為0(這部分可以根據需要定義轉換建構函式)。在一般的類體中,可以有轉換建構函式,也可以沒有轉換建構函式,視需要而定。

舉一些例子來說:如果在Complex 類中定義了上面的轉換建構函式,那麼在 Complex 類的作用域內可定義 Complex c1(3) ,由於其只有一個引數,故會呼叫上面的轉換建構函式,將 int 型常數轉換成一個名為 c1 的物件,其實部為 3 ,虛部為 0;也可以建立一個無名物件,即Complex (3),雖然是合法的,但無法使用它,但可以有 c2 = Complex (3),其作用是建立一個無名物件,值為(3+0i),然後將此無名物件的值賦給 c2 ,c2的值此時就是(3+0i);又如果已對運算子 “+”,進行了過載,使之能進行兩個 Complex 類物件進行相加,若有表示式:c3=c1+2 ,編譯是出錯的,應先將 int 型數轉換成類物件後再相加,即 c3=c1+Complex(2)。轉換建構函式的作用類似於前邊的強制型別轉換,可以認為 Complex(2)也是作一個強制型別轉換,是將 2 轉換成 Complex 類物件。

轉換建構函式也是建構函式的一種,遵循建構函式的一般規則,它的函式體中也可以不做型別轉換功能,比如 Complex (double r){cout<< r ; },但這種用法沒有任何意義,雖然轉換建構函式是根據使用者需要而確定,但最好使它有一定的實際意義。需要注意的是,轉換建構函式只能有一個引數,如果引數多了的話,那麼到底是將哪個引數轉換成類的物件?另外轉換建構函式不僅能把一個標準型別資料轉換成類物件,也能夠把另一個類的物件轉換成建構函式所在的類物件,比如一個學生畢業後當了老師,可以將學生類物件轉換成教師類物件,就是把學生的編號、姓名、性別複製到一個教師類物件中:

Teacher (Student s){
    num=s.num;
    sex=s.sex;
    strcpy(name,s.name);
}// 學生類中的這幾個資料成員需要是 public 的,不然不能再類外訪問。

 

3、型別轉換函式

該函式與上邊的轉換建構函式功能恰恰相反,轉換建構函式是為了將一個標準型別的資料(如 int 型)轉換成一個類物件,而型別轉換函式則是將一個類物件轉換成一個標準型別的資料。其一般形式是 : operator 型別名(){實現轉換的語句};注意型別轉換函式的函式名是 operator 型別名 ,這是一部分,同為其函式名,別搞混淆了。那我們會發現型別轉換函式的函式名前沒有函式型別,也沒有引數,其返回值的型別是由函式名中指定的型別名來確定的,如:

operator int (){
    return real ;
}

函式返回 int 型別的real值,它的作用是將一個 Complex 類物件轉換成一個 int 型資料,其值是類中資料成員 real 的值,該函式名是 operator int ,有點類似運算子過載函式(operator+)。因為型別轉換函式轉換的主體是一個類物件,且沒有形參,故它只能作為該類的成員函式,不能作為友元函式或者一般函式,那麼有個問題是,Complex類物件是不是就一律轉換成 int 型別資料呢?C++中規定,它們是具有“雙重身份”,既是 Complex 類物件,又可作為 int 型別資料,相當於一個人有兩種國籍,在不同的場合下用不同的面貌。

舉一些例子來說明這些建構函式間複雜的使用關係:若已定義 i1 ,i2 作為 int 型變數, c1 , c2 作為 Complex 類物件,並且類中已定義了型別轉換函式,有表示式 : i1=i2+c1 ,在編譯時系統發現 + 左側的 i2 是 int 型,右側的 c1 是 Complex 類,如果沒有對運算子 “+” 進行過載,就會檢查有無型別轉換函式,結果發現有對 int 型的過載函式,系統就會呼叫該函式,把 Complex 類物件 c1 轉換成 int 型資料,建立一個臨時的 int 型資料,並與 i2 相加,把最後的結果值賦給 i1 ;如果類中已經定義了轉換建構函式並且又過載了運算子 “+”,但沒有對 int 型定義型別轉換函式(或者說沒有對 int 過載),若有表示式: c1=c1+i1 ,在編譯時系統發現 + 左側的 c1 是Complex 類,右側的 i1 是int 型,編譯系統就會找有無對 + 的過載,發現有 operator + 函式,用於將兩個複數類物件進行相加,但現在 i1 是 int 型 ,不合要求,在類中有沒有對 int 進行過載,因此不可能把 c1 轉換成 int 型資料再相加,那麼編譯系統找有無轉換建構函式,發現有,就呼叫該函式去建立一個臨時物件Complex(i1),再呼叫 operator+函式,將兩個複數類相加,最後賦給 c1 。

通常把型別轉換函式稱為型別轉換運算子函式,由於它也是過載函式,因此也稱為型別轉換運算子過載函式(或強制型別轉換運算子過載函式)。

下面是一個例子,涉及幾個函式的過載:

#include <iostream>
using namespace std;
class Complex{
		double real;
		double imag;
	public:
		Complex(double r=0,double i=0):real(r),imag(i){}
		friend Complex operator+(Complex ,Complex );
//		operator int (){
//			return real;
//		}
		Complex(int i){
			real=i;
			imag=0;
		} 
		friend ostream& operator<<(ostream& , Complex&);
};
Complex operator+(Complex c1,Complex c2){
	return Complex(c1.real+c2.real,c1.imag+c2.imag);
}
ostream& operator<<(ostream& output , Complex& c){
	output<<"("<<c.real << ","<<c.imag<<"i)"<<endl;
	return output;
}
int main(){
	Complex c1(1,1);
	Complex c2(2,2);
	Complex c3;
	int i=1 ;
	int j=2 ;
//	j=i+c1;
	cout<<"j="<<j<<endl;
	c3=c1;
//	j=c2+c3;
	cout<<"j="<<j<<endl;
	c3=j+c2;
	cout<<c3;
} 

關於型別轉換函式、轉換建構函式、運算子的過載等函式,如果一起使用的話注意不要出現二義性,我上邊的例子中就有這種情況,大家有興趣可以自己試一下,填入一些資料看看結果是不是自己所想要的,以及錯的話該怎麼改。還記得前邊在說運算子+過載函式時,要將一個標準型別資料和一個類物件相加,如果兩者的位置不一樣,作為友員過載函式只需將形參對應互換一下位置,或者再過載一次,而如果作為成員過載函式就會出錯,此處運用轉換建構函式和運算子+過載函式就能夠實現自由相加,但如果運算子過載函式是作為成員函式的話,交換律是不適用的,作為成員函式需要其第一個運算元必須是類物件,也正是由於這個原因,一般情況下將雙目運算子過載為友元函式,單目運算子則多過載為成員函式,有興趣也可以都試一下。

總之這些運算子的過載和建構函式都是根據問題需要進行設定,一般情況下用到的地方不多,但還是很重要的知識點。

 

關於運算子過載的歸納

運算子的過載使類的設計更加豐富多彩,擴大了類的功能和適用範圍,使程式易於理解,也易於對物件進行操作,體現了為使用者著想、方便使用者使用的思想。有了運算子的過載,在聲明瞭類之後,我們就可以把用於標準型別的1運算子用於自己宣告的類,類的宣告是一勞永逸的,有了一個好的類後,在程式中就不必定義許多成員函式去完成某些運算和輸入輸出的功能,使主函式更加簡單易讀,好的運算子過載能體現面向物件程式設計思想。

但在設計運算子過載時要注意:1、要先確定過載的是哪一個運算子,以及要將它作用於哪個類,過載運算子只能用於一個指定的類。2、設計運算子過載函式的功能完全是由設計者指定,最終的目的是實現使用者對使用運算子的要求。3、在實際工作中,這些運算子的過載都是已經被設計好的,統一放在一個頭檔案中(檔名自定)。4、我們需要對運算子的過載能夠看懂,知道具體實現什麼功能。5、如果有需要,並無現成的過載運算子可用,此時需要自己設計,最好把每次設計的運算子過載函式保留下來,以免以後會再用到。

最後說一點在過載運算子中使用引用的重要性(我好像都不習慣用引用。。。),引用是通過地址傳遞使形參稱為實參的別名,沒有生成臨時變數,減少時間和空間的開銷。此外,如果過載函式返回值是物件的引用時,返回的不是常量,而是引用所代表的物件,它可以出現在賦值號的左側而成為左值,可以被賦值或參與其他操作(如 保留cout 流的當前值以便能連續用 << 輸出 ),但使用引用時要小心,因為改變了引用就等於改變了它所代表的物件。