C++ 中的友元概念和使用方法

C++ 借用类的概念实现了封装的概念,使得对外提供接口,对内开放数据的想法得到了实现。但是如果在类外定义的函数需要访问类内的数据成员,并且类的数据成员一般会被设定为 private,因此就会被拒绝访问。此时就需要使用友元的概念。

友元

借由友元(友元函数和友元类),可以提高程序的运行效率(减少类型的安全性检查和调用的时间开销),但是某种程度上会破坏掉类的封装性。也就是说友元能够使原来不属于类的成员(友元函数和友元类),成为类的成员从而具备类成员的属性。

友元函数

  • 友元函数能够直接访问类的私有数据成员。
  • 是在类外定义的普通函数
  • 不属于任何类
  • 需要在类定义中声明,声明时在友元前加关键字 friend
  • 一个函数可以是多个类的友元函数,只是需要分别声明

定义

friend datatype funcname(argument);

 全局函数作友元函数

#include <iostream>
#include <math.h>

using namespace std;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    POINT(const POINT &obj)
    {
        this->x = obj.x;
        this->y = obj.y;
    }

    POINT & operator=(const POINT &obj)
    {
        if (this == &obj)
            return *this;
        this->x = obj.x;
        this->y = obj.y;
        return *this;
    }

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }
    friend double distance(POINT &p1,POINT &p2);

private:
    int x;
    int y;
};

double distance(POINT &p1,POINT &p2)
{
    return sqrt(pow((p1.x-p2.x),2)+pow((p1.y-p2.y),2));
}

int main()
{
    POINT p1(10,20);
    POINT p2(30,40);

    p1.display();
    p2.display();

    cout<<"The distance between p1 and p2 is "<<distance(p1,p2)<<endl;

    return 0;
}

结果为:

The x is 10,The y is 20
The x is 30,The y is 40
The distance between p1 and p2 is 28.2843

在上边的程序中,我们定义了函数 distance,该函数使用类 POINT 成员作为参数,因此需要将该函数声明为该类的友元。从上边的结果可以看出:

  • 友元函数最好在类内声明,类外定义。因为全局函数定义在类内部会显得有点奇怪。
  • 友元函数声明与定义分开书写时,定义前不需要加 friend 关键字

类成员函数作友元函数

#include <iostream>
#include <math.h>

using std::cout;
using std::endl;

class POINT;

class POINT3
{
public:
    POINT3(int x = 0,int y = 0,int z = 0):x(x),y(y),z(z){}

    POINT3(const POINT3 &obj)
    {
        this->x = obj.x;
        this->y = obj.y;
        this->z = obj.z;
    }

    POINT3 & operator=(const POINT3 &obj)
    {
        if (this == &obj)
            return *this;
        this->x = obj.x;
        this->y = obj.y;
        this->z = obj.z;
        return *this;
    }

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<","<<"The z is "<<this->z<<endl;
    }

    double distance(POINT3 &p)
    {
        return sqrt(pow((this->x-p.x),2)+pow((this->y-p.y),2)+pow((this->z-p.z),2));
    }

    double distance(POINT &p);
private:
    int x;
    int y;
    int z;
};

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    POINT(const POINT &obj)
    {
        this->x = obj.x;
        this->y = obj.y;
    }

    POINT & operator=(const POINT &obj)
    {
        if (this == &obj)
            return *this;
        this->x = obj.x;
        this->y = obj.y;
    }

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

    double distance(POINT &p)
    {
        return sqrt(pow((this->x-p.x),2)+pow((this->y-p.y),2));
    }

    friend double POINT3::distance(POINT &p);
private:
    int x;
    int y;
};

double POINT3::distance(POINT &p)
{
    return sqrt(pow((this->x-p.x),2)+pow((this->y-p.y),2));
}

int main()
{
    POINT p1(10,20);
    POINT p2(30,40);
    POINT3 p3(50,60,70);

    p1.display();
    p2.display();
    p3.display();

    cout<<"The distance between p1 and p2 is "<<p1.distance(p2)<<endl;
    cout<<"The distance between p1 and p3 is "<<p3.distance(p1)<<endl;

    return 0;
}

结果为:

The x is 10,The y is 20
The x is 30,The y is 40
The x is 50,The y is 60,The z is 70
The distance between p1 and p2 is 28.2843
The distance between p1 and p3 is 56.5685

上边的程序中,我们定义了一个二维点 POINT 的类和一个三维点 POINT3 的类,并且在 POINT3 重载了函数 distance,希望在参数为 POINT 对象或者 POINT3 对象的时候都能够给出点之间的距离。因此:

  • 需要在 POINT 中声明 POINT3 中的该函数为友元函数
  • 类定义 POINT3 中使用了 POINT,因此需要在定义 POINT3 之前先声明 POINT
  • 在 POINT3 中的函数 distance 使用了 POINT 对象的数据成员 x 和 y,因此只能先在类中声明该函数,函数实现放在类 POINT 定义之后
  • 友元函数声明与定义分开书写时,定义前不需要加 friend 关键字

