友元关系

封装的目的是为了实现信息隐蔽,一个对象的私有成员只能通过自己的成员访问到,类外想要访问私有成员只能通过该类提供的公有成员间接地进行
但是上面这种方式通常是以函数调用的方式进行的,不方便并且开销大,因此,c++提供了友元的机制来打破私有化的界限,即一个类的友元可以访问该类的私有成员

友元函数

友元函数可以直接访问类对象的公有,私有成员
声明可以放在类的任何位置(哪一个段都没有区别),但是友元函数不属于类,不是该类的成员函数,因此一般的友元函数也没有this指针
调用时需要将对象作为参数传入友元函数内,调用方式和普通函数一样不需要通过对象,如:

Point类代表点(其中有点的坐标(x,y)的数据),需要一个函数计算任意两点间的距离

单从实现的角度来说并不复杂,首先只需要定义一个计算距离的函数,通过Point类的对象提供点的坐标,并在类外通过成员函数访问即可计算距离,或者也可以在类中定义一个成员函数,然后一个对象通过上面的方法接受另一个Point类对象提供的坐标,调用成员函数计算距离即可
但这两种法式显然不如友元快捷高效,并且计算距离应该是一个独立的功能,反映两点间的关系,并不属于某一单独的点也不属于Point类

class Point
{
    float x,y;
    friend float distance(Point a,Point b);  
public:
    Point(float a,float b):x(a),y(b){}
};
float distance(Point a,Point b)
{
    float dx,dy;
    dx=a.x-b.x;
    dy=a.x-b.y;
    return sqrt(dx*dx+dy*dy);
}
int main()
{
    Point point1(23,42),point2(46,34);
    cout<<distance(point1,point2)<<endl;      //输出结果:25.4951
}

虽然distance()看似定义在私有区,但实际上友元函数不属于类,在类外也可以调用(并且不需要指定作用域)
(如果要定义为类内的友元成员函数则需要指定作用域)

友元类

除将一个普通函数声明为一个类的友元函数外,也可在类X中声明另一个类Y为X的友元类
友元类的特点:

  • 友元的关系是单向的而不是双向的,不具有对称性,类Y为X的友元类不代表类X是Y的友元类
  • 友元的关系不能传递,不具有传递性,类Y是类X的友元类,类Z是类Y的友元类,不等于类Z是类X的友元类

要想达到上面两种目的都必须单独声明
友元类Y中的所有成员函数都是类X的友元函数,都能直接访问类X中所有成员,例:

class X
{
    int x;
    friend class Y;
private:
    X(int v):x(v){}
public:
    void show(){cout<<x;}
};
class Y
{
public:
    void set(int x,X &obj){obj.x=x;}
};
int main()
{
    X obj1(5);
    Y obj2;
    obj2.set(10,obj1);
    obj1.show();           输出结果:10
}

友元成员函数

友元函数虽然不是目标类的成员函数,但可以是另一个类的成员函数,注意在声明时需要加上当前类和作用域符,以说明是某个类的成员函数,如:

class X{…};class Y{…;friend void Y::SetX(X &obj,int v){}};

类的组合

一个类的对象作为另一个类的成员,体现了整体与部分的关系,即对象的包含关系
这个作为成员的对象被称为子对象,在初始化时,如果子对象对应的类的构造函数有参数,那么包含该子对象的类必须使用参数初始化表的方式先初始化子对象即先构造部分再构造整体
析构函数的调用顺序相反,先析构整体再析构部分 (实际上和基类派生类的构造方法是一致的,这一点在后面单继承和多继承、派生类构造析构规则中会详细说明)

注意顺序是组合类中各类对象的声明顺序(析构时为逆序)

例:使用点(Point)类构造一个园(Circle)类

class Point
{
    float x,y;
public:
    Point(int a,int b):x(a),y(b){}
    void MoveTo(float xx,float yy);
};
void Point::MoveTo(float xx,float yy)
{
    x=xx,y=yy;
}
class Circle
{
    float r;
    Point centre;
public:
    Circle(float a,float b,float c):r(a),centre(b,c){}     当然也可以直接用类或类引用/指针初始化,总之子对象完成初始化就可以
    void MoveTo(float xx,float y);
};
void Circle::MoveTo(float xx,float yy)
{
    centre.MoveTo(xx,yy);                        因为是通过成员访问符调用centre的MoveTo()函数,所以可以和Circle类的成员函数同名
}

通过类的组合可以在已有的抽象的基础上实现更复杂的抽象
类似模块化程序设计的思想,先实现各个子对象,再实现整体的组合类,自底向上完成复杂项目的实现

组合类示例程序:发动机类Motor,车门类Doors,车轮类Wheels,用这些类组合为汽车类Cars,并输出各个类的构造和析构顺序

#include<iostream>
using namespace std;
class Motor
{
public:
    Motor(){cout<<"构造Motor类"<<' ';}
    ~Motor(){cout<<"析构Motor类"<<' ';}
};
class Doors
{
public:
    Doors(){cout<<"构造Doors类"<<' ';}
    ~Doors(){cout<<"析构Doors类"<<' ';}
};
class Wheels
{
public:
    Wheels(){cout<<"构造Wheels类"<<' ';}
    ~Wheels(){cout<<"析构Wheels类"<<' ';}
};
class Cars
{
    Motor motor;
    Doors doors;
    Wheels wheels;
public
    Cars(){cout<<"构造Cars类"<<endl;}
    ~Cars(){cout<<"析构Cars类"<<' ';}
};
void display()
{
    Cars car;
}
int main()
{
    display();              //输出结果:构造Motor类 构造Doors类 构造Wheels类 构造Cars类
}                                   // 析构Cars类 析构Wheels类 析构Doors类 析构Motor类