分清成員函式,非成員函式和友元函式

2021-04-12 18:26:48 字數 3046 閱讀 7421

成員函式和非成員函式最大的區別在於成員函式可以是虛擬的而非成員函式不行。所以,如果有個函式必須進行動態繫結(見條款38),就要採用虛函式,而虛函式必定是某個類的成員函式。關於這一點就這麼簡單。如果函式不必是虛擬的,情況就稍微複雜一點。

看下面表示有理數的乙個類:

class rational ;

這是乙個沒有一點用處的類。(用條款18的術語來說,介面的確最小,但遠不夠完整。)所以,要對它增加加,減,乘等算術操作支援,但是,該用成員函式還是非成員函式,或者,非成員的友元函式來實現呢?

當拿不定主意的時候,用物件導向的方法來考慮!有理數的乘法是和rational類相聯絡的,所以,寫乙個成員函式把這個操作包到類中。

class rational ;

(如果你不明白為什麼這個函式以這種方式宣告——返回乙個const值而取乙個const的引用作為它的引數——參考條款21-23。)

現在可以很容易地對有理數進行乘法操作:

rational oneeighth(1, 8);

rational onehalf(1, 2);

rational result = onehalf * oneeighth;   // 執行良好

result = result * oneeighth;             // 執行良好

但不要滿足,還要支援混合型別操作,比如,rational要能和int相乘。但當寫下下面的**時,只有一半工作:

result = onehalf * 2;      // 執行良好

result = 2 * onehalf;      // 出錯!

這是乙個不好的苗頭。記得嗎?乘法要滿足交換律。

如果用下面的等價函式形式重寫上面的兩個例子,問題的原因就很明顯了:

result = onehalf.operator*(2);      // 執行良好

result = 2.operator*(onehalf);      // 出錯!

物件onehalf是乙個包含operator*函式的類的例項,所以編譯器呼叫了那個函式。而整數2沒有相應的類,所以沒有operator*成員函式。編譯器還會去搜尋乙個可以象下面這樣呼叫的非成員的operator*函式(即,在某個可見的名字空間裡的operator*函式或全域性的operator*函式):

result = operator*(2, onehalf);      // 錯誤!

但沒有這樣乙個引數為int和rational的非成員operator*函式,所以搜尋失敗。

再看看那個成功的呼叫。它的第二引數是整數2,然而rational::operator*期望的引數卻是rational物件。怎麼回事?為什麼2在乙個地方可以工作而另乙個地方不行?

秘密在於隱式型別轉換。編譯器知道傳的值是int而函式需要的是rational,但它也同時知道呼叫rational的建構函式將int轉換成乙個合適的rational,所以才有上面成功的呼叫(見條款m19)。換句話說,編譯器處理這個呼叫時的情形類似下面這樣:

const rational temp(2);      // 從2產生乙個臨時

// rational物件

result = onehalf * temp;     // 同onehalf.operator*(temp);

當然,只有所涉及的建構函式沒有宣告為explicit的情況下才會這樣,因為explicit建構函式不能用於隱式轉換,這正是explicit的含義。如果rational象下面這樣定義:

class rational

rational onefourth(1, 4);

rational result;

result = onefourth * 2;           // 工作良好

result = 2 * onefourth;           // 萬歲, 它也工作了!

這當然是乙個完美的結局,但還有乙個擔心:operator*應該成為rational類的友元嗎?

這種情況下,答案是不必要。因為operator*可以完全通過類的公有(public)介面來實現。上面的**就是這麼做的。只要能避免使用友元函式就要避免,因為,和現實生活中差不多,友元(朋友)帶來的麻煩往往比它(他/她)對你的幫助多。

然而,很多情況下,不是成員的函式從概念上說也可能是類介面的一部分,它們需要訪問類的非公有成員的情況也不少。

讓我們回頭再來看看本書那個主要的例子,string類。如果想過載operator>>和operator《來讀寫string物件,你會很快發現它們不能是成員函式。如果是成員函式的話,呼叫它們時就必須把string物件放在它們的左邊:

// 乙個不正確地將operator>>和

// operator《作為成員函式的類

class string ;

string s;

s >> cin;                   // 合法, 但

// 有違常規

s << cout;                  // 同上

這會把別人弄糊塗。所以這些函式不能是成員函式。注意這種情況和前面的不同。這裡的目標是自然的呼叫語法,前面關心的是隱式型別轉換。

所以,如果來設計這些函式,就象這樣:

istream& operator>>(istream& input, string& string)

ostream& operator<<(ostream& output,

const string& string)

注意上面兩個函式都要訪問string類的data成員,而這個成員是私有(private)的。但我們已經知道,這個函式一定要是非成員函式。這樣,就別無選擇了:需要訪問非公有成員的非成員函式只能是類的友元函式。

·虛函式必須是成員函式。如果f必須是虛函式,就讓它成為c的成員函式。

·operator>>和operator《決不能是成員函式。如果f是operator>>或operator<<,讓f成為非成員函式。如果f還需要訪問c的非公有成員,讓f成為c的友元函式。

·只有非成員函式對最左邊的引數進行型別轉換。如果f需要對最左邊的引數進行型別轉換,讓f成為非成員函式。如果f還需要訪問c的非公有成員,讓f成為c的友元函式。

·其它情況下都宣告為成員函式。如果以上情況都不是,讓f成為c的成員函式。

分清成員函式,非成員函式和友元函式

成員函式和非成員函式最大的區別在於成員函式可以是虛擬的而非成員函式不行。所以,如果有個函式必須進行動態繫結 見條款38 就要採用虛函式,而虛函式必定是某個類的成員函式。關於這一點就這麼簡單。如果函式不必是虛擬的,情況就稍微複雜一點。看下面表示有理數的乙個類 class rational 這是乙個沒有...

條款十九 分清成員函式,非成員函式和友元函式

成員函式和非成員函式最大的區別在於成員函式可以是虛擬的而非成員函式不行。所以,如果有個函式必須進行動態繫結 見條款38 就要採用虛函式,而虛函式必定是某個類的成員函式。如果函式不必是虛擬的,情況就稍微複雜一點。看下面表示有理數的乙個類 class rational 現在可以很容易地對有理數進行乘法操...

成員函式 非成員函式和友元函式

成員函式和非成員函式最大的區別在於成員函式可以是虛擬的而非成員函式不行。所以,如果有個函式必須進行動態繫結,就要採用虛函式,而虛函式必定是某個類的成員函式。如果函式不必是虛擬的,情況就稍微複雜一點。看下面表示有理數的乙個類 class rational 這是乙個沒有一點用處的類 介面最小,但遠不夠完...