友元和继承

友元函数

原则上,一个类的私有(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
目录