第12章-運算符重載課件_第1頁
第12章-運算符重載課件_第2頁
第12章-運算符重載課件_第3頁
第12章-運算符重載課件_第4頁
第12章-運算符重載課件_第5頁
已閱讀5頁,還剩69頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領

文檔簡介

本章要點:什么是運算符重載一元運算符的重載二元運算符的重載通過友元函數(shù)實現(xiàn)重載輸出、輸入運算符的重載在前面學習類和對象的時候,我們明確了這樣一個概念:定義一個類就是定義一種新類型。因此,對象和變量一樣,可以作為函數(shù)的參數(shù)傳遞,可以作為函數(shù)的返回值的類型,也可以說明對象數(shù)組,甚至還可能有類類型的常量。在基本數(shù)據(jù)類型上,系統(tǒng)提供了許多預定義的運算符,它們以一種簡潔的方式工作。例如“+”運算符:int x,y;y=x+y;表示兩個整數(shù)相加,很簡潔。但是,兩個字符串合并:char x[20],y[10]; //定義兩個字符串類型//……strcat(x,y);表達起來就不如“y=x+y;”那樣直觀簡潔。因此,為了表達上的方便,希望已預定義的運算符,也可以在特定類的對象上以新的含義進行解釋。如在string類對象x、y的環(huán)境下,運算符“+”能被解釋為字符串x和y的合并。換言之,希望預定義運算符能被重載,讓用戶自定義的類也可以使用。一、為什么要進行運算符重載C語言中有多種內(nèi)置的數(shù)據(jù)類型,例如int、float、double和char等等。對應于每一種類型都有一系列的內(nèi)置運算符,比如加法運算符“+”和乘法運算符“*”。就拿運算符“+”來說吧,它可以用于int值,也可以用于float,雖然使用相同的運算符,但生成的代碼不同,因為整數(shù)和浮點數(shù)在內(nèi)存中的表示是不同的。這時,“+”運算符具有兩種不同的解釋(即實現(xiàn)代碼)。也就是說,像“+”這樣的運算符在C語言中已被重載。但是,C語言僅支持少量有限的運算符重載。C++語言對重載功能進行了擴充,也允許我們在自己定義的類上添加運算符,允許對已存在的預定義運算符由我們在不同的上下文中做出不同的解釋。比如,經(jīng)過運算符重載,我們可以直接對兩個字符串string類對象進行加法運算stringx,y;x=x+y;可以直接輸出復數(shù)類CComplex對象Ccomplexc;cout<<c;通過運算符重載,程序代碼就顯得簡潔明了,類對象的使用也方便了。這就是我們重載運算符的目的。第一個基本動作就這么簡單,沒問題吧?好了,如果第一關沒問題,可以繼續(xù)前進了。先看看下面這段話:因為本書針對C++的入門讀者,所以對一些深層次復雜的運算符重載,比如new和delete的重載以及類型轉換等等不做介紹,以免倒了你的胃口。這里我們只介紹一下常用的運算符重載的實現(xiàn),讓大家明白運算符重載是怎么回事、如何實現(xiàn),就算是完成任務了。如果想進一步學習復雜的高級的重載,可以參見別的參考書。運算符重載函數(shù)可以作為類的成員函數(shù),也可以作為類的友元函數(shù)來實現(xiàn)。下面對這兩種不同的實現(xiàn)分別講述。通俗地說,重載運算符就是在寫函數(shù),用這個運算符函數(shù)體對重載的運算符的含義做出新的解釋。這里所解釋的含義與重載該運算符的類有關,比如,字符串類重載的加運算符“+”,實際上是對兩個字符串進行拼接。重載后的運算符還是按這些運算符的表達方式使用。例如,在一個string類中重載了運算符“+”為兩個字符串的合并,我們可以這樣寫:strings1,s2;s1=s1+s2; //合并串s1和串s2,存放到新串s1中。為了把運算符從頭到腳地看個明明白白,我們創(chuàng)建一個新類:計數(shù)器類CCounter,逐步地根據(jù)需求來完善此類,并進一步地解釋為什么要重載運算符以及如何來重載。例15.1創(chuàng)建類CCounter,類CCounter的一個對象可以用來在循環(huán)中計數(shù)(感到驚奇吧),而且這個類的對象還可以用在其它程序中以實現(xiàn)數(shù)字遞增、遞減或跟蹤一些值的地方。二、 以成員函數(shù)實現(xiàn)運算符重載程序清單C15_01.cpp//類Counter的簡單定義及實現(xiàn)#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} //缺省構造函數(shù),成員變量m_val初始化為0 ~CCounter(){} //什么事都不干的析構函數(shù) //下面兩個函數(shù)是私有變量m_val的存取訪問函數(shù) unsignedGetVal()const {returnm_val;} voidSetVal(unsignedx) {m_val=x;}private: unsignedm_val;};main(){ CCountercounter; cout<<"counter的值是:"<<counter.GetVal()<<endl;}輸出結果counter的值是:0程序分析:事實上,上面創(chuàng)建的CCounter類是一個“垃圾類”,沒有任何用處,只有惟一的一個成員變量m_val,而且被缺省的構造函數(shù)初始化為零。用類CCounter創(chuàng)建的對象不能像整型變量那樣進行自增和自減運算,也不能進行加法、賦值或者其它的算術操作。更惡劣的是,這個類創(chuàng)建的對象的輸出也是一個問題,不能像整型數(shù)那樣直接用cout進行輸出。通過我們即將要學習的運算符重載來對CCounter類進行“教化”,使它能夠像一般的整型數(shù)一樣來進行一系列的算術運算。再次說明,我們只是想通過對類CCounter的逐步改造來講解運算符重載的知識,至于這個類本身,用處倒不大。1. 重載一元運算符:遞增運算符“++”(1)通過添加成員函數(shù)Increment(),實現(xiàn)自增功能運算符重載可以使類獲得使用這些運算符的“權限”。例如,我們可以通過下面兩種方式讓類CCounter的對象具有自增運算的功能。一種方式是通過在類CCounter中添加一個實現(xiàn)自增功能的方法Increment(),如下面例15.2:例15.2給CCounter類添加自增函數(shù)Increment()。程序清單C15_02.cpp#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} voidIncrement(){++m_val;}//自增函數(shù),將變量的值加1private: unsignedm_val;};main(){ CCountercounter; cout<<"counter的值是:"<<counter.GetVal()<<endl; counter.Increment(); cout<<"調(diào)用自增函數(shù)之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl;}輸出結果counter的值是:0調(diào)用自增函數(shù)之后,counter的值是:1程序分析:例15.2中,在類CCounter中添加了自增函數(shù)Increment(),雖然它可以正常工作,但是使用起來和運算符“++”比較起來可是差遠了,太麻煩了。似乎聽見哪有抗議聲,哦,原來是上面的程序在喊:“為什么不給我添加++運算符”。別急,當然可以滿足它的愿望了,通過重載運算符++就可以實現(xiàn)。(2)重載前綴運算符“++”1) 重載前綴運算符“++”,返回類型為void要重載前綴++運算符,可以通過運算符重載函數(shù)實現(xiàn),我們這里是把運算符重載函數(shù)作為類的成員函數(shù)實現(xiàn)的,它的語法形式如下:其中,returntype是返回值類型,operator是關鍵字,“op”是要重載的運算符符號,classname是重載該運算符的類的名稱。重載函數(shù)的函數(shù)名是由關鍵字operator后面加上要重載的運算符符號組成的,為operatorop。這里我們用“op”泛指可被重載的運算符。因此,自增運算符++可以按如下的形式進行重載:voidoperator++();returntypeclassname::operatorop(參數(shù)表){//相對于該類而定義的操作代碼}例15.3在CCounter類中通過運算符++的重載,實現(xiàn)CCounter類的自增運算++。程序清單C15_03.cpp//重載運算符++,運算符重載函數(shù)作為類CCounter的成員函數(shù)#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} voidIncrement(){++m_val;} voidoperator++(){++m_val;}//重載運算符++private: unsignedm_val;};main(){ CCountercounter; cout<<"counter的值是:"<<counter.GetVal()<<endl; counter.Increment(); cout<<"調(diào)用自增函數(shù)之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; ++counter; cout<<"使用自增運算符++之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl;}輸出結果counter的值是:0調(diào)用自增函數(shù)之后,counter的值是:1使用自增運算符++之后,counter的值是:2程序分析:在上面的程序中,函數(shù):voidoperator++(){++m_val;}實現(xiàn)了運算符++的重載。在main()函數(shù)中,語句:++counter;對象counter,使用了類CCounter重載的運算符++,這種語法形式與我們期望類CCounter對象所具有的形式非常接近。但是,或許你想讓類CCounter對象具有更多附加的功能,比如檢查CCounter類對象是否超出了最大值范圍等等。2) 重載運算符“++”,返回一個CCounter類對象,通過創(chuàng)建臨時變量實現(xiàn)不知道你是否注意到,我們上面重載的自增運算符有一個很大的缺陷,那就是如果你想把CCounter類對象放在賦值運算符的右側,那么對不起,編譯器會不客氣地報錯,你不妨試試。把CCounter類對象放在賦值運算符的右側,例如:CCountercounter2=++counter;這條語句試圖建立一個新的CCounter類對象counter2,然后把對象counter自增后的值賦給這個對象。內(nèi)置的拷貝構造函數(shù)將處理賦值運算,但是現(xiàn)在的自增運算符并沒有返回一個CCounter類對象,而是返回空類型void,我們不能把一個空類型void對象賦給一個CCounter類對象。那怎么辦?下面我們就來解決這個棘手的問題。顯然,我們只需要讓重載運算符函數(shù)返回一個CCounter類對象問題就解決了,這樣返回的對象值就可以賦給另一個CCounter類對象了。那么返回哪個對象呢?一種方法是創(chuàng)建一個臨時對象,然后返回。例15.4在CCounter類中重載運算符++,并返回一個CCounter類型的臨時對象。程序清單C15_04.cpp//重載運算符++,并返回一個臨時對象#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} voidIncrement(){++m_val;} CCounteroperator++();//重載++運算符,返回值為CCounter類型private: unsignedm_val;};CCounterCCounter::operator++(){ ++m_val; CCountertemp; temp.SetVal(m_val); returntemp;}main(){ CCountercounter; cout<<"counter的值是:"<<counter.GetVal()<<endl; counter.Increment(); cout<<"調(diào)用自增函數(shù)之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; ++counter; cout<<"使用自增運算符++之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; CCountercounter2=++counter; cout<<"counter2的值是:"<<counter2.GetVal()<<endl; cout<<"counter的值是:"<<counter.GetVal()<<endl;}輸出結果counter的值是:0調(diào)用自增函數(shù)之后,counter的值是:1使用自增運算符++之后,counter的值是:2counter2的值是:3counter的值是:3程序分析:在上面這個版本的程序中,運算符重載函數(shù)operator++的返回值為一個CCounter類對象。在函數(shù)operator++中,我們創(chuàng)建了一個臨時對象temp:CCountertemp;而且通過語句temp.SetVal(m_val);將對象temp的值設置為當前對象的值。臨時對象被返回而且賦值給對象counter2。CCountercounter2=++counter;至此,上面的實現(xiàn)好像是很完美了,但是,有一個問題,就是:為什么我們要創(chuàng)建一個臨時對象?”,記?。好總€臨時對象被創(chuàng)建而且使用完之后必須要被銷毀——這是一個潛在的“奢侈”的操作,要耗費資源,況且對象counter已經(jīng)存在,并且已經(jīng)有正確的值,那么為什么不直接返回它呢?我們可以通過使用this指針來解決這個問題。3)重載運算符“++”,通過this指針返回一個CCounter類對象的引用正如我們在第13章中討論的那樣,this指針是類的所有成員函數(shù)的隱含參數(shù),因為運算符重載函數(shù)operator++是類的成員函數(shù),所以this指針是該函數(shù)的隱含參數(shù)。this指針指向對象counter,如果間接引用this指針(*this)則其返回對象counter,對象counter的成員變量m_val已經(jīng)有正確的值。例15.5在CCounter重載運算符函數(shù)中返回間接引用this指針的值,這樣就可以避免創(chuàng)建一個臨時對象,從而可以提高程序運行的效率。程序清單C15_05.CPP//返回this指針的間接引用#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} voidIncrement(){++m_val;} constCCounter&operator++();//重載++運算符,返回this指針的間接引用private: unsignedm_val;};constCCounter&CCounter::operator++(){ ++m_val; return*this;}main(){ CCountercounter; cout<<"counter的值是:"<<counter.GetVal()<<endl; counter.Increment(); cout<<"調(diào)用自增函數(shù)之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; ++counter; cout<<"使用自增運算符++之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; CCountercounter2=++counter; cout<<"counter2的值是:"<<counter2.GetVal()<<endl; cout<<"counter的值是:"<<counter.GetVal()<<endl;}輸出結果counter的值是:0調(diào)用自增函數(shù)之后,counter的值是:1使用自增運算符++之后,counter的值是:2counter2的值是:3counter的值是:3程序分析:在上面的程序中,通過間接引用this指針,返回當前對象的引用,并將當前對象的值賦給對象counter2。注意:上面程序中返回的是當前對象的引用,因此就避免了創(chuàng)建多余的臨時對象。因為要防止當前的對象值被別的函數(shù)改變,所以返回值是const限定的常量引用(回憶一下const限定的使用)。好了,現(xiàn)在類CCounter已經(jīng)有了一個使用方便,性能卓越的++運算符了。大家自然的會想到前面的++運算符是前綴自增,那后綴自增運算符又該如何重載呢?想的周到,值得表揚。我們下面就看看重載后綴自增運算符如何實現(xiàn)。(3)重載后綴運算符“++”首先來看看編譯器是如何區(qū)別前綴自增和后綴自增運算的?按照約定,在運算符重載函數(shù)的聲明中,提供一個整型數(shù)作為函數(shù)的參數(shù),就表示重載的是后綴運算符。參數(shù)值并沒有什么用,它只是用來表明重載的是后綴運算符。在我們重載后綴自增運算符之前,先要徹底搞清楚到底它與前綴自增運算符有什么不同。如果忘記了,請回頭去看看前面的C語言部分。前面我們講到,前綴自增是“先自增,然后再拿來參與運算”,而后綴自增相反,它是“先拿來用,然后變量再自增”。因此,重載前綴運算符只要簡單地遞增變量的值,然后返回對象本身即可,而后綴運算符必須返回進行自增運算之前的對象的值。為了做到這一點,我們必須創(chuàng)建一個臨時對象,用它來存儲對象的初始值,然后增加對象的初始值,最后返回臨時對象。讓我們通過下面的小例子再簡單地回顧一下后綴運算符的使用。m=n++;如果n的值是6,執(zhí)行完上述語句之后,m的值是6,但是n的值是7。這樣,我們返回了變量n原來的值,并且賦給變量m,然后將n的值加1。如果n是一個對象,它的后綴自增運算必須先將對象原來的值(如6)存儲到一個臨時對象中,然后增加n的值(n的值變?yōu)?),最后返回臨時對象的值并賦給變量m。注意:因為我們返回的是臨時對象,所以必須返回對象的值,而不能是返回引用,這是因為臨時對象在函數(shù)返回時就超出了它的作用范圍,將被銷毀,如果返回它的引用,那么這個引用將是一個已經(jīng)不存在的對象的引用,其后果是不可預料的。例15.6在CCounter中實現(xiàn)后綴和前綴自增運算符的重載。程序清單C15_06.cpp//重載后綴和前綴自增運算符#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} voidIncrement(){++m_val;} constCCounter&operator++(); //重載前綴++運算符 constCCounteroperator++(int); /*重載后綴++運算符,帶一個整型參數(shù), 表示是后綴運算符*/private: unsignedm_val;};constCCounter&CCounter::operator++(){ ++m_val; return*this;}constCCounterCCounter::operator++(int){ CCountertemp; temp.m_val=m_val; //將對象原有的值保存到臨時對象中 ++m_val; //自增對象的值 returntemp; //返回臨時對象}main(){ CCountercounter; cout<<"counter的值是:"<<counter.GetVal()<<endl; counter.Increment(); cout<<"調(diào)用自增函數(shù)之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; ++counter; cout<<"使用自增運算符++之后,"; cout<<"counter的值是:"<<counter.GetVal()<<endl; CCountercounter2=++counter; cout<<"counter2的值是:"<<counter2.GetVal()<<endl; cout<<"counter的值是:"<<counter.GetVal()<<endl; counter2=counter++; cout<<"counter2的值是:"<<counter2.GetVal()<<endl; cout<<"counter的值是:"<<counter.GetVal()<<endl;}輸出結果counter的值是:0調(diào)用自增函數(shù)之后,counter的值是:1使用自增運算符++之后,counter的值是:2counter2的值是:3counter的值是:3counter2的值是:3counter的值是:4在上面的程序中,分別實現(xiàn)了前綴和后綴運算符的重載。注意,在main()函數(shù)中使用后綴++運算符時,并沒有在它的后面加一個整型的標志,而是像其它內(nèi)置數(shù)據(jù)類型一樣的來使用后綴++運算符。前面我們已經(jīng)強調(diào),在后綴運算符重載函數(shù)中帶一個整型參數(shù),只是為了表明是后綴運算符,其所帶的參數(shù)值從不被使用。至此,我們實現(xiàn)了前綴和后綴的++運算符的重載。前綴和后綴的自減運算符--的重載完全類似。你不妨自己在類CCounter中添加進去(不要偷懶呦?。?。重載運算符事實上就是在實現(xiàn)一個函數(shù),函數(shù)的函數(shù)名由關鍵字operator和要重載的運算符組成。如果運算符重載函數(shù)是類的成員函數(shù)(如上面的示例程序),那么重載一元運算符的函數(shù)不帶參數(shù)。注意,如果重載的是后綴運算符,如++或--,那么不要忘記帶一個整型參數(shù)作為標志。例如:constCCounter&CCounter::operator++(); //前綴++CCounterCCounter::operator--(int); //后綴--2. 二元運算符重載:重載加運算符“+”前面我們講述了一元運算符前綴自增和后綴自增的重載,它們只是在一個對象上操作。加法運算符“+”是二元運算符,是對兩個對象進行操作。如何對類CCounter實現(xiàn)加運算(+)的重載呢?我們的目標是聲明兩個CCounter類對象,然后對它們進行加運算,例如:CCountercounter1,counter2,counter3;counter3=counter1+counter2;(1)添加成員函數(shù)Add(),實現(xiàn)對象相加的功能實現(xiàn)加法功能的第一種方式是:通過在類CCounter中添加一個成員函數(shù)Add(),該函數(shù)以一個CCounter類對象作為參數(shù),將對象參數(shù)的值和當前對象的值相加,然后返回一個CCounter類對象。例15.7在CCounter類中添加Add()函數(shù),實現(xiàn)兩個CCounter對象相加。程序清單C15_07.cpp#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} CCounter(unsignedinitVal):m_val(initVal){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} CCounterAdd(constCCounter&cnt);private: unsignedm_val;};CCounterCCounter::Add(constCCounter&cnt){ CCountertemp; temp.SetVal(m_val+cnt.GetVal()); returntemp;}main(){ CCountercounter1(1),counter2(3),counter3; //創(chuàng)建三個CCounter類對象 counter3=counter1.Add(counter2); //調(diào)用成員函數(shù)Add() cout<<"counter1的值是:"<<counter1.GetVal()<<endl; cout<<"counter2的值是:"<<counter2.GetVal()<<endl; cout<<"counter3=counter1+ounter2的值是:"<<counter3.GetVal()<<endl;}輸出結果counter1的值是:1counter2的值是:3counter3=counter1+counter2的值是:4(2)重載加運算符“+”我們實現(xiàn)的Add()函數(shù)雖然可以正常工作了,但是使用起來不夠簡單自然。不說大家可能也會想到,我們實現(xiàn)兩個CCounter類對象進行加運算的另外一種方法是重載運算符“+”,使加運算可以寫為如下形式:CCountercounter1,counter2,counter3;counter3=counter1+counter2;例15.8在CCounter類中實現(xiàn)重載運算符“+”。程序清單C15_08.cpp//重載運算符"+"#include<iostream>usingnamespacestd;classCCounter{public: CCounter():m_val(0){} CCounter(unsignedinitVal):m_val(initVal){} ~CCounter(){} unsignedGetVal()const{returnm_val;} voidSetVal(unsignedx){m_val=x;} CCounteroperator+(constCCounter&cnt); //重載加(+)運算符private: unsignedm_val;};CCounterCCounter::operator+(constCCounter&cnt){ CCountertemp; temp.SetVal(m_val+cnt.GetVal()); returntemp;}main(){ CCountercounter1(2),counter2(4),counter3; //創(chuàng)建三個CCounter類對象 counter3=counter1+counter2; cout<<"counter1的值是:"<<counter1.GetVal()<<endl; cout<<"counter2的值是:"<<counter2.GetVal()<<endl; cout<<"counter3=counter1+counter2的值是:"<<counter3.GetVal()<<endl;}輸出結果counter1的值是:2counter2的值是:4counter3=counter1+counter2的值是:6程序分析:在上面的程序中,重載了加(+)運算符。將加運算符重載函數(shù)的聲明和實現(xiàn)分別與Add()函數(shù)的聲明和實現(xiàn),做個比較,它們幾乎是一模一樣,只是它們的調(diào)用語法不同,使用下面的調(diào)用方式(2)顯然要比方式(1)簡單自然:(1)counter3=counter1.Add(counter2);(2)counter3=counter1+counter2;雖然沒有多大的變化,但已經(jīng)足以使程序易于理解,代碼書寫也更方便了。注意:重載加運算符的方法同樣適用于其它的二元運算符,比如,減(-)操作等等。除了重載二元運算符的函數(shù)帶有一個參數(shù)之外,其它的都和重載一元運算符類似。重載運算符函數(shù)的參數(shù)是對象的常值引用類型。例如:CounterCounter::operator+(constCounter&cnt);//重載+運算符CounterCounter::operator-(constCounter&cnt);//重載-運算符在上面的程序中,我們實現(xiàn)了二元運算符“+”的重載,其它二元運算符的重載類似,大家可以嘗試對類CCounter重載“-”運算符。3. 重載賦值運算符“=”賦值運算符“=”可以被重載,而且必須被重載為成員函數(shù),其重載格式為:其中A表示類名,source是一個A類型的對象。當用戶在一個類中顯式地重載了賦值運算符“=”時,稱用戶定義了類賦值運算。它將一個A類的對象source逐域拷貝(拷貝所有的成員)到賦值號左端的類對象中。如果用戶沒有為一個類重載賦值運算符,編譯程序將生成一個缺省的賦值運算符。賦值運算把源對象的值逐域地拷貝到目標對象中。對許多簡單的類,如前面的CCounter類,缺省的賦值函數(shù)工作的很好。AA::operator=(constAsource){//復制對象source的所有成員}但是,如果用戶定義的類中有指針成員,牽扯到內(nèi)存的分配問題,那么系統(tǒng)提供的缺省的賦值運算就不能正確工作,用戶必須顯式地為類定義賦值運算。此問題類似于我們在第13章講到的拷貝構造函數(shù)??截悩嬙旌瘮?shù)和賦值運算符都是把一個對象的數(shù)據(jù)成員拷貝到另一個對象,它們的函數(shù)體實現(xiàn)非常類似,但是它們是有區(qū)別的。拷貝構造函數(shù)是要以一個已存在的對象來創(chuàng)建一個新對象,而賦值運算符則是改變一個已存在的對象的值。我們在前面的部分提到過,如果我們自己不定義拷貝構造函數(shù),那么系統(tǒng)會自動提供一個。同樣,如果我們對自己定義的類,不定義賦值運算,那么系統(tǒng)也會提供默認的賦值運算操作(operator=)。無論什么時候你使用對象進行賦值時,這個操作都會被調(diào)用。例如:CCatcat1(2,4),cat2(5,7);//其它程序代碼cat2=cat1;注:關于CCat類的定義,請看第13章的示例13.2。在上面這段代碼中,創(chuàng)建了兩個CCat類對象:cat1和cat2,并且把cat1的成員變量m_age和m_weight分別初始化為2和4,把cat2的成員變量m_age和m_weight分別初始化為5和7。在執(zhí)行過一些程序代碼之后,將對象cat1的值賦給cat2,別看這小小的一個舉動,卻會引發(fā)兩個潛在的問題:首先,如果成員m_age是一個指針而不是整型變量,會導致什么情況發(fā)生?其次,對象cat2的原有的值會發(fā)生什么變化?在第13章我們討論拷貝構造函數(shù)的時候,就提出了關于成員變量如果是指針類型的情況,對于賦值運算有同樣的問題。在C++的程序中,淺拷貝(逐域拷貝)不同于深拷貝,前者只是簡單地拷貝對象的成員。如果類中有指針成員,那么如果使用淺拷貝將導致兩個對象將指向同一塊存儲空間;而深拷貝不同,它會另外分配一塊空間給目標對象的指針變量。例15.9在CCat類中定義賦值運算,即重載賦值運算符。程序清單C15_09.cpp//重載賦值運算符=,重載函數(shù)中包括預防自復制的代碼#include<iostream>#include<string>usingnamespacestd;classCCat{public: CCat(); //缺省構造函數(shù) intGetAge()const{return*m_age;} char*GetName()const{returnm_name;} voidSetAge(intage){*m_age=age;} voidSetName(char*name); CCatoperator=(constCCat&); //重載賦值運算符private: int*m_age; char*m_name;};CCat::CCat(){ m_age=newint; //給指針分配空間 *m_age=5; m_name=newchar[20]; //給Name指針分配空間 strcpy(m_name,"Mypet");}voidCCat::SetName(char*name){ delete[]m_name; m_name=newchar[strlen(name)+1]; strcpy(m_name,name);}CCatCCat::operator=(constCCat&sCat){ //如果源對象的地址和this指針相等,說明它們是同一個對象,是自賦值 if(this==&sCat) return*this; delete[]m_name; //刪除原有的空間 m_name=newchar[strlen(sCat.m_name)+1]; //分配新的存儲空間 *m_age=sCat.GetAge(); strcpy(m_name,sCat.m_name); //將源對象的值賦給當前對象 return*this; //返回當前對象}main(){ CCatcat1; cat1.SetAge(2); cout<<"cat1的年齡是:"<<cat1.GetAge()<<endl <<"昵稱:"<<cat1.GetName()<<endl; CCatcat2; cat2.SetName("SunFlower"); cout<<"cat2的年齡是:"<<cat2.GetAge()<<endl <<"昵稱:"<<cat2.GetName()<<endl; cout<<"\n把cat1的值賦給cat2...\n\n"; cat2=cat1; cout<<"cat2的年齡是:"<<cat2.GetAge()<<endl <<"昵稱:"<<cat1.GetName()<<endl;}輸出結果cat1的年齡是:2昵稱:Mypetcat2的年齡是:5昵稱:SunFlower把cat1的值賦給cat2...cat2的年齡是:2昵稱:Mypet上面CCat類的定義可能把你都搞糊涂了,我想你肯定不理解為什么要把成員變量聲明為如下的形式:int*m_age;char*m_name;沒錯,這樣做是有點讓人費解。之所以這樣做,目的只是為了說明:當成員變量中有指針時,應該如何重載賦值運算符。我們在賦值運算符的重載函數(shù)中,通過語句“if(this==&sCat)return*this;”檢查了自賦值的情況。這樣做的目的是處理由于當對象cat2作為賦值運算符的右操作數(shù)將它的值都拷貝到內(nèi)存中后(cat2=cat2;),接著它會釋放它占用的存儲空間(delete[]m_name;),所導致的一個非常大的問題:存儲空間沒了。更重要的是,當我們使用引用或者間接的引用指針時這種自賦值的情況很可能發(fā)生。比如:CCatcat2;//其它的程序代碼CCat&cat3=cat1;//其它的程序代碼cat2=cat3;//自賦值這樣通過重載賦值運算符,很好地解決了我們上面提到的兩個問題,一個是當成員變量是指針的問題,另一個是自賦值的問題。1.用友元函數(shù)重載加法運算符“+”有些時候,用成員函數(shù)重載運算符會碰到一些麻煩。例如:例15.10在復數(shù)類CComplex中用成員函數(shù)重載運算符“+”。程序清單C15.10.cpp//用成員函數(shù)重載運算符"+"classCComplex{public: CComplex(){real=0.0;image=0.0;} CComplex(doublerv){real=rv;image=0.0;} CComplex(doublerv,doubleiv){real=rv;image=iv;} CComplexoperator+(constCComplex&); //重載的加運算符 ~CComplex(){};private: double real; //復數(shù)的實部 double image; //復數(shù)的虛部};三、 用友元函數(shù)重載運算符CComplexCComplex::operator+(constCComplex&c){ CComplex temp; temp.real=real+c.real; temp.image=image+c.image; returntemp;}main(){ CComplexc1(1,5),c2(3); c1=c2+16; //正確:被編譯器解釋為c1=c2.operator+(CComplex(16)) c1=16+c2; //錯誤:被解釋為:c1=16.operator+(c2);}在上面的程序段中,語句“c1=c2+16;”可被解釋為“c1=c2.operator+(16)”。c2是一個復數(shù)類CComplex對象,系統(tǒng)用的是復數(shù)類中重載的運算符“+”。所需要的參數(shù)應該是CComplex類對象,雖然上述語句中給出的參數(shù)是整數(shù)16,但是系統(tǒng)可以自動的將16進行類型轉換,它通過構造函數(shù)“CComplex(doublerv){real=rv;image=0.0;}”,將整數(shù)16變?yōu)镃Complex類類型常量CComplex(16)。因此上述語句可以正常工作。但是,語句“c1=16+c2;”將被解釋為“c1=16.operator+(c2);”。這句不會逃過編譯器的考核,因為16不是用戶定義的CComplex類對象,在程序的上下文中,編譯器并不知道用戶重載的運算符“+”的含義,所以只好用系統(tǒng)預定義的“+”運算符的含義進行解釋,顯然,整數(shù)常量16不能與復數(shù)對象c2進行加法,所以這個語句不能正常工作。在上述代碼中,我們把“+”運算符作為CComplex類的成員函數(shù)進行重載,不具有交換性。這是因為作為成員函數(shù)重載的運算符“+”僅能通過實際的對象所調(diào)用。如果引起調(diào)用的是一個非CComplex類對象的值,比如上面的整型數(shù)16,則該成員函數(shù)就不知何去何從了。那我們該如何做才能讓“+”運算符恢復它的靈活性,具有可交換性呢?C++語言的開發(fā)者,當然替我們想得很周到了,提供了“靈丹妙藥”來解決此問題了。這就是將運算符重載函數(shù)作為友元函數(shù)來實現(xiàn)。因為友元函數(shù)沒有隱含的this指針,所以用友元函數(shù)實現(xiàn)運算符重載時,該運算符的操作數(shù)都必須在友元函數(shù)的參數(shù)表中明確聲明。修改例15.10的程序,將“+”運算符的重載函數(shù)作為類CComplex的友元函數(shù)來實現(xiàn)。具體實現(xiàn)代碼如下:例15.11在CComplex類中使用友元函數(shù)重載運算符。程序清單C15.11.cpp//鏈表類CLinkList的使用//用友元函數(shù)重載運算符"+"classCComplex{public: CComplex(){real=0.0;image=0.0;} CComplex(doublerv){real=rv;image=0.0;} CComplex(doublerv,doubleiv){real=rv;image=iv;} friendCComplexoperator+(CComplexc1,CComplexc2); //作為類的友元函數(shù),重載加運算符。 ~CComplex(){}private: double real; //復數(shù)的實部 double image; //復數(shù)的虛部};CComplexoperator+(CComplexc1,CComplexc2){ CComplex temp; temp.real=c1.real+c2.real; temp.image=c1.image+c2.image; returntemp;}main(){ CComplexc1(1,5),c2(3); c1=c2+16; //正確:被編譯器解釋為c1=operator+(c2,CComplex(16)) c1=16+c2; //正確:被編譯器解釋為c1=operator+(CComplex(16),c2)}這次就不會受編譯器的“責難”了,可以順利地通過編譯。那么什么時候使用成員函數(shù)實現(xiàn)重載,什么時候又用友元函數(shù)實現(xiàn)呢?有些運算符必須用成員函數(shù)重載,比如賦值運算符“=”、取下標運算符“[]”、函數(shù)調(diào)用運算符“()”以及間接指針運算符“->”。但是有些時候,比如例15.10中的情況,我們只能通過友元函數(shù)重載來實現(xiàn),還有輸出運算符“<<”也只能通過友元函數(shù)來重載(原因在講解例15.12時會說明)??傊?,二者選擇其一,要視具體情況而定。下面給出作為成員函數(shù)或是友元函數(shù)來實現(xiàn)運算符重載的不同之處:1)因為成員函數(shù)有隱含的this指針,所以在用成員函數(shù)重載運算符時,this指針可以作為一個參數(shù)使用。這樣,如果重載的運算符是一元運算符,則運算符重載函數(shù)不需要參數(shù),因為this指針所指的對象就是運算符的操作數(shù);如果重載的是二元運算符,那么運算符重載函數(shù)只需要帶一個參數(shù),其中參數(shù)表示運算符的右操作數(shù),this指針所指的對象則是運算符的左操作數(shù)。形式如下://成員函數(shù)形式,重載一元運算符返回類型C::operatorop(){ //類C對op的解釋}//成員函數(shù)形式,重載二元運算符返回類型C::operatorop(C&b) { //類C對op的解釋}其中,C是類名,op是要重載的運算符,b是參數(shù)。2)因為友元函數(shù)沒有隱含的this指針,所以用友元函數(shù)實現(xiàn)運算符重載時,該運算符的操作數(shù)都必須在友元函數(shù)的參數(shù)表中明確聲明。如果重載的運算符是一元運算符,則運算符重載函數(shù)就要帶一個參數(shù),表示操作數(shù);如果重載的是二元運算符,那么運算符重載函數(shù)就要帶兩個參數(shù),分別表示兩個操作數(shù)。形式如下://作為類C的友員函數(shù),重載一元運算符返回類型operatorop(C&a){ //對op的解釋}//作為類C的友員函數(shù),重載二元運算符返回類型operatorop(C&a,C&b){ //對op的解釋}其中,C是類名,op是要重載的運算符,a、b是參數(shù)。2. 重載輸出運算符“<<”下面我們再來看一個使用友元函數(shù)實現(xiàn)重載輸出運算符“<<”的例子。例15.12在復數(shù)類CComplex中重載輸出運算符“<<”。程序清單C15_12.cpp//重載輸出運算符"<<"#include<iostream.h> //有些編譯系統(tǒng)可能是包含iostream,并指明名字空間std;classCComplex{public: CComplex(){real=0.0;image=0.0;} CComplex(doublerv){real=rv;image=0.0;} CComplex(doublerv,doubleiv){real=rv;image=iv;} friendCComplexoperator+(CComplexc1,CComplexc2); //作為類的友元函數(shù),重載加運算符 friendostream&operator<<(ostream&stream,CComplexc); //重載輸出運算符"<<" ~CComplex(){};private: double real; //復數(shù)的實部 double image; //復數(shù)的虛部};CComplexoperator+(CComplexc1,CComplexc2){ CComplex temp; temp.real=c1.real+c2.real; temp.image=c1.image+c2.image; returntemp;}ostream&operator<<(ostream&stream,CComplexc){ stream<<“(”<<c.real<<“+”<<c.image<<“i)”<<endl; //以(a+bi)的格式輸出復數(shù) returnstream;}main(){ CComplex c1(1,5),c2(3); cout<<"c1="<<c1; //使用重載輸出運算符"<<",輸出復數(shù)c1 cout<<"c2="<<c2; //使用重載輸出運算符"<<",輸出復數(shù)c2 c1=c2+16; cout<<"執(zhí)行語句c1=c2+16;之后,"; cout<<"c1="<<c1;}輸出結果c1=(1+5i)c2=(3+0i)執(zhí)行語句c1=c2+16;之后,c1=(19+0i)運算符重載的限制:重載運算符時,不能改變它們的優(yōu)先級,也不能改變它們的結合性。當然如果運算符是一元的,我們不能把它重載為二元運算符。也就是說,我們不能改變運算符所需要的操作數(shù)的數(shù)目,更不能創(chuàng)造出新的運算符。比如,不能聲明運算符“**”來表示求操作數(shù)的平方。如果這樣,就違反了我們重載運算符的初衷。進行運算符重載要注意的問題:在C++的新手的編程工作中,運算符重載是最容易被濫用的C++特性之一。因為對一些含義模糊的運算符進行重載,使其具有有意義的功能,這是非常有誘惑力的。但是,盲目地重載運算符會導致代碼混亂,難于理解。毫無疑問,通過重載,可以使運算符“+”實現(xiàn)減法的功能,而使運算符“*”用來進行加法,這些也是非常有趣的事情,但是沒有專業(yè)的程序員會這么干。這樣做的最大的危險是在于企圖是好的,但是卻使運算符變了“質(zhì)”。一定要記住,重載運算符的目的是增強代碼的可理解性,可不要背道而馳,異想天開地盲目地重載運算符。注意:只有在重載運算符有益于代碼清晰的情況下,再實行重載。不要竄改運算符原有的意義。在前面學習類和對象的時候,我們明確了這樣一個概念:定義一個類就是定義一種新類型。因此,對象和變量一樣,可以作為函數(shù)的參數(shù)傳遞,可以作為函數(shù)的返回值的類型,也可以說明對象數(shù)組,甚至還可能有類類型的常量。在基本數(shù)據(jù)類型上,系統(tǒng)提供了許多預定義的運算符,它們以一種簡潔的方式工作。例如“+”運算符:int x,y;y=x+y;表示兩個整數(shù)相加,很簡潔。但是,兩個字符串合并:char x[20],y[10]; //定義兩個字符串類型//……strcat(x,y);表達起來就不如“y=x+y;”那樣直觀簡潔。因此,為了表達上的方便,希望已預定義的運算符,也可以在特定類的對象上以新的含義進行解釋。如在string類對象x、y的環(huán)境下,運算符“+”能被解釋為字符串x和y的合并。換言之,希望預定義運算符能被重載,讓用戶自定義的類也可以使用。四、 應用舉例例15.13修改類CPeople的實現(xiàn),重載類的輸出運算符和賦值運算符。程序清單People.h//類CPeople的定義#include<iostream>usingnamespacestd;classCPeople{ intm_nAge; floatm_fSalary;public: char*m_pstrName; CPeople(); ~CPeople(); CPeople(intage,floatsalary,char*name); floatGetSalary()const; voidSetSalary(float); intGetAge()const; voidSetAge(intage); CPeople&operator=(constCPeople&AnotherPeople); friendostream&operator<<(ostream&stream,constCPeople&p);};程序清單People.cpp//類CPeople的實現(xiàn)#include"People.h"#include<string>usingnamespacestd;CPeople::CPeople(){ m_nAge=20; m_fSalary=3000.00f; m_pstrName=newchar[20]; strcpy(m_pstrName,"無名氏");}CPeople::CPeople(intage,floatsalary,char*name){ m_nAge=age; m_fSalary=salary; m_pstrName=newchar[20]; strcpy(m_pstrName,name);}//類CPeople的實現(xiàn)#include"People.h"#include<string>usingnamespacestd;CPeople::CPeople(){ m_nAge=20; m_fSalary=3000.00f; m_pstrName=newchar[20]; strcpy(m_pstrName,"無名氏");}CPeople::CPeople(intage,floatsalary,char*name){ m_nAge=age; m_fSalary=salary; m_pstrName=newchar[20]; strcpy(m_pstrName,name);}CPeople::~CPeople(){ if(m_pstrName!=NULL) delete[]m_pstrName;}intCPeople::GetAge()const{ returnm_nAge;}voidCPeople::SetAge(intage){ m_nAge=age;}floatCPeople::GetSalary()const{ if(m_nAge<20) return0; elseif(GetAge()>60) returnm_fSalary/2; else returnm_fSalary;}voidCPeople::SetSalary(floatnum){ m_fSalary=num;}//賦值運算符重載的實現(xiàn)CPeople&CPeople::operator=(constCPeople&AnotherPeople){ if(this==&AnotherPeople) //檢查自賦值 return*this; if(m_pstrName) delete[]m_pstrName; //釋放原有的內(nèi)存資源 m_nAge=AnotherPeople.m_nAge; m_fSalary=AnotherPeople.m_fSalary; //分配新的內(nèi)存資源,并復制內(nèi)容 m_pstrName=newchar[strlen(AnotherPeople.m_pstrName)+1]; strcpy(m_pstrName,AnotherPeople.m_pstrName); return*this;}//輸出運算符重載函數(shù)的實現(xiàn)ostream&operator<<(ostream&stream,constCPeople&p){ stream<<"姓名:"<<p.m_pstrName<<"," <<"年齡:"<<p.GetAge()<<"," <<"薪水:"<<p.GetSalary(); returnstream;}程序清單C15_13.cpp//測試CPeople類#include<iostream>#include"people.h"usingnamespacestd;main(){ CPeopleZhang(65,2000.00f,"張飛"); cout<<Zhang<<endl; //輸出對象的值 CPeopleA,B; A.SetAge(34); A.SetSalary(4000.00f); cout<<A<<endl; B=A; //將對象A的值賦給對象B cout<<B<<endl;}輸出結果姓名:張飛,年齡:65,薪水:1000姓名:無名氏,年齡:34,薪水:4000姓名:無名氏,年齡:34,薪水:4000程序分析在上面的程序中,我們重新定義了類CPeople,重載了賦值運算符“=”和輸出運算符“<<”。重載賦值運算符的函數(shù)體實現(xiàn)與拷貝構造函數(shù)的實現(xiàn)非常相似(參照例13.12)。在賦值運算符重載函數(shù)中,我們進行了自賦值檢查,從而確保類對象的賦值運算可以正常工作。重載了CPeople類的輸出運算符,使對象的輸出變得簡單好用。例15.14在復數(shù)類CComplex中實現(xiàn)=、+、-、++、<<運算符的。程序清單complex.h#include<iostream.h>classCComplex{public: CComplex(){real=0.0;image=0.0;} CComplex(doublerv){real=rv;image=0.0;} CComplex(doublerv,doubleiv){real=rv;image=iv;} ~CComplex(){} CComplex&operator=(constCComplex&); CComplexoperator+(constCComplex&); CComplexoperator-(constCComplex&); CComplex&operator++(); //前綴 CComplexoperator++(int); //后綴 friendostream&operator<<(ostream&stream,CComplexc);private: double real; double image;};程序清單complex.cpp#include<iostream.h>#include"complex.h"CComplex&CComplex::operator=(constCComplex&c){ //判斷是否自復制 if(this==&c) return*this; real=c.real; image=c.image; return*this;}CComplexCComplex::operator+(constCComplex&c){ CComplex temp; temp.real=real+c.real; temp.image=image+c.image; returntemp;}CComplexCComplex::operator-(constCComplex&c){ CComplex temp; temp.real=this->real-c.real; temp.image=this->image-c.image; returntemp;}CComplex&CComplex::operator++() //前綴{ real++; image++; return*this;}CComplexCComplex::operator++(inta) //后綴{ CComplextemp; temp.real=real; temp.image=image; real++; image++; returntemp;}ostream&operator<<(ostream&stream,CComplexc){ cout<<c.image; stream<<"("<<c.real<<"+"<<c.image<<"i)"; //以格式(a+bi)顯示 returnstream;}程序清單C15_14.cpp#include<iostream.h>#include"complex.h"main(){ CComplexz(2.0,3.0),k(3.0,4.0),x(1.0),y;

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
  • 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論