友元和繼承

友元函式

原則上,一個類的私有(private)和保護(protected)成員不能從宣告它們的類外部訪問。但是,這條規則不適用於“友元”

友元是使用 friend 關鍵字宣告的函式或類。

如果一個非成員函式被宣告為某個類的友元,那麼它就可以訪問該類的私有和保護成員。這需要在該類內部包含這個外部函式的宣告,並在其前面加上關鍵字 friend

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// friend functions
#include <iostream>
using namespace std;

class Rectangle {
    int width, height;
  public:
    Rectangle() {}
    Rectangle (int x, int y) : width(x), height(y) {}
    int area() {return width * height;}
    friend Rectangle duplicate (const Rectangle&);
};

Rectangle duplicate (const Rectangle& param)
{
  Rectangle res;
  res.width = param.width*2;
  res.height = param.height*2;
  return res;
}

int main () {
  Rectangle foo;
  Rectangle bar (2,3);
  foo = duplicate (bar);
  cout << foo.area() << '\n';
  return 0;
}
24

函式 duplicate 是類 Rectangle 的一個友元。因此,函式 duplicate 能夠訪問 Rectangle 型別不同物件的成員 widthheight(它們是私有的)。但請注意,無論是在 duplicate 的宣告中,還是之後在 main 函式中的使用,函式 duplicate 都未被視作類 Rectangle 的成員。它不是!它只是有權訪問其私有和保護成員,但本身並非成員。

友元函式的典型用例是需要在兩個不同類之間進行操作,並訪問這兩個類的私有或保護成員。

友元類

與友元函式類似,友元類是一個其成員函式可以訪問另一個類的私有或保護成員的類。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// friend class
#include <iostream>
using namespace std;

class Square;

class Rectangle {
    int width, height;
  public:
    int area ()
      {return (width * height);}
    void convert (Square a);
};

class Square {
  friend class Rectangle;
  private:
    int side;
  public:
    Square (int a) : side(a) {}
};

void Rectangle::convert (Square a) {
  width = a.side;
  height = a.side;
}
  
int main () {
  Rectangle rect;
  Square sqr (4);
  rect.convert(sqr);
  cout << rect.area();
  return 0;
}
16

在此示例中,類 Rectangle 是類 Square 的友元,這允許 Rectangle 的成員函式訪問 Square 的私有和保護成員。更具體地說,Rectangle 訪問了描述正方形邊長的成員變數 Square::side

這個例子中還有一個新內容:在程式開頭,有一個類 Square 的空宣告。這是必需的,因為類 Rectangle 用到了 Square(作為成員函式 convert 的引數),而 Square 又用到了 Rectangle(宣告其為友元)。

友元關係除非特別宣告,否則絕不是相互的:在我們的例子中,SquareRectangle 視為友元類,但 Rectangle 並不將 `Square` 視為友元。因此,Rectangle 的成員函式可以訪問 Square 的保護和私有成員,但反之則不行。當然,如果需要,Square 也可以被宣告為 Rectangle 的友元,從而授予這種訪問許可權。

友元關係的另一個特性是它不具有傳遞性:友元的友元不會被視為友元,除非被明確宣告。

類之間的繼承

C++ 中的類可以被擴充套件,創建出保留基類特性的新類。這個過程稱為繼承,涉及一個基類和一個派生類派生類繼承基類的成員,並在此基礎上可以新增自己的成員。

例如,讓我們想象一系列類來描述兩種多邊形:矩形和三角形。這兩種多邊形具有某些共同的屬性,比如計算面積所需的值:它們都可以簡單地用一個高和一個寬(或底)來描述。

這在類的世界裡可以表示為:一個 Polygon 類,然後從它派生出另外兩個類:RectangleTriangle


Polygon 類將包含兩種多邊形共有的成員。在我們的例子中是:widthheight。而 RectangleTriangle 將是它的派生類,各自擁有不同於其他多邊形的特定特性。

從其他類派生出來的類會繼承基類所有可訪問的成員。這意味著,如果一個基類包含一個成員 A,我們從它派生出一個帶有另一個成員 B 的類,那麼這個派生類將同時包含成員 A 和成員 B

兩個類的繼承關係在派生類中宣告。派生類的定義使用以下語法:

class derived_class_name: public base_class_name
{ /*...*/ };

其中 derived_class_name 是派生類的名稱,base_class_name 是其所基於的類的名稱。public 訪問說明符可以被其他任何訪問說明符(protectedprivate)替換。這個訪問說明符限制了從基類繼承的成員的最寬鬆訪問級別:訪問級別更寬鬆的成員將以此級別被繼承,而訪問級別相同或更嚴格的成員則在派生類中保持其嚴格級別。

// derived classes
#include <iostream>
using namespace std;

class Polygon {
  protected:
    int width, height;
  public:
    void set_values (int a, int b)
      { width=a; height=b;}
 };

class Rectangle: public Polygon {
  public:
    int area ()
      { return width * height; }
 };

class Triangle: public Polygon {
  public:
    int area ()
      { return width * height / 2; }
  };
  
int main () {
  Rectangle rect;
  Triangle trgl;
  rect.set_values (4,5);
  trgl.set_values (4,5);
  cout << rect.area() << '\n';
  cout << trgl.area() << '\n';
  return 0;
}
20
10

