【cpluscplus教程翻译】友元和继承(Friendship and inheritance)

发布时间 2023-05-24 15:28:03作者: xiaoweing

友元函数(Friend functions)

原则上,private和protected成员不能在声明的类外被使用,然而这条规则不适用于友元
友元是用friend关键字声明的函数或者说类
如果一个非成员函数声明成一个类的友元,那么它可以访问private和protected。这可以通过在类里添加函数的声明实现,注意关键字

// 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&);
};

// 不是Rect类里的函数 没有用::操作符 注意与静态函数的差异
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;
}

因为duplicate函数是Rect的友元,因此duplicate可以访问Rect的private成员变量,不管是声明还是定义,或者是最后的使用,duplicate都没有被认为是Rect的成员,这是因为duplicate确实不是成员,它只是有权限而已。
友元函数的典型用例是在访问两个不同类的私有或受保护成员之间进行的操作。

友元类(Friend classes)

和友元函数类似,友元类可以访问另一个类的private和protected成员

// 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;
}

Rect是Square的友元类,因此Rect的成员函数可以访问Square的private成员,更准确地说,Rect访问的是变量Square::side(可以认为friend约等于using,和::差不多,把命名空间暴露出来
友元关系除非显式声明,否则不认为可以传播或相互,Rect是Square的友元,但是Square不是Rect的友元,当然可以加上友元声明。

继承(Inheritance between classes)

C++的类是可以被扩展的,创造新的类可以保留基础类的特点,在这个过程被称为继承,涉及基类和派生类的概念:派生类继承了基类的成员,在这基础上还可以有自己的成员。
例如,假设我们有一系列的类来描述两种形状:rect和tri,这两个多边形有一些通用属性,比如说都可以用宽和高描述
在类的世界里,可以描述成三个类,继承关系为

Polygon类可以包括两种形状的共同成员,width和height。Rect和Tri就是派生类,各自可以有不同的特点。
派生类继承了基类所有可以访问的成员,这个意思是如果基类有成员A,派生类有成员B,实际上派生类既有A又有B
声明派生类的语法为:class derived_class_name: public base_class_name { /*...*/ };
public可以替换成protected和private,这个权限限制符基类成员访问权限的最高等级,比这个权限更松的权限变成这个权限,比这个更紧的权限保持不变

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

class Polygon {
// private会编译不过 也可以写一个返回函数
  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;
}

注意Rectangle和Triangle对象的成员
注意Polygon使用的是protected权限,其实和private差不多,主要差别就是继承:当一个类继承另一个类时,派生类的成员可以访问从基类继承的受保护成员,但不能访问其私有成员。
通过将宽度和高度声明为protected而非private,这些成员也可以从派生类Rectangle和Triangle访问,而不仅仅是从Polygon的成员访问。如果它们是公共的,就可以从任何地方访问。
总结如下(这也是为什么会有三种权限,对应三类函数,友元也是为了改变,成员还可以考虑是否静态和常):

not members代表类外的所有地方,包括main函数、别的类
上面这个例子protected被保留权限

Polygon::width           // protected access
Rectangle::width         // protected access

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

这是因为继承关系是public继承class Rectangle: public Polygon { /* ... */ }
继承权限相当于min操作,注意只是对基类的成员进行设置
如果没有设置,如果用class默认是private,如果是struct默认是public
事实上,大部分继承用的都是public,如果需要别的访问权限,通常可以通过成员变量进行替换?

What is inherited from the base class?

原则上,public派生类会继承基类的所有成员,除了:

  • 构造函数和析构函数
  • 赋值运算符
  • 友元
  • 私有变量
    尽管构造函数和析构函数没有被继承,但是派生类的构造函数和析构函数会自动调用基类的构造和析构
    除非额外指定,派生类的构造函数会调用基类的默认构造函数,要想调用其他的构造函数也是可以的,可以在初始化列表进行初始话操作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;
}

请注意两个派生类构造函数调用了不同的基类构造函数,区别就是派生类的构造函数写法

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

多继承

一个类可以继承多个基类,用逗号分隔,例如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;
}

访问权限总结总结

记住两句话
1.类的一个特征就是封装,public和private作用就是实现这一目的。所以:
用户代码(类外)可以访问public成员而不能访问private成员;private成员只能由类成员(类内)和友元访问。
2.类的另一个特征就是继承,protected的作用就是实现这一目的。所以:
protected成员可以被派生类对象访问,不能被用户代码(类外)访问。
不管是否继承,上面的规则永远适用!有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。无论怎么继承,基类的private变量都不能被派生类访问,protected和public成员不会继承方式取min,但是都可以在派生类里访问,影响的是派生类外访问行为。