C++ 面向對象(三)—— 類之間的關係

友元函數(Friend functions)

在前面的章節中我們已經看到了對class的不同成員存在3個層次的內部保護:public, protected 和 private。在成員爲 protected 和 private的情況下,它們不能夠被從所在的class以外的部分引用。然而,這個規則可以通過在一個class中使用關鍵字friend來繞過,這樣我們可以允許一個外部函數獲得訪問class的protected 和 private 成員的能力。

爲了實現允許一個外部函數訪問class的private 和 protected 成員,我們必須在class內部用關鍵字friend來聲明該外部函數的原型,以指定允許該函數共享class的成員。在下面的例子中我們聲明瞭一個 friend 函數 duplicate:

    // friend functions
    #include <iostream.h>
    
    class CRectangle {
        int width, height;
      public:
        void set_values (int, int);
        int area (void) {return (width * height);}
        friend CRectangle duplicate (CRectangle);
    };
    
    void CRectangle::set_values (int a, int b) {
        width = a;
        height = b;
    }
    
    CRectangle duplicate (CRectangle rectparam) {
        CRectangle rectres;
        rectres.width = rectparam.width*2;
        rectres.height = rectparam.height*2;
        return (rectres);
    }
    
    int main () {
        CRectangle rect, rectb;
        rect.set_values (2,3);
        rectb = duplicate (rect);
        cout << rectb.area();
    }
			
24

函數duplicate是CRectangle的friend,因此在該函數之內,我們可以訪問CRectangle 類型的各個object的成員 width 和 height。注意,在 duplicate()的聲明中,及其在後面main()裏被調用的時候,我們並沒有把duplicate 當作class CRectangle的成員,它不是。

friend 函數可以被用來實現兩個不同class之間的操作。廣義來說,使用friend 函數是面向對象編程之外的方法,因此,如果可能,應儘量使用class的成員函數來完成這些操作。比如在以上的例子中,將函數duplicate() 集成在class CRectangle 可以使程序更短。


友元類 (Friend classes)

就像我們可以定義一個friend 函數,我們也可以定義一個class是另一個的friend,以便允許第二個class訪問第一個class的 protected 和 private 成員。

    // friend class
    #include <iostream.h>
    
    class CSquare;

    class CRectangle {
        int width, height;
      public:
        int area (void) {return (width * height);}
        void convert (CSquare a);
    };
    
    Class CSquare {
      private:
        int side;
      public:
        void set_side (int a){side=a;}
        friend class CRectangle;
    };
    
    void CRectangle::convert (CSquare a) {
        width = a.side;
        height = a.side;
    }
    
    int main () {
        CSquare sqr;
        CRectangle rect;
        sqr.set_side(4);
        rect.convert(sqr);
        cout << rect.area();
        return 0;
    }
			
16

在這個例子中,我們聲明瞭CRectangle 是CSquare 的friend,因此CRectangle可以訪問CSquare 的protected 和 private 成員,更具體地說,可以訪問CSquare::side,它定義了正方形的邊長。

在上面程序的第一個語句裏你可能也看到了一些新的東西,就是class CSquare空原型。這是必需的,因爲在CRectangle 的聲明中我們引用了CSquare (作爲convert()的參數)。CSquare 的定義在CRectangle的後面,因此如果我們沒有在這個class之前包含一個CSquare 的聲明,它在CRectangle中就是不可見的。

這裏要考慮到,如果沒有特別指明,友元關係(friendships)並不是相互的。在我們的CSquare 例子中,CRectangle 是一個friend類,但因爲CRectangle 並沒有對CSquare作相應的聲明,因此CRectangle 可以訪問CSquare 的 protected 和private 成員,但反過來並不行,除非我們將 CSquare 也定義爲CRectangle的 friend。


類之間的繼承(Inheritance between classes)

類的一個重要特徵是繼承,這使得我們可以基於一個類生成另一個類的對象,以便使後者擁有前者的某些成員,再加上它自己的一些成員。例如,假設我們要聲明一系列類型的多邊形,比如長方形CRectangle或三角形CTriangle。它們有一些共同的特徵,比如都可以只用兩條邊來描述:高(height)和底(base)。

這個特點可以用一個類CPolygon 來表示,基於這個類我們可以引申出上面提到的兩個類CRectangle 和 CTriangle 。

類CPolygon 包含所有多邊形共有的成員。在我們的例子裏就是: width 和 height。而CRectangle 和 CTriangle 將爲它的子類(derived classes)。

由其它類引申而來的子類繼承基類的所有可視成員,意思是說,如果一個基類包含成員A ,而我們將它引申爲另一個包含成員B的類,則這個子類將同時包含 A 和 B。

要定義一個類的子類,我們必須在子類的聲明中使用冒號(colon)操作符: ,如下所示:

class derived_class_name: public base_class_name;

這裏derived_class_name 爲子類(derived class)名稱,base_class_name 爲基類(base class)名稱。public 也可以根據需要換爲protected 或 private,描述了被繼承的成員的訪問權限,我們在以下例子後會很快看到:

    // derived classes
    #include <iostream.h>
    
    Class CPolygon {
      protected:
        int width, height;
      public:
        void set_values (int a, int b) { width=a; height=b;}
    };
    
    class CRectangle: public CPolygon {
      public:
        int area (void){ return (width * height); }
    };
    
    class CTriangle: public CPolygon {
      public:
        int area (void){ return (width * height / 2); }
    };
    
    int main () {
        CRectangle rect;
        CTriangle trgl;
        rect.set_values (4,5);
        trgl.set_values (4,5);
        cout << rect.area() << endl;
        cout << trgl.area() << endl;
        return 0;
    }
			