上边先声明类 POINT 的方式叫做前向声明,形式为:

class classname;

前向声明是一种不完全的声明,只需要声明类名即可,因此:

  • 不能定义类的对象
  • 可以用于定义指向这个类型的指针或者引用
  • 用于声明,使用该类作为形参类型或者函数的返回值类型

 友元类

友元类说明该类中的所有成员函数都是另一个类的友元函数,都能够访问另一个类中的数据成员。

友元类可以算是友元函数的一个扩展,大大地增加了数据的单项流动,只是这种方式对于程序的封装形式也有极大的破坏。

定义

friend class classname;
  • 定义的友元类不属于函数成员,也不属于数据成员,而只是一种关系声明
  • 声明为友元类的类一定要在程序中定义过,定义先后顺序则无所谓
#include <iostream>
#include <math.h>

using std::cout;
using std::endl;

class POINT
{
public:
    POINT(int x = 0,int y = 0):x(x),y(y){}

    POINT(const POINT &obj)
    {
        this->x = obj.x;
        this->y = obj.y;
    }

    POINT & operator=(const POINT &obj)
    {
        if (this == &obj)
            return *this;
        this->x = obj.x;
        this->y = obj.y;
    }

    void display()
    {
        cout<<"The x is "<<this->x<<","<<"The y is "<<this->y<<endl;
    }

    double distance(POINT &p)
    {
        return sqrt(pow((this->x-p.x),2)+pow((this->y-p.y),2));
    }

private:
    int x;
    int y;
    friend class POINT3;
};

class POINT3
{
public:
    POINT3(int x = 0,int y = 0,int z = 0):z(z)
    {
        xy.x = x;
        xy.y = y;
    }

    POINT3(const POINT3 &obj)
    {
        this->xy.x = obj.xy.x ;
        this->xy.y = obj.xy.y ;
        this->z = obj.z;
    }

    POINT3 & operator=(const POINT3 &obj)
    {
        if (this == &obj)
            return *this;
        this->xy.x = obj.xy.x;
        this->xy.y = obj.xy.y;
        this->z = obj.z;
        return *this;
    }

    void display()
    {
        cout<<"The x is "<<this->xy.x<<","<<"The y is "<<this->xy.y<<","<<"The z is "<<this->z<<endl;
    }

    double distance(POINT3 &p)
    {
        return sqrt(pow((this->xy.x-p.xy.x),2)+pow((this->xy.y-p.xy.y),2)+pow((this->z-p.z),2));
    }

private:
    POINT xy;
    int z;
};

int main()
{
    POINT p1(10,20);
    POINT p2(30,40);
    POINT3 p3(50,60,70);
    POINT3 p4(80,90,100);

    p1.display();
    p2.display();
    p3.display();
    p4.display();

    cout<<"The distance between p1 and p2 is "<<p1.distance(p2)<<endl;
    cout<<"The distance between p3 and p4 is "<<p3.distance(p4)<<endl;

    return 0;
}

结果为:

The x is 10,The y is 20
The x is 30,The y is 40
The x is 50,The y is 60,The z is 70
The x is 80,The y is 90,The z is 100
The distance between p1 and p2 is 28.2843
The distance between p3 and p4 is 51.9615

上边的程序中,我们在 POINT 中声明 POINT3 为友元类,因此在 POINT3 中就能够使用 POINT,进而访问到 POINT 中的数据成员。

这种做法其实就是为了避开 POINT 数据成员是 private 的限制,如果 POINT 中的数据成员都是 public 的话,当然就不需要这么麻烦了,直接使用就可以了。

这种形式有点类似“继承”的意思,但含义却要比“继承”弱的多,POINT3 只是可以将 POINT 拿过来用,而不是能够完全继承 POINT 的属性,至少对于 POINT3 的对象来说,就不能直接调用 POINT 的函数,而只能在 POINT3 的成员函数中调用 POINT 的函数,因此只能是“朋友”,而不是“父子”。

注意事项

声明

  • 友元声明的关键字为 friend
  • 友元声明只能出现在类定义中
  • 声明的友元不属于函数成员,也不属于数据成员,而只是一种关系声明,因此不受 public、private、protected 关键字的影响

友元使用

  • 友元是为不属于类的函数或者类开放了一种数据访问的通道,使之能够绕过类中的权限设置,得到访问权限
  • 友元能够提到程序的运行效率,但是会破坏类的封装和数据隐藏

友元关系

  • 友元关系不能够被继承
  • 友元关系是单向的,不具有交换性。如果需要双向友元,就需要双向声明
  • 友元关系也不具有传递性。同样,只要声明过的才是“朋友”,否则就是“陌生人”
  • 这些关系特性也是为了在打破封装的情况下,尽可能的保存封装吧。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章