天天看點

C++ 面向對象(二)—— 操作符重載

C++ 實作了在類(class)之間使用語言标準操作符,而不隻是在基本資料類型之間使用。例如:

int a, b, c; a = b + c;

是有效操作,因為加号兩邊的變量都是基本資料類型。然而,我們是否可以進行下面的操作就不是那麼顯而易見了(它實際上是正确的):

struct { char product [50]; float price; } a, b, c; a = b + c;

将一個類class (或結構struct)的對象賦給另一個同種類型的對象是允許的(通過使用預設的複制構造函數 copy constructor)。但相加操作就有可能産生錯誤,理論上講它在非基本資料類型之間是無效的。

但歸功于C++ 的操作符重載(overload)能力,我們可以完成這個操作。像以上例子中這樣的組合類型的對象在C++中可以接受如果沒有操作符重載則不能被接受的操作,我們甚至可以修改這些操作符的效果。以下是所有可以被重載的操作符的清單:

+    -    *    /    =    <    >    +=   -=   *=   /=   <<   >>
	<<=  >>=  ==   !=   <=   >=   ++   --   %    &    ^    !    |
	~    &=   ^=   |=   &&   ||   %=   []   ()   new  delete
	      

要想重載一個操作符,我們隻需要編寫一個成員函數,名為operator ,後面跟我們要重載的操作符,遵循以下原型定義:

type operator sign (parameters);

這裡是一個操作符 +的例子。我們要計算二維向量(bidimensional vector) a(3,1) 與b(1,2)的和。兩個二維向量相加的操作很簡單,就是将兩個x 軸的值相加獲得結果的x 軸值,将兩個 y 軸值相加獲得結果的 y值。在這個例子裡,結果是 (3+1,1+2) = (4,3)。

// vectors: overloading operators example
    #include <iostream.h>
    
    class CVector {
      public:
        int x,y;
        CVector () {};
        CVector (int,int);
        CVector operator + (CVector);
    };
    
    CVector::CVector (int a, int b) {
        x = a;
        y = b;
    }
    
    CVector CVector::operator+ (CVector param) {
        CVector temp;
        temp.x = x + param.x;
        temp.y = y + param.y;
        return (temp);
    }
    
    int main () {
        CVector a (3,1);
        CVector b (1,2);
        CVector c;
        c = a + b;
        cout << c.x << "," << c.y;
        return 0;
    }    
			      
4,3

如果你迷惑為什麼看到這麼多遍的 CVector,那是因為其中有些是指class名稱CVector ,而另一些是以它命名的函數名稱,不要把它們搞混了:

CVector (int, int);            // 函數名稱 CVector (constructor)
   CVector operator+ (CVector);   // 函數 operator+ 傳回CVector 類型的值
         

Class CVector的函數 operator+ 是對數學操作符+進行重載的函數。這個函數可以用以下兩種方法進行調用:

c = a + b; c = a.operator+ (b);

注意:我們在這個例子中包括了一個空構造函數 (無參數),而且我們将它定義為無任何操作:

CVector ( ) { };

這是很必要的,因為例子中已經有另一個構造函數,

CVector (int, int);

是以,如果我們不像上面這樣明确定義一個的話,CVector的兩個預設構造函數都不存在。

這樣的話,main( )中包含的語句

CVector c;

将為不合法的。

盡管如此,我已經警告過一個空語句塊 (no-op block)并不是一種值得推薦的構造函數的實作方式,因為它不能實作一個構造函數至少應該完成的基本功能,也就是初始化class中的所有變量。在我們的例子中,這個構造函數沒有完成對變量x 和 y 的定義。是以一個更值得推薦的構造函數定義應該像下面這樣:

CVector ( ) { x=0; y=0; };

就像一個class預設包含一個空構造函數和一個複制構造函數一樣,它同時包含一個對指派操作符assignation operator (=)的預設定義,該操作符用于兩個同類對象之間。這個操作符将其參數對象(符号右邊的對象) 的所有非靜态 (non-static) 資料成員複制給其左邊的對象。當然,你也可以将它重新定義為你想要的任何功能,例如,隻拷貝某些特定class成員。

重載一個操作符并不要求保持其正常的數學含義,雖然這是推薦的。例如,雖然我們可以将操作符+定義為取兩個對象的內插補點,或用==操作符将一個對象賦為0,但這樣做是沒有什麼邏輯意義的。