20
10

如上所示,類 CRectangle 和 CTriangle 的每一個對象都包含CPolygon的成員,即: width, height 和 set_values()。

標識符protected 與 private類似,它們的唯一區別在繼承時才表現出來。當定義一個子類的時候,基類的protected 成員可以被子類的其它成員所使用,然而private 成員就不可以。因爲我們希望CPolygon的成員width 和 height能夠被子類CRectangle 和 CTriangle 的成員所訪問,而不只是被CPolygon自身的成員操作,我們使用了protected 訪問權限,而不是 private。

下表按照誰能訪問總結了不同訪問權限類型:

可以訪問 public protected private
本class的成員 yes yes yes
子類的成員 yes yes no
非成員 yes no no

這裏"非成員"指從class以外的任何地方引用,例如從main()中,從其它的class中或從全域(global)或本地(local)的任何函數中。

在我們的例子中,CRectangle 和CTriangle 繼承的成員與基類CPolygon擁有同樣的訪問限制:

   CPolygon::width           // protected access
   CRectangle::width         // protected access
   CPolygon::set_values()    // public access
   CRectangle::set_values()  // public access
   

這是因爲我們在繼承的時候使用的是public,記得我們用的是:

class CRectangle: public CPolygon;

這裏關鍵字 public 表示新的類(CRectangle)從基類(CPolygon)所繼承的成員必須獲得最低程度保護。這種被繼承成員的訪問限制的最低程度可以通過使用 protected 或 private而不是public來改變。例如,daughter 是mother 的一個子類,我們可以這樣定義:

class daughter: protected mother;

這將使得protected 成爲daughter 從mother處繼承的成員的最低訪問限制。也就是說,原來mother 中的所有public 成員到daughter 中將會成爲protected 成員,這是它們能夠被繼承的最低訪問限制。當然這並不是限制daughter 不能有它自己的public 成員。最低訪問權限限制只是建立在從mother中 繼承的成員上的。

最常用的繼承限制除了public 外就是private ,它被用來將基類完全封裝起來,因爲在這種情況下,除了子類自身外,其它任何程序都不能訪問那些從基類繼承而來的成員。不過大多數情況下繼承都是使用public的。

如果沒有明確寫出訪問限制,所有由關鍵字class 生成的類被默認爲private ,而所有由關鍵字struct 生成的類被默認爲public。


什麼是從基類中繼承的? (What is inherited from the base class?)

理論上說,子類(drived class)繼承了基類(base class)的所有成員,除了:

  • 構造函數Constructor 和析構函數destructor
  • operator=() 成員
  • friends

雖然基類的構造函數和析構函數沒有被繼承,但是當一個子類的object被生成或銷燬的時候,其基類的默認構造函數 (即,沒有任何參數的構造函數)和析構函數總是被自動調用的。

如果基類沒有默認構造函數,或你希望當子類生成新的object時,基類的某個重載的構造函數被調用,你需要在子類的每一個構造函數的定義中指定它:

derived_class_name (parameters) : base_class_name (parameters) {}

例如 (注意程序中黑體的部分):

    // constructors and derivated classes
    #include <iostream.h>
    
    class mother {
      public:
        mother ()
          { cout << "mother: no parameters\n"; }
        mother (int a)
          { cout << "mother: int parameter\n"; }
    };
    
    class daughter : public mother {
      public:
        daughter (int a)
          { cout << "daughter: int parameter\n\n"; }
    };
    
    class son : public mother {
      public:
        son (int a) : mother (a)
          { cout << "son: int parameter\n\n"; }
    };
    
    int main () {
        daughter cynthia (1);
        son daniel(1);
        return 0;
    }
			
mother: no parameters
daughter: int parameter

mother: int parameter
son: int parameter

觀察當一個新的daughter object生成的時候mother的哪一個構造函數被調用了,而當新的son object生成的時候,又是哪一個被調用了。不同的構造函數被調用是因爲daughter 和 son的構造函數的定義不同:

   daughter (int a)          // 沒有特別制定:調用默認constructor
   son (int a) : mother (a)  // 指定了constructor: 調用被指定的構造函數
   

多重繼承(Multiple inheritance)

在C++ 中,一個class可以從多個class中繼承屬性或函數,只需要在子類的聲明中用逗號將不同基類分開就可以了。例如,如果我們有一個特殊的class COutput 可以實現向屏幕打印的功能,我們同時希望我們的類CRectangle 和 CTriangle 在CPolygon 之外還繼承一些其它的成員,我們可以這樣寫:

class CRectangle: public CPolygon, public COutput {
class CTriangle: public CPolygon, public COutput {

以下是一個完整的例子:

    // multiple inheritance
    #include <iostream.h>
    
    class CPolygon {
      protected:
        int width, height;
      public:
        void set_values (int a, int b)
          { width=a; height=b;}
    };
    
    class COutput {
      public:
        void output (int i);
    };
    
    void COutput::output (int i) {
        cout << i << endl;
    }
    
    class CRectangle: public CPolygon, public COutput {
      public:
        int area (void)
          { return (width * height); }
    };
    
    class CTriangle: public CPolygon, public COutput {
      public:
        int area (void)
          { return (width * height / 2); }
    };
    
    int main () {
        CRectangle rect;
        CTriangle trgl;
        rect.set_values (4,5);
        trgl.set_values (4,5);
        rect.output (rect.area());
        trgl.output (trgl.area());
        return 0;
    }
			
20
10
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章