RectangleTriangle 的物件各自包含從 Polygon 繼承的成員。它們是:widthheightset_values

Polygon 類中使用的 protected 訪問說明符與 private 類似。它們唯一的區別實際上體現在繼承上:當一個類繼承另一個類時,派生類的成員可以訪問從基類繼承的保護成員,但不能訪問其私有成員。

透過將 widthheight 宣告為 protected 而不是 private,這些成員也可以從派生類 RectangleTriangle 中訪問,而不僅僅是從 Polygon 的成員中訪問。如果它們是 public,那麼它們可以從任何地方被訪問。

我們可以透過下表總結不同訪問型別以及哪些函式可以訪問它們:

訪問許可權publicprotectedprivate
同一個類的成員
派生類的成員
非成員

這裡的“非成員”指任何來自類外部的訪問,例如從 main 函式、從另一個類或從一個函式中訪問。

在上面的例子中,RectangleTriangle 繼承的成員具有與它們在基類 Polygon 中相同的訪問許可權。

1
2
3
4
5
Polygon::width           // protected access
Rectangle::width         // protected access

Polygon::set_values()    // public access
Rectangle::set_values()  // public access  

這是因為在每個派生類中,繼承關係都使用了 public 關鍵字來宣告。

1
class Rectangle: public Polygon { /* ... */ }

冒號(:)後面的 public 關鍵字指定了從其後類(本例中為 Polygon)繼承的成員在派生類(本例中為 Rectangle)中所能擁有的最寬鬆的訪問級別。由於 public 是最寬鬆的訪問級別,透過指定此關鍵字,派生類將以與基類中完全相同的訪問級別繼承所有成員。

如果使用 protected 繼承,基類的所有 public 成員在派生類中都會變成 protected。反之,如果指定了最嚴格的訪問級別(private),那麼所有基類成員都會被繼承為 private

例如,如果 daughter 是從 mother 派生的類,我們定義為:

1
class Daughter: protected Mother;

這將把 protected 設定為 Daughter 從 mother 繼承的成員的最寬鬆訪問級別。也就是說,所有在 Mother 中是 public 的成員,在 Daughter 中都會變成 protected。當然,這並不會限制 Daughter 宣告自己的 public 成員。這個“最寬鬆訪問級別”只針對從 Mother 繼承來的成員。

如果在繼承時沒有指定訪問級別,編譯器對用 class 關鍵字宣告的類預設使用 private 繼承,對用 struct 宣告的類預設使用 public 繼承。

實際上,C++ 中絕大多數繼承用例都應該使用 public 繼承。當需要對基類使用其他訪問級別時,通常將基類作為成員變數來表示會是更好的設計。

從基類繼承了什麼?

原則上,一個公有派生類繼承了基類中除以下之外的每個成員的訪問權:

  • 它的建構函式和解構函式
  • 它的賦值運算子成員(operator=)
  • 它的友元
  • 它的私有成員

儘管對基類的建構函式和解構函式的訪問權本身不會被繼承,但它們會被派生類的建構函式和解構函式自動呼叫。

除非另有說明,派生類的建構函式會呼叫其基類的預設建構函式(即不帶引數的建構函式)。呼叫基類的其他建構函式是可能的,使用的語法與在初始化列表中初始化成員變數的語法相同:

derived_constructor_name (parameters) : base_constructor_name (parameters) {...}

例如:

// constructors and derived classes
#include <iostream>
using namespace std;

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 kelly(0);
  Son bud(0);
  
  return 0;
}
Mother: no parameters
Daughter: int parameter

Mother: int parameter
Son: int parameter

請注意,當建立新的 Daughter 物件和 Son 物件時,呼叫的 Mother 建構函式是不同的。這種差異是由於 DaughterSon 的建構函式宣告不同所致。

1
2
Daughter (int a)          // nothing specified: call default constructor
Son (int a) : Mother (a)  // constructor specified: call this specific constructor 

多重繼承

一個類可以從多個類繼承,只需在類的基類列表(即冒號之後)中指定多個基類,並用逗號分隔。例如,如果程式中有一個專門用於在螢幕上列印的類叫做 Output,並且我們希望我們的 RectangleTriangle 類在繼承 Polygon 成員的同時,也繼承 Output 的成員,我們可以這樣寫:

1
2
class Rectangle: public Polygon, public Output;
class Triangle: public Polygon, public Output;

下面是完整的示例:

// multiple inheritance
#include <iostream>
using namespace std;

class Polygon {
  protected:
    int width, height;
  public:
    Polygon (int a, int b) : width(a), height(b) {}
};

class Output {
  public:
    static void print (int i);
};

void Output::print (int i) {
  cout << i << '\n';
}

class Rectangle: public Polygon, public Output {
  public:
    Rectangle (int a, int b) : Polygon(a,b) {}
    int area ()
      { return width*height; }
};

class Triangle: public Polygon, public Output {
  public:
    Triangle (int a, int b) : Polygon(a,b) {}
    int area ()
      { return width*height/2; }
};
  
int main () {
  Rectangle rect (4,5);
  Triangle trgl (4,5);
  rect.print (rect.area());
  Triangle::print (trgl.area());
  return 0;
}
20
10  
Index
目錄