雖然函數operator+ 的原型定義看起來很明顯,因為它取操作符右邊的對象為其左邊對象的函數operator+的參數,其它的操作符就不一定這麼明顯了。以下清單總結了不同的操作符函數是怎樣定義聲明的 (用操作符替換每個@):

Expression Operator (@) Function member Global function
@a + - * & ! ~ ++ -- A::[email protected]( ) [email protected](A)
[email protected] ++ -- A::[email protected](int) [email protected](A, int)
[email protected] + - * / % ^ & | < > == != <= >= << >> && || , A::[email protected](B) [email protected](A, B)
[email protected] = += -= *= /= %= ^= &= |= <<= >>= [ ] A::[email protected](B) -
a(b, c...) ( ) A::operator()(B, C...) -
a->b -> A::operator->() -

* 這裡a 是class A的一個對象,b 是 B 的一個對象,c 是class C 的一個對象。

從上表可以看出有兩種方法重載一些class操作符:作為成員函數(member function)或作為全域函數(global function)。它們的用法沒有差別,但是我要提醒你,如果不是class的成員函數,則不能通路該class的private 或 protected 成員,除非這個全域函數是該class的 friend (friend 的含義将在後面的章節解釋)。

關鍵字 this

關鍵字this 通常被用在一個class内部,指正在被執行的該class的對象(object)在記憶體中的位址。它是一個指針,其值永遠是自身object的位址。

它可以被用來檢查傳入一個對象的成員函數的參數是否是該對象本身。例如:

// this
    #include <iostream.h>
    
    class CDummy {
      public:
        int isitme (CDummy& param);
    };
    
    int CDummy::isitme (CDummy& param) {
        if (&param == this) return 1;
        else return 0;
    }
    
    int main () {
        CDummy a;
        CDummy* b = &a;
        if ( b->isitme(a) )
            cout << "yes, &a is b";
        return 0;
    }
			      
yes, &a is b

它還經常被用在成員函數operator= 中,用來傳回對象的指針(避免使用臨時對象)。以下用前面看到的向量(vector)的例子來看一下函數operator= 是怎樣實作的:

CVector& CVector::operator= (const CVector& param) {
       x=param.x;
       y=param.y;
       return *this;
   }      

實際上,如果我們沒有定義成員函數operator=,編譯器自動為該class生成的預設代碼有可能就是這個樣子的。

靜态成員(Static members)

一個class 可以包含靜态成員(static members),可以是資料,也可以是函數。

一個class的靜态資料成員也被稱作類變量"class variables",因為它們的内容不依賴于某個對象,對同一個class的所有object具有相同的值。

例如,它可以被用作計算一個class聲明的objects的個數,見以下代碼程式:

// static members in classes
    #include <iostream.h>
    
    class CDummy {
      public:
        static int n;
        CDummy () { n++; };
        ~CDummy () { n--; };
    };
    
    int CDummy::n=0;
    
    int main () {
        CDummy a;
        CDummy b[5];
        CDummy * c = new CDummy;
        cout << a.n << endl;
        delete c;
        cout << CDummy::n << endl;
        return 0;
    } 
			      

7

6

實際上,靜态成員與全域變量(global variable)具有相同的屬性,但它享有類(class)的範圍。是以,根據ANSI-C++ 标準,為了避免它們被多次重複聲明,在class的聲明中隻能夠包括static member的原型(聲明),而不能夠包括其定義(初始化操作)。為了初始化一個靜态資料成員,我們必須在class之外(在全域範圍内),包括一個正式的定義,就像上面例子中做法一樣。

因為它對同一個class的所有object是同一個值,是以它可以被作為該class的任何object的成員所引用,或者直接被作為class的成員引用(當然這隻适用于static 成員):

cout << a.n; cout << CDummy::n;

以上兩個調用都指同一個變量:class CDummy裡的static 變量 n 。

在提醒一次,它其實是一個全域變量。唯一的不同是它的名字跟在class的後面。

就像我們會在class中包含static資料一樣,我們也可以使它包含static 函數。它們表示相同的含義:static函數是全域函數(global functions),但是像一個指定class的對象成員一樣被調用。它們隻能夠引用static 資料,永遠不能引用class的非靜态(nonstatic)成員。它們也不能夠使用關鍵字this,因為this實際引用了一個對象指針,但這些 static函數卻不是任何object的成員,而是class的直接成員。

繼續閱讀