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::operator@( ) operator@(A)
a@ ++ -- A::operator@(int) operator@(A, int)
a@b + - * / % ^ & | < > == != <= >= << >> && || , A::operator@(B) operator@(A, B)
a@b = += -= *= /= %= ^= &= |= <<= >>= [ ] A::operator@(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的直接成員。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章