重要的事情说三遍:
友元函数(Friend functions)
原则上,类的私有成员和保护成员不能从它们被声明的类之外访问。然而,这条规则对“友元”不适用。
友元是使用 friend
关键字声明的函数或类。
如果一个非成员函数被声明为某个类的友元函数,那么它可以访问该类的私有成员和保护成员。要实现这一点,只需在类中包含该外部函数的声明,并在前面加上 friend
关键字:
// 友元函数
#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
类的成员函数。它只是能够访问其私有和保护成员,而不是其成员函数。
友元函数的典型用例是执行涉及两个不同类的操作,并且需要访问这两个类的私有或保护成员。
友元类(Friend classes)
与友元函数类似,友元类是其成员能够访问另一个类的私有或保护成员的类:
// 友元类
#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
)替代。这个访问说明符限制了从基类继承的成员的最大可访问级别:具有更高可访问级别的成员将以这个级别继承,而具有相同或更严格访问级别的成员将保持其在派生类中的限制级别。
// 派生类
#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
类似。它的唯一不同之处在于继承:当一个类继承另一个类时,派生类的成员可以访问从基类继承的保护成员,而不能访问其私有成员。
通过将 width
和 height
声明为 protected
而不是 private
,这些成员也可以从派生类 Rectangle
和 Triangle
中访问,而不仅仅是从 Polygon
的成员中访问。如果它们是 public
,则可以从任何地方访问它们。
我们可以根据哪些函数可以访问它们来总结不同的访问类型,如下所示:
访问类型 | public | protected | private |
---|---|---|---|
同一类的成员 | 是 | 是 | 是 |
派生类的成员 | 是 | 是 | 否 |
非成员 | 是 | 否 | 否 |
“非成员”表示从类外部的任何访问,例如从 main
、从另一个类或从一个函数。
在上述例子中,Rectangle
和 Triangle
继承的成员具有与它们在基类 Polygon
中相同的访问权限:
Polygon::width // 保护访问
Rectangle::width // 保护访问
Polygon::set_values() // 公共访问
Rectangle::set_values() // 公共访问
这是因为在每个派生类上使用 public
关键字声明了继承关系:
class Rectangle: public Polygon { /* ... */ }
冒号(:
)后的 public
关键字表示从后面跟随的类(在这种情况下为 Polygon
)继承的成员将在派生类(在这种情况下为 Rectangle
)中具有的最大可访问级别。由于 public
是最高可访问级别,通过指定这个关键字,派生类将继承所有成员,并保持它们在基类中的访问级别。
使用 protected
时,基类的所有公共成员将作为 protected
成员在派生类中继承。相反,如果指定了最严格的访问级别(private
),则所有基类成员将作为 private
成员继承。
例如,如果 daughter
是从 mother
派生的类,我们定义为:
class Daughter: protected Mother;
这将设置 protected
作为 Daughter
从 Mother
继承的成员的最低限制访问级别。也就是说,所有在 Mother
中为 public
的成员将在 Daughter
中变为 protected
。当然,这不会限制 Daughter
声明其自己的公共成员。这个最低限制访问级别仅适用于从 Mother
继承的成员。
如果没有为继承指定访问级别,编译器会假定类使用 class
关键字声明时为 private
,使用 struct
关键字声明时为 public
。
实际上,大多数 C++ 中的继承使用案例应使用公共继承。当基类需要其他访问级别时,它们通常可以更好地表示为成员变量。
基类继承了什么?
原则上,公开派生类继承基类的每个成员,除了:
- 它的构造函数和析构函数
- 它的赋值操作符成员(operator=)
- 它的友元
- 它的私有成员
尽管基类的构造函数和析构函数的访问权限不是直接继承的,但它们会被派生类的构造函数和析构函数自动调用。
除非另有规定,派生类的构造函数调用基类的默认构造函数(即,不带参数的构造函数)。可以使用初始化列表的语法调用基类的不同构造函数:
derived_constructor_name (parameters) : base_constructor_name (parameters) {...}
例如:
// 构造函数和派生类
#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
对象和创建新的 Son
对象时分别调用了哪个 Mother
的构造函数。这种差异是由于 Daughter
和 Son
的不同构造函数声明造成的:
Daughter (int a) // 未指定任何内容:调用默认构造函数
Son (int a) : Mother (a) // 指定构造函数:调用这个特定构造函数
多重继承(Multiple inheritance)
一个类可以从多个类继承,只需在类的基类列表中指定多个基类,并用逗号分隔(即,冒号之后)。例如,如果程序中有一个专门用于在屏幕上打印的类 Output
,并且我们希望类 Rectangle
和 Triangle
也继承它的成员以及 Polygon
的成员,可以这样写:
class Rectangle: public Polygon, public Output;
class Triangle: public Polygon, public Output;
这是完整的示例:
// 多重继承
#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;
}