C++官方参考链接:Friendship and inheritance - C++ Tutorials (cplusplus.com)
友元和继承
友元函数
原则上,类的私有(private)成员和受保护(protected)成员不能从声明它们的同一个类的外部访问。然而,这条规则不适用于“友元”。
友元是用friend关键字声明的函数或类。
非成员函数可以访问类的私有(private)成员和受保(protected)成员,如果它被声明为类的友元。这是通过在类中包含这个外部函数的声明,并在它之前加上关键字friend来实现的:
// 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;
}
duplicate函数是Rectangle类的友元。因此,函数duplicate能够访问Rectangle类型的不同对象的成员width和height(它们是私有的)。请注意,无论是duplicate的声明还是其后在main中的使用,都没有将函数duplicate视为Rectangle类的成员。它不是!它只需访问其私有和受保护的成员,而无需成为成员。
友元函数的典型用例是在两个不同的类之间执行的操作,这些操作访问两个类的私有(private)成员或受保护(protected)成员。
友元类
类似于友元函数,友元类是一个类,其成员可以访问另一个类的私有(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;
}
在本例中,类Rectangle是类Square的友元,允许Rectangle的成员函数访问Square的私有成员和受保护成员。更具体地说,Rectangle访问成员变量Square::side,该变量描述正方形的边。
这个例子中还有一些新的东西:在程序的开始,类Square的声明是空的。这是必要的,因为类Rectangle使用Square(作为成员convert中的形参),而Square使用Rectangle(声明它为友元)。
除非指定,否则友元永远不会相互对应:在我们的示例中,Rectangle被Square视为友元类,但Square不被Rectangle视为友元类。因此,Rectangle的成员函数可以访问Square的受保护成员和私有成员,但反之则不行。当然,如果需要,Square也可以被声明为Rectangle的友元,授予这样的访问权限。
友元的另一个特点是它们不能传递:友元的友元不被认为是友元,除非明确指定。
类之间的继承
C++中的类可以扩展,创建保留基类特征的新类。这个过程称为继承,涉及一个基类和一个派生类:派生类继承基类的成员,在基类的基础上添加自己的成员。
例如,让我们想象有一系列的类来描述两种多边形:矩形和三角形。这两个多边形具有某些共同的属性,例如计算它们的面积所需的值:它们都可以简单地用高和宽(或基)来描述。
这可以在类的世界中用一个类Polygon来表示,我们将从中派生出另外两个类:Rectangle和Triangle:
Polygon类将包含对这两种类型的多边形都通用的成员。在我们的例子中:width和height。Rectangle和Triangle是它的派生类,具有不同类型多边形的特定特征。
派生自其他类的类继承基类的所有可访问成员。这意味着,如果基类包含成员A,而我们从它派生出一个带有另一个成员B的类,派生类将同时包含成员A和成员B。
两个类的继承关系在派生类中声明。派生类定义使用以下语法:
class derived_class_name: public base_class_name
{ /*...*/ };
其中derived_class_name是派生类的名称,base_class_name是它所基类的名称。public访问说明符可以由任何一个其他访问说明符(protected或private)替换。此访问说明符限制了从基类继承的成员的最高访问级别:具有更高访问级别的成员用此级别继承,而具有相同或更严格访问级别的成员将其限制级别保留在派生类中。
// 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;
}
Rectangle和Triangle类的对象都包含从Polygon继承的成员。它们是:width,height和set_values。
Polygon类中使用的protected访问说明符类似于private。它唯一的区别实际上发生在继承方面:当一个类继承另一个类时,派生类的成员可以访问从基类继承的protected成员,但不能访问其private成员。
通过将width和height声明为protected而非private,这些成员也可以从派生类Rectangle和Triangle中访问,而不仅仅是从Polygon的成员中访问。如果它们是public的,就可以从任何地方访问。
我们可以根据函数的访问方式总结不同的访问类型,如下所示:
Access(访问级别) | public | protected | private |
---|---|---|---|
members of the same class(同一个类的成员) | yes | yes | yes |
members of derived class(派生类的成员) | yes | yes | no |
not members(非成员) | yes | no | no |
其中“not members”表示来自类外部的任何访问,如来自main、来自另一个类或来自函数。
在上面的例子中,Rectangle和Triangle继承的成员具有与它们在基类Polygon中相同的访问权限:
Polygon::width // protected access
Rectangle::width // protected access
Polygon::set_values() // public access
Rectangle::set_values() // public access
这是因为继承关系已经在每个派生类上使用public关键字声明了:
class Rectangle: public Polygon { /* ... */ }
冒号后面的此public关键字 (:)表示从它后面的类(在本例中为 Polygon)继承的成员将从派生类(在本例中为Rectangle)继承的最高访问的级别。由于public是最高访问的级别,通过指定此关键字,派生类将继承所有具有与基类中相同级别的成员。
使用protected时,基类的所有公有成员都被继承为派生类中的protected成员。相反,如果指定了限制最大的访问级别(private),则所有基类成员都继承为private。
例如,如果女儿是从母亲派生的类,我们将其定义为:
class Daughter: protected Mother;
这将把protected设置为从Mother继承的Daughter成员的限制性较低的访问级别。也就是说,所有在Mother中public成员将在Daughter中变为protected。当然,这并不会限制Daughter声明自己的public的成员。限制较低的访问级别仅为继承自Mother的成员设置。
如果没有为继承指定访问级别,编译器假定用关键字class声明的类是private的,而用struct声明的类是public的。
实际上,C++中继承的大多数用例都应该使用公有继承。当基类需要其他访问级别时,它们通常可以更好地表示为成员变量。
从基类继承了什么?
原则上,公有派生类继承对基类的每个成员的访问权,除了:
*它的构造函数和析构函数
*它的赋值操作符成员(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;
}
请注意在创建一个新的Daughter对象时调用那个Mother的构造函数与当它是一个Son对象时调用哪个Mother的构造函数之间的区别。这种差异是由于Daughter和Son的构造函数声明不同造成的:
Daughter (int a) // nothing specified: call default constructor
Son (int a) : Mother (a) // constructor specified: call this specific constructor
多重继承
一个类可以从多个类继承,只需在类的基类列表中指定多个用逗号分隔的基类(例如,在冒号之后)。例如,如果程序有一个名为Output的特定类要打印到屏幕上,并且我们希望我们的类Rectangle和Triangle除了继承Polygon的成员外,也继承它的成员,那么我们可以这样写:
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;
}