版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
第4章函數(shù)重載4.1函數(shù)重載概述4.2操作符重載4.3函數(shù)重載與默認參數(shù)本章小結習題
能讓一個名字方便地使用,是任何一門編程語言的特征。C++也不例外。當我們創(chuàng)建一個變量時,就要為存儲區(qū)取一個名字。函數(shù)也一樣,不同的是它是一個操作的名稱。我們創(chuàng)建了各種各樣易用的名字用來描述程序,就是為了方便別人閱讀和修改。寫程序就像寫文章一樣,目的是為了與人交流。當我們用自然語言進行交流時,一個詞可以表示多種意義,具體含義可以依賴語境確定。而當我們用代碼與機器交流時就必須想辦法去解決這個問題,那就是重載。這是C++中一種成熟的技術。本章還將介紹另外一種重載技術——運算符重載。它只是一種語法上的方便,也就是說它只是另一種形式的函數(shù)調用。不同之處是函數(shù)參數(shù)出現(xiàn)的位置不是在圓括號里,而是緊貼在運算符的旁邊。當然運算符重載和普通函數(shù)重載也是不同的,下面將詳細介紹。4.1函數(shù)重載概述4.1.1函數(shù)重載的概念通常,給每個不同的函數(shù)取一個不同的名字是個好的習慣,但是當許多函數(shù)對不同類型的對象執(zhí)行了相同的操作時,給它們取同一個名字更為方便。對不同對象上的操作使用同一名字來表示就叫做重載。C++支持此種技術。例如,表達式:
1+2
調用了針對整數(shù)操作數(shù)的加法操作,而表達式:
1.0+2.0
調用了另外一個專門處理浮點操作數(shù)的不同加法操作。此處的加法被重載了,以便處理不同的操作數(shù)類型。根據(jù)操作數(shù)的類型來區(qū)分不同的操作并應用適當?shù)牟僮鳎蔷幾g器的責任。當然此種技術對自定義的函數(shù)仍然適用。在第3章中介紹的構造函數(shù)就是一個很好的例子。自定義的String類中有好幾個構造函數(shù)有不帶參數(shù)的默認構造,有帶一個參數(shù)的構造函數(shù),有帶兩個的,還有拷貝構造函數(shù),它們都是以類名稱命名的構造函數(shù)。又如:
voidprint(int);voidprint(constchar*);
第一個函數(shù)是打印一個整數(shù),而第二個函數(shù)是打印一個字符串。給它們都取了同一個名字print,這樣給了程序員很大的方便。
如果多個函數(shù)名字相同,而參數(shù)列表不同,那么這些函數(shù)即被重載。這里的參數(shù)列表不同應怎樣理解呢?下面給出詳細的說明。
(1)如果兩個函數(shù)參數(shù)列表中參數(shù)個數(shù)、類型、順序不同,即參數(shù)列表不同,則認為是函數(shù)重載。例如:
//參數(shù)類型不同
voidprint(int);voidprint(constchar*);//參數(shù)個數(shù)不同
voidprint(constchar*);voidprint(constchar*,constchar*);//順序不同
voidprint(constchar*c,inti)voidprint(inti,constchar*c)
如果兩個函數(shù)的參數(shù)列表和返回值類型都相同,則認為第二個是第一個的重復聲明。例如:
//聲明同一個函數(shù)
voidprint(constchar*);voidprint(constchar*str);
這是因為參數(shù)表的比較與參數(shù)名無關。也就是說這樣的兩個函數(shù):
voidprint(constchar*str1);voidprint(constchar*str2);第二個也會被認為是第一個的重復聲明。(2)如果兩個函數(shù)參數(shù)列表相同但返回類型不同,則第二個會被認為是重復聲明而被標記為編譯錯誤。例如:
voidprint(constchar*);boolprint(constchar*); //編譯器會報錯函數(shù)的返回類型不足以區(qū)分兩個函數(shù)是否被重載,具體原因在后面講述。
(3)如果兩個函數(shù)的參數(shù)列表中只是默認參數(shù)不同,則第二個聲明被視為第一個的重復聲明。例如:
//聲明同一函數(shù)
voidprint(constchar*);voidprint(constchar*="Hello!");(4)?typedef為現(xiàn)有的數(shù)據(jù)類型提供了一個替換名,它并沒有創(chuàng)建一個新類型,因此,如果兩個函數(shù)參數(shù)表的區(qū)別只在于一個使用了typedef而另一個使用了與typedef相應的類型,則第二個仍是第一個的重復聲明。例如:
//typedef并不定義新的類型
typedefconstchar*STRING;//聲明同一函數(shù)
voidprint(constchar*);voidprint(STRING);
總之,當函數(shù)列表看起來不同時,并不代表就是兩個不同的函數(shù)列表。而返回類型不能作為判斷函數(shù)是否被重載的因素。
現(xiàn)有函數(shù)如下:
voidf();intf();
當寫表達式
intx=f();時,編譯器會根據(jù)上下文選擇調用哪個函數(shù),但是調用一個函數(shù)也可以不得到它的返回值。例如:
f();此時編譯器就沒法找哪一個去調用了。所以靠返回值來重載在C++中是不允許的。4.1.2全局函數(shù)與成員函數(shù)重載在C++中引入類的思想后,函數(shù)可以分為兩類:全局函數(shù)和成員函數(shù)。這樣函數(shù)重載就可分為全局函數(shù)重載和成員函數(shù)重載。顯然,全局函數(shù)就是指此函數(shù)不屬于任何一個類,大家都可以調用;成員函數(shù)就是指此函數(shù)屬于某個類。下面給出簡單的例子。
【程序4.1】#include<iostream>usingnamespacestd;classString{public: String(); String(char*src); intlength(); char*getString()const; voidsetString(char*str); voidprint(); //成員函數(shù)重載,打印字符串
voidprint(boolisLen);//成員函數(shù)重載,打印字符串和長度
private: intcurLen; //串的長度
intmaxLen; char*ch; //串存放數(shù)組
};
下面是部分函數(shù)的實現(xiàn):
//得到字符串長度
intString::length(){ returncurLen;}//得到字符串
char*String::getString()const{ returnch;}//打印字符串,成員函數(shù)重載
voidString::print(){ cout<<ch<<endl;}//打印字符串和長度,成員函數(shù)重載
voidString::print(boolisLen){ cout<<ch; if(isLen){ cout<<":"<<"符串長度:"<<curLen;}cout<<endl;}//打印字符串,全局函數(shù)重載
voidprint(Strings){ cout<<s.getString()<<endl;}//打印字符串和長度,全局函數(shù)重載
voidprint(Strings,boolisLen){ cout<<s.getString(); if(isLen){ cout<<":"<<"符串長度:"<<s.lengh();}cout<<endl;}
在上面的例子中有四個print函數(shù),其中voidString::print()和voidString::print(boolisLen)是成員函數(shù)重載,voidprint(Strings)和voidprint(Strings,boolisLen)是全局函數(shù)重載。也就是說,不管函數(shù)是屬于某個類的還是全局的都可以被重載。下面看看被重載后的函數(shù)的使用:
Strings("hello"); //構造String s.print(); s.print(ture); print(s); print(s,ture);
可以看出,“s.print();”和“print(s);”的打印結果是一樣的,“s.print(ture);”和“print(s,ture);”的打印結果是一樣的。但是它們的參數(shù)列表不一樣,全局函數(shù)重載比成員函數(shù)重載的函數(shù)多一個參數(shù)。這是因為成員函數(shù)有一個默認的參數(shù)即this指針,就是指向本對象的指針。成員函數(shù)中的語句“cout<<ch<<endl;”其實是語句“cout<<this->ch<<endl;”的簡寫。成員函數(shù)重載和全局函數(shù)重載各有其用處,在實際編碼中要根據(jù)實際情況來決定采取什么方式的重載。4.2操?作?符?重?載所謂重載,就是重新賦予新的含義。C++支持操作符重載。操作符重載的方法是定義一個重載操作符的函數(shù),在需要執(zhí)行被重載的操作符時,系統(tǒng)就自動調用該函數(shù),以實現(xiàn)相應的運算。運算符重載實質上是函數(shù)的重載。操作符重載能對自定義的數(shù)據(jù)類型進行加減乘除等操作,使自定義的數(shù)據(jù)類型能像內置的數(shù)據(jù)類型那樣很方便地使用操作符。例如自定義的Integer類:
#include<iostream>usingnamespacestd;classInteger{inti;public:Integer(intii):i(ii){}};
現(xiàn)在有兩個Integer類型的變量i1、i2,可以通過對操作符“+”進行重載,得到i1+i2;的和。例如:
classInteger{inti;public:Integer(intii):i(ii){}constIntegeroperator+(constInteger&rv)const{ i+=rv.i;return*this;}};
如此改動后可以寫這樣的表達式:
Integeri3=i1+i2;
黑體部分就是操作符重載的語法。關鍵字operator后面是要重載的操作符,+?后面是參數(shù)列表,常類型引用Integerrv;然后是函數(shù)體的實現(xiàn),把兩個類型的成員變量i相加,然后返回。其實它就和普通函數(shù)一樣,只是函數(shù)名變?yōu)榱瞬僮鞣?梢灾剌d的操作符如表4.1所示。不能自己創(chuàng)建操作符來重載,如**C++是不允許的。不能改變運算符的優(yōu)先級,也不能改變運算符的參數(shù)個數(shù)。下面分別介紹各操作符重載的用法。表4.1可重載操作符表4.2.1一元運算符表4.1中的一元操作符有:+(正號),-,~,&,!,++,--。同樣操作符和函數(shù)一樣也分成員操作符和全局操作符。一元操作符如果作為成員函數(shù),那么就不能有參數(shù);如果作為全局函數(shù)則只能帶一個參數(shù)。下面給出它們的簡單例子,擴展上面的Integer類:
#include"String.h"usingnamespacestd;#include<iostream.h>usingnamespacestd;classInteger{ inti; Integer*This(){returnthis;}public: Integer(longl=0):i(l){}
//***************全局函數(shù)**********// //參數(shù)為常引用,函數(shù)體不改變參數(shù)的值
friendconstInteger& operator+(constInteger&a); friendconstInteger operator-(constInteger&a); friendconstInteger operator~(constInteger&a); friendInteger* operator&(Integer&a); friendint operator!(constInteger&a);
//參數(shù)為非常量,可以改變參數(shù)的值
//前綴++ friendconstInteger& operator++(Integer&a); //后綴++ friendconstInteger operator++(Integer&a,int);//前綴-- friendconstInteger& operator--(Integer&a); //后綴-- friendconstInteger operator--(Integer&a,int);};
下面給出它們的部分實現(xiàn):
constIntegeroperator-(constInteger&a){ cout<<"-Integer\n"; returnInteger(-a.i);}Integer*operator&(Integer&a){ cout<<"&Integer\n"; returna.This(); //如果寫成?&a將形成遞歸
}intopeator!(constInteger&a){ cout<<"!Integer\n"; return!a.i;}+、~?和?-?類似,不再列出。可以看出全局操作符函數(shù)前面都加了一個friend。這是因為該函數(shù)需要訪問Integer的成員i,所以把它們聲明為該類的友元函數(shù)。++?和?--?各有兩種:前綴加后綴加,前綴減后綴減。為了區(qū)別它們,后綴添加一個int類型的參數(shù),此參數(shù)一般沒有真實的含義,只用來區(qū)別前綴和后綴。在調用后綴的加加和減減時,編譯器會自動傳一個0。下面將舉例說明它們的具體實現(xiàn)。
//前綴加,先加再返回
constInteger&operator++(Integer&a){ cout<<"++Integer\n"; a.i++; returna;}//后綴加,先返回再加
constIntegeroperator++(Integer&a,int){ cout<<"Integer++\n"; Integerbefore(a.i); a.i++; returnbefore;}//前綴減,先減再返回
constInteger&operator--(Integer&a){ cout<<"--Integer\n";a.i--; returna;}//后綴減,先返回再減
constIntegeroperator--(Integer&a,int){ cout<<"Integer--\n"; Integerbefore(a.i); a.i--; returnbefore;}
后綴加和減中都創(chuàng)建了臨時對象,先用臨時對象保存,然后做相應的操作,最后將臨時對象返回。
//全局操作符重載的使用
voidg(Integera){ +a; -a; ~a; Integer*ip=&a; !a; ++a; a++;--a; a--;}
為了給出成員操作符重載,新加一個類Byte:
//*******************成員函數(shù)*************//classByte{ unsignedcharb;public: Byte(unsignedcharbb=0):b(bb){} //參數(shù)為常引用,函數(shù)體不改變參數(shù)的值
//利用成員函數(shù)的默認參數(shù)this constByte&operator+()const{ cout<<"+Byte\n"; return*this; } constByteoperator-()const { cout<<"-Byte\n"; returnByte(-b); } constByteoperator~()const{ cout<<"~Byte\n"; returnByte(~b); } Byteoperator!()const { cout<<"!Byte\n"; returnByte(!b); } Byte*operator&(){ cout<<"&Byte\n"; returnthis; } //參數(shù)為非常量,可以改變參數(shù)的值
constByte&operator++(){//前綴
cout<<"++Byte\n"; b++; return*this; } constByteoperator++(int) {//后綴
cout<<"Byte++\n"; Bytebefore(b); b++; returnbefore; }constByte&operator--() {//前綴
cout<<"--Byte\n"; --b; return*this; } constByteoperator--(int) {//后綴
cout<<"Byte--\n"; Bytebefore(b); --b; returnbefore; }};//成員操作符重載的使用
voidm(Byteb){ +b; -b; ~b; Byte*bp=&b; !b; ++b; b++; --b; b--;}//main函數(shù)
intmain(){ Integera; g(a); Byteb; m(b); return0;}
可以看出成員函數(shù)比全局函數(shù)少一個參數(shù),成員函數(shù)使用了默認參數(shù)this。在函數(shù)g中,當編譯器遇到?+a時,它會調用函數(shù)operator+(a);在函數(shù)m中,當編譯器遇到?+b時,它會調用Byte的operator+()函數(shù)。也可以顯式調用:在g中寫為operator+(a),在m中寫為b.operator+(),其效果相同。這樣寫既簡潔又符合使用操作符的習慣。4.2.2二元運算符常見的二元運算符有:
(1)數(shù)學運算符,如?+、-、*、/、%、^、&、<<、>>?等;
(2)賦值運算符,如?+=、-=、*=、/=、%=、^=、&=、|=、>>=、<<=?等;
(3)條件運算符,如?==、!=、<、>、<=、>=、&&、||?等。
這些二元運算符如果重載為全局函數(shù)則可以有兩個參數(shù),如果重載為成員函數(shù)則只能有一個參數(shù)。下面給出簡單的例子,仍以Integer為例:
classInteger{ longi;public: Integer(longll=0):i(ll){} //數(shù)學運算符
friendconstInteger operator+(constInteger&left,constInteger&right);friendconstInteger operator-(constInteger&left,constInteger&right); friendconstInteger operator*(constInteger&left,constInteger&right); friendconstInteger operator/(constInteger&left,constInteger&right); //…其他數(shù)學運算符//賦值運算符
friendInteger& operator+=(Integer&left,constInteger&right); friendInteger& operator-=(Integer&left,constInteger&right); friendInteger& operator*=(Integer&left,constInteger&right); friendInteger& operator/=(Integer&left,constInteger&right); //…其他賦值運算符//條件運算符
friendint operator==(constInteger&left,constInteger&right); friendint operator!=(constInteger&left,constInteger&right); friendint operator<(constInteger&left,constInteger&right); friendint operator>(constInteger&left,constInteger&right); //…其他條件運算符
};
每個函數(shù)前都有friend修飾,說明該函數(shù)對該類是友好的,可以訪問該類的成員變量??梢钥闯鲞@些函數(shù)都被定義為全局函數(shù),下面看它們的實現(xiàn):
//數(shù)學運算符的實現(xiàn)
constIntegeroperator+(constInteger&left,constInteger&right){ returnInteger(left.i+right.i);}constIntegeroperator-(constInteger&left,constInteger&right){ returnInteger(left.i-right.i);}constIntegeroperator*(constInteger&left,constInteger&right){ returnInteger(left.i*right.i);}constIntegeroperator/(constInteger&left,constInteger&right){ if(right.i==0) { cout<<"除以零錯誤"<<endl; returnInteger(-1); } returnInteger(left.i/right.i);}//…其他數(shù)學運算符的實現(xiàn)//賦值運算符的實現(xiàn)
Integer&operator+=(Integer&left,constInteger&right){ //自賦值檢查
if(&left==&right) { return2*left; } left.i+=right.i; returnleft;}//…其他賦值運算符的實現(xiàn)//條件運算符的實現(xiàn)
intoperator==(constInteger&left,constInteger&right){ returnleft.i==right.i;}intoperator!=(constInteger&left,constInteger&right){ returnleft.i!=right.i;}//…其他條件運算符的實現(xiàn)
上面都是操作符的基本用法。數(shù)學運算符的實現(xiàn)就是把傳進來的兩個參數(shù)進行相應的操作,最后將結果以臨時對象的形式返回。還應注意有些操作符的特殊之處,如除法的除數(shù)不能為零,賦值操作符都得進行自賦值檢查等。在實際應用中根據(jù)實際情況,可以寫得更加復雜一些。上面的例子是操作符作為全局的實現(xiàn)。下面仍以Byte為例給出成員函數(shù)的簡單用法。
classByte{ unsignedcharb;public: Byte(unsignedcharbb=0):b(bb){} //常量參數(shù),不改變參數(shù)的值//數(shù)學運算符
constByte operator+(constByte&right)const { returnByte(b+right.b); } constByte operator-(constByte&right)const { returnByte(b-right.b); }constByte operator*(constByte&right)const { returnByte(b*right.b); } constByte operator/(constByte&right)const { if(right.b==0) { cout<<"除以零錯誤"<<endl; returnByte(-1); }returnByte(b/right.b); } //… //賦值運算符
Byte&operator+=(constByte&right) { if(this==&right) { b*=2; return*this; }b+=right.b; return*this; } //… //條件運算符
intoperator==(constByte&right)const { returnb==right.b; } //…};
可以看出二元操作符作為成員函數(shù)只需帶一個參數(shù),這是因為默認的帶一個this參數(shù)。雖然類的定義者可以把上面的操作符定義為全局的,也可以定義為成員的,但是有幾個操作符必須定義為成員的,這幾個操作符是?=、[]、()、->。下面簡單介紹這幾個特殊操作符的用法。
1)操作符?=
先看下面的代碼:
Strings1("haha!");Strings2=s1;s2=s1;
上面三行代碼每行的意義都不一樣。第一行調用了String的帶參構造函數(shù);第二行調用了第3章中講過的拷貝構造函數(shù);第三行則調用的是重載的操作符?=,將s1的值直接賦給s2。這是同類型的變量間的相互賦值,也可以將其他類型的變量賦給它,例如:
s2="haha";這是將一個常字符串賦給了自定義的String類。而這樣寫對自定義的類是有要求的,即要重載相應的操作符。下面給出Sting的定義:
classString{public: //構造函數(shù)的重載集合
//提供自動初始化
String(constchar*=0); String(constString&); //析構函數(shù):自動釋放初始化的對象
~String(); //賦值操作符的重載集合
String&operator=(constString&); String&operator=(constchar*); //重載的下標操作符char&operator[](int)const; //成員訪問函數(shù)
intsize(){returnm_size;} char*c_str(){returnm_string;} private: intm_size; char*m_string;};
可以看出重載的?=?有兩個版本,一個是同類型賦值,另一個是將常字符串賦給String。下面給出這兩個函數(shù)分別是怎樣實現(xiàn)的:
String&String::operator=(constString&s){ m_size=s.size(); delete[]m_string; m_string=newchar[m_size+1]; strcpy(m_string,s.c_str()); return*this;}String&String::operator=(constchar*sobj){ //sobj是個空指針
if(!sobj){ m_size=0; delete[]m_string; m_string=0; } else{ m_size=strlen(sobj); delete[]m_string; m_string=newchar[m_size+1]; strcpy(m_string,sobj);} return*this;}
同類型賦值時,先進行長度賦值,接著刪除m_string指向的空間,然后建立新空間,將另一個字符串的內容拷貝到新建立的空間中,最后將this返回。支持C類型賦值時,先檢測是不是空指針,如果是則將長度置為零,刪除其指向的空間,將指針置為零以防野指針。接下來的操作與將String賦給String類似。如果不自己重載?=,系統(tǒng)會有默認的版本,不過這個默認的版本只是簡單的位拷貝,對于這里自己定義的String中涉及到指針時,默認的版本是不能勝任的,必須自己寫代碼來拷貝相應的空間。2)操作符[]
操作符?[]?稱為下標操作符。如果定義了一個數(shù)組a,就可以通過下標操作符去訪問每一個元素。通過重載,對自己定義的類也可使用此操作符去訪問類的元素。例如:
Stringentry("welcome!");Stringmycopy;for(intix=0;ix<entry.size();++ix)mycopy[ix]=entry[ix];
這里需要對類成員m_string的單個字符進行讀/寫,下標操作符必須能夠出現(xiàn)在一個賦值操作符的左右兩邊,為了能在左邊出現(xiàn),它的返回值必須是一個左值。這可以通過把返回類型指定為一個引用來實現(xiàn):
char&String::operator[](intelem)const{ assert(elem>=0&&elem<m_size); returnm_string[elem];}
此處下標操作符帶了一個參數(shù)來指定下標。在下標操作符的定義中它對于接收到的索引值進行邊界檢查,這里用C庫函數(shù)assert()執(zhí)行這種檢查,也可以拋出一個異常來指明elem值為負或大于m_string指向的C風格字符串的長度。關于異常處理將在后面的章節(jié)中討論。3)操作符()
操作符?()?稱為函數(shù)調用操作符。如果一個類類型被定義成表示一個操作時,則可以為這個類類型重載函數(shù)調用操作符以便調用這個操作。例如,absInt類封裝了將取一個int型操作數(shù)的絕對值的操作:
classabsInt{public:intoperator()(intval){ intresult=val<0?-val:val; returnresult; }};
此類很簡單,只定義了一個操作:函數(shù)調用操作符重載,此函數(shù)帶一個參數(shù),返回參數(shù)的絕對值。調用此操作符只要像調用函數(shù)那樣,給該類的對象一個參數(shù)就可以了。例如:
inti=-12;absIntabsObj; //該對象定義了函數(shù)調用操作符
unsignedintui=absObj(i);//調用了absInt的函數(shù)調用操作符盡管absObj是個對象而不是函數(shù),但是對那個對象進行了“調用”。這是因為這個對象的所屬類對函數(shù)調用操作符進行了重載。定義了函數(shù)調用的對象叫函數(shù)對象,因為這些對象可以像函數(shù)一樣工作。
函數(shù)對象一般作為算法的參數(shù)來使用。在第12章將講解STL(標準模板庫)算法,函數(shù)對象和算法相結合是很有效的。
4)操作符->
可以為類類型的對象重載成員訪問操作符→,它必須被定義為一個類的成員函數(shù)。它的作用是賦予一個類類型與指針類似的行為。它通常被用于代表“智能指針”(SmartPointer)的類類型。也就是說一個類的行為很像內置的指針類型,但是還有某些額外的功能。例如:
classScreen{public: voidmove(int,int);//…private: short_height; //行數(shù)
short_width; //列數(shù)
//…};classScreenPtr{//…private: Screen*ptr;};
該例簡單地定義了一個Screen類,ScreenPtr只有一個數(shù)據(jù)成員即指向Screen的指針。定義了ScreenPtr類來保證這種類的對象總是指向Screen對象。它不能不指向對象,如同內置指針一樣的應用程序可以直接使用ScreenPtr類型的對象而不用先測試它是否指向一個Screen對象。為了獲得這種行為,需定義一個帶有構造函數(shù)的ScreenPtr類,但是它沒有缺省構造函數(shù)。
classScreenPtr{public: ScreenPtr(Screen&s):ptr(&s){} //…};ScreenPtr類型的對象的定義必須提供初始值——一個Screen類型的對象,ScreenPtr對象將指向它,否則ScreenPtr對象的定義就是錯誤的。
ScreenPtrp1; //錯誤:ScreenPtr沒有缺省構造函數(shù)
ScreenmyScreen(4,4);ScreenPtrps(myScreen); //ok
為使ScreenPtr類的行為像內置指針,必須再定義一些重載操作符,定義的兩個操作符是解引用操作符?*?和成員操作符?->。
//支持指針行為的重載操作符classScreenPtr{public:ScreenPtr(Screen&s):ptr(&s){} Screen&operator*(){return*ptr;} Screen*operator->(){returnptr;} //...};
這樣就可以使用重載的?->?了:
ScreenmyScreen(4,4);ScreenPtrps(myScreen); ps->move(3,4);
成員訪問操作符?->?的左操作數(shù)的類型是ScreenPtr,而該類型重載了?->,所以會調用重載的操作符。該操作符返回一個指向Screen類對象的指針,內置成員訪問操作符->被應用在這個返回值上,以調用Screen類的成員函數(shù)move()。上面介紹的幾個操作符都必須定義為成員函數(shù),當左側操作符是當前類的對象時,運算符會工作得很好。但是,當左側運算符是其他類的對象時,成員函數(shù)是不能勝任的。這種情況通常出現(xiàn)在為輸入/輸出流重載operator<<?和?>>?時。如為自定義的String重載?<<?函數(shù):
#include<iostream>usingnamespacestd;classString{public: //… //成員訪問函數(shù)
intsize(){returnm_size;} char*c_str(){returnm_string;} //全局的函數(shù)
friendostream& operator<<(ostream&os,constString&s);private: intm_size; char*m_string;};ostream&operator<<(ostream&os,constString&s){ os<<s.c_str()<<endl; returnos;}
現(xiàn)在如果有個String的對象s,則可以將它輸出到控制臺:
cout<<s;
這樣可以將自定義的類像內置數(shù)據(jù)一樣很方便地輸出到輸出流上。全局的操作符能滿足這種需求,而成員類型是不能勝任的。所以成員重載和全局重載都有它們各自的用武之地。還有幾個不常用的操作符,如:“->”、“*”、“,”等很少重載,重載了也比較晦澀。4.2.3不能重載的運算符在可用的運算符集合中存在一些不能被重載的運算符。這樣限制的原因通常是出于對安全的考慮。如果這些運算符被重載了,將會危害或破壞安全機制。不能被重載的運算符有四個,即“::”、“.”、“.*”、“?:”。
(1)范圍解析操作符(::)可以用來標示一個變量是全局的或者是哪個類的變量,通過它可以引用到類的靜態(tài)變量和方法。它不能被重載,如果被重載將會引起混亂。
(2)成員選擇符(.)在類中對任何成員都有一定的意義。如果允許它被重載,就不能用普通的方法訪問成員,只能用指針和箭頭操作符?->?訪問。(3)成員指針間接引用操作符(.*)因為與操作符?.
同樣的原因而不能被重載。
(4)三元運算符(?:)不常被使用,也因為它是三元操作符所以不能被重載。4.2.4new和delete重載當寫一個new表達式時,首先調用operatornew()分配內存,然后調用構造函數(shù)。而使用delete表達式時,先調用析構函數(shù),然后調用operatordelete()釋放內存。重載雖然不能控制構造函數(shù)和析構函數(shù),但是能改變operatornew()和operatordelete()的內容。如果要創(chuàng)建和銷毀一個特定類的非常多的對象,那么new和delete會成為速度的瓶頸,它們會影響效率。可以重載它們來實現(xiàn)自己的內存分配方案。還有就是堆碎片問題,分配大小不同的內存可能會在堆上產(chǎn)生很多碎片,以至于很快用完內存。雖然可能還有內存,但是那些都是碎片,找不到足夠大的內存塊滿足需求。通過為特定類制定內存分配方案會防止這類問題的發(fā)生。在缺省情況下,空閑存儲區(qū)中的類對象的分配和釋放由在C++標準庫中定義的全局操作符new()和delete()來執(zhí)行。如果一個類提供了操作符new()和delete()的成員函數(shù),那么它就可以承接自己的內存管理權。如果在類中定義了這些成員操作符,則它們會被調用,以取代全局操作符來分配和釋放該類類型的對象。下面的例子中為類Framis創(chuàng)建了簡單的內存分配系統(tǒng)。程序開始時在靜態(tài)數(shù)據(jù)區(qū)留出一塊存儲單元。這塊內存區(qū)被用來為Framis類型的對象分配存儲空間。使用一個byte數(shù)組來標明哪些存儲單元被使用過了,一個字節(jié)代表一塊存儲單元?!境绦?.2】#include<cstddef>#include<fstream>#include<iostream>#include<new>usingnamespacestd;classFramis{ staticunsignedcharpool[]; staticboolalloc_map[];public: enum{psize=100}; //最多100個
Framis(){cout<<"Framis()\n";} ~Framis(){cout<<"~Framis()...";} void*operatornew(size_t); voidoperatordelete(void*);};//初始化
unsignedcharFramis::pool[psize*sizeof(Framis)];boolFramis::alloc_map[psize]={false};void*Framis::operatornew(size_t){ for(inti=0;i<psize;i++) if(!alloc_map[i]){ out<<"usingblock"<<i<<"..."; alloc_map[i]=true; //標示被用了
returnpool+(i*sizeof(Framis)); } cout<<"outofmemory"<<endl; returnnull;}voidFramis::operatordelete(void*m){ //檢測空指針
if(!m)return;
//假設已在pool中創(chuàng)建,算出block號
unsignedlongblock=(unsignedlong)m-(unsignedlong)pool; block/=sizeof(Framis); cout<<"freeingblock"<<block<<endl; //標志那塊空間變?yōu)榭捎?/p>
alloc_map[block]=false;}intmain(){ Framis*f[Framis::psize];
for(inti=0;i<Framis::psize;i++) f[i]=newFramis;
newFramis; //超過最大申請數(shù)了,內存溢出,報錯
deletef[10]; f[10]=0;//使用釋放的空間
Framis*x=newFramis; deletex;
for(intj=0;j<Framis::psize;j++) deletef[j]; //f[10]已被釋放了
return0;}
類成員操作符new()的返回類型必須是void*?型,并且有一個size_t類型的參數(shù),這里的size_t是一個在系統(tǒng)頭文件<cstddef>中被定義的typedef。返回的就是申請到的空間的頭指針,size_t是表明要申請多大的空間。類成員操作符delete()的返回類型必須是void,delete可以有多個參數(shù),但第一個參數(shù)的類型必須是void*。此參數(shù)表示要釋放空間的頭指針。操作符new()和delete()都是類的靜態(tài)static成員函數(shù),它們遵從靜態(tài)成員函數(shù)的一般限制。這些操作符被自動做成靜態(tài)成員函數(shù),而無需程序員顯式地把它們聲明為靜態(tài)的。靜態(tài)成員函數(shù)沒有this指針,因此它們只能訪問靜態(tài)數(shù)據(jù)成員。
程序通過創(chuàng)建一個容納psize個Fraims對象的字節(jié)數(shù)組pool來為Framis分配內存。相應地,分配表alloc_map也有psize個成員,其中每個bool類型成員對應一塊內存。初始化時,分配表alloc_map被全置為false,這里使用了設置首元素的集合初始化技巧,因為編譯器能自動以常規(guī)的預設值來初始化其余的元素(對于bool類型來說,就是初始化為false)。下面看看operatornew()和operatordelete()的實現(xiàn)。類成員operatornew()和全局operatornew()具有相同的語法。首先對分配表進行搜索,尋找值為false的成員,如果找到,就將此塊標示為使用過,然后將此塊的地址返回。如果找不到空閑空間,就打印出內存溢出的錯誤信息,然后返回空指針。operatordelete()首先檢測傳進來的指針是否為空,如果為空則返回;接著假設Framis的地址是在這個堆里創(chuàng)建的,這是個正確的假設,因為無論何時在堆上創(chuàng)建單個的Framis對象都將調用類的成員operatornew()方法;接著根據(jù)傳進來的指針計算出該指針的block號,然后將該號對應的部分置為false,以表明此塊空間被釋放了。在main中,動態(tài)分配足夠多的Framis對象,把可用內存消耗完。接下來再申請一個,這樣可以測試是否還有可用內存。然后釋放一個Framis對象,再創(chuàng)建一個對象以表明釋放的內存可被重復使用。
這個內存分配方案是針對Framis對象的,所以可能比全局的系統(tǒng)默認的new和delete內存分配方案效率高一些。如果為類定義了new和delete,可以通過全局域解析操作符來調用。例如:
::?newFramis;
::?deleteFramis;
如果為一個類重載了operatornew()和operatordelete(),那么無論何時創(chuàng)建這個類的一個對象,都會調用這些操作符。但要創(chuàng)建這個類的一個對象數(shù)組時,全局operatornew()就會被立即調用。因此,可以通過為這個類重載運算符的數(shù)組版本,即operatornew[]和operatordelete[]來控制對象數(shù)組的內存分配。下面給出簡單的例子:#include<new> //定義Size_t#include<iostream>#include<fstream>usingnamespacestd;classWidget{ enum{sz=10}; inti[sz];public: Widget(){cout<<"*";} ~Widget(){cout<<"~";} void*operatornew(size_tsz){ cout<<"Widget::new:" <<sz<<"bytes"<<endl; return::newchar[sz]; } voidoperatordelete(void*p) { cout<<"Widget::delete"<<endl; ::delete[]p; } void*operatornew[](size_tsz){ cout<<"Widget::new[]:" <<sz<<"bytes"<<endl; return::newchar[sz]; } voidoperatordelete[](void*p) { cout<<"Widget::delete[]"<<endl; ::?delete[]p; }};intmain(){ cout<<"newWidget"<<endl; Widget*w=newWidget; cout<<"\ndeleteWidget"<<endl; deletew; cout<<"\nnewWidget[25]"<<endl; Widget*wa=newWidget[25]; cout<<"\ndelete[]Widget"<<endl; delete[]wa; return0;}
這里自定義的new和delete調用了全局版本的,除了打印提示信息外與全局的new和delete的效果是一樣的。當然,可以在重載的new和delete里使用任意的內存分配方案。可以看出,數(shù)組版本的new和delete與單個對象版本相比,除多了一對方括號([])外,語法以及參數(shù)和返回值的規(guī)定都是一樣的。以下是程序的運行結果:
可以看出,new表達式先調用new分配空間,然后調用構造函數(shù)打印星號;delete表達式是先調用析構函數(shù),然后調用delete釋放空間。執(zhí)行newWidget[25]時調用了自定義的數(shù)組new版本,先申請了1004字節(jié)的空間,接著為每個對象調用一次構造函數(shù),所以打印出了25個星號。執(zhí)行delete[]wa時,先為每個對象調用析構函數(shù),打印出了25個~,然后調用全局的delete釋放空間。如果少寫一個[],則只釋放一個內存,這樣會造成24個內存泄露。注意,如果是數(shù)組,一定不要忘記這個方括號。4.3函數(shù)重載與默認參數(shù)前面兩節(jié)中介紹了函數(shù)重載和操作符重載的知識。如果想以不同的方法調用同一函數(shù),怎么辦呢?當函數(shù)有一個長長的參數(shù)列表時,因大多數(shù)參數(shù)每次調用都一樣,書寫這樣的函數(shù)調用會使人感到厭煩,程序的可讀性也差。而C++中有一個很通用的特征叫默認參數(shù)。默認參數(shù)就是在函數(shù)聲明時給定一個值,在調用時沒有指定這一參數(shù)值,編譯器會自動插上這個值的參數(shù)。舉個簡單的例子:一個函數(shù)打印一個整數(shù),讓用戶選擇以幾進制打印出來是合理的,但是在大部分程序中都是以十進制形式打印出來的。例如:
voidprint(intvalue,intbase=10); //默認進制是十進制
函數(shù)在聲明時在參數(shù)后面緊跟著等號(=),后面跟著默認值。這樣就給出默認參數(shù)。在調用含默認參數(shù)的函數(shù)時,可以指定默認參數(shù)的值,也可以不指定。如果指定會覆蓋默認的參數(shù)值。例如:
voidf(){ print(31); print(31,10); print(31,16); print(31,2);}
可能會產(chǎn)生如下結果:
31,31,1f,11111
第一個沒有給參數(shù)傳值,編譯器使用了默認參數(shù)10;后三個都指定了參數(shù),將默認參數(shù)覆蓋了。默認參數(shù)不一定是參數(shù)表達式,可以是任意的表達式。默認參數(shù)在函數(shù)聲明時進行類型檢查,在函數(shù)調用時進行值的計算。例如:
Strings;intf(inta,intb=0,char*=0); //正確
intg(inta,intb=0,char*=0); //錯誤
inth(int,int=0,char*c=s.c_str()); //正確f函數(shù)的默認值是常數(shù)。g函數(shù)為什么錯誤呢?因為?*?和?=?之間沒有空格,編譯器會把?*?和?=?認成一個乘等于的賦值操作符,而不是在給指針賦值,所以編譯器會報錯
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年度車輛抵押登記與汽車租賃及保險服務合同
- 2025年度舞臺設備租賃及舞臺設計服務合同
- 2025年度蔬菜種植戶與農(nóng)業(yè)技術推廣站合作合同范本
- 二零二五年度籃球賽事安全培訓與教育合同
- 2025年度美容院員工培訓及勞動合同范本
- 2025年度跨境電商貿易合同范本4篇
- 2025年度足療店加盟市場拓展合同
- 二零二五版腳手架租賃與施工質量控制合同樣本3篇
- 二零二五版電商平臺數(shù)據(jù)安全處理規(guī)范合同2篇
- 2025年物聯(lián)網(wǎng)(IoT)平臺軟件產(chǎn)品銷售合同2篇
- 2024年資格考試-對外漢語教師資格證筆試參考題庫含答案
- 軟件研發(fā)安全管理制度
- 三位數(shù)除以兩位數(shù)-豎式運算300題
- 寺院消防安全培訓課件
- 比摩阻-管徑-流量計算公式
- GB/T 42430-2023血液、尿液中乙醇、甲醇、正丙醇、丙酮、異丙醇和正丁醇檢驗
- 五年級數(shù)學應用題100道
- 西方經(jīng)濟學(第二版)完整整套課件(馬工程)
- 高三開學收心班會課件
- GB/T 33688-2017選煤磁選設備工藝效果評定方法
- 科技計劃項目申報培訓
評論
0/150
提交評論