友元

C++中友元分为:友元函数友元类

封装是面向对象的三个基本特征之一,C++中通过类的概念将功能封装在内部,只对外提供 api 接口。而友元提供了一种突破封装的方式,这有时提供了便利,但突破封装意味着耦合度的增加,所以友元需要“酌情使用”。

友元函数

我们知道,C++ 的运算符重载函数的参数中,有一个隐藏的 this指针,这通常代表了这个操作符的第一个操作数,例如下面这个例子:

class cls {
public:
    int operator+(int num) {}
}

加号有两个操作数,左操作数对应隐含的 this 指针,右操作数才对应参数列表中的 num 。但这带来了一个限制,即类对象必须是这个运算符的左操作数才能触发重载的运算符,例如

int main() {
    cls obj;
    
    cout << obj + 1 << endl;  // 正确,类对象在操作符左边
    cout << 1 + obj << endl;  // 错误,类对象在操作符右边
}

这个限制在大多数时候没什么关系,毕竟 1 + objobj + 1 没啥区别,但对于两个特殊的操作符来说,这就很严重了。

输入运算符 >> 和输出运算符 << 就不同寻常,他们的左操作数通常是该运算符要读取的流的引用,例如 cout << 123cin >> a 。所以如果你想重载你自己的类的输入输出运算符就很难办到。一个解决办法是声明一个全局的输入输出运算符重载函数,这样你就可以自由的控制参数的顺序,例如这样:

class cls {
public:
    int a;
};

ostream &operator<<(ostream &os, cls _obj) {
    os << _obj.a;
}

但这样的限制在于,全局的运算符重载函数没有 this 指针,所以不能访问私有属性,这就导致很难完成输入输出的任务,除非你像上面一样把属性定义成公有的,但这很明显不符合常理。

所以最终的解决方案就是友元函数友元函数可以让一个非成员函数访问类的私有成员

class cls {
friend ostream &operator<<(ostream &os, cls &obj);
friend istream &operator>>(istream &is, cls &obj);
public:
    cls(int _a, int _b) {
        a = _a;
        b = _b;
    }
private:
    int a, b;
};

ostream &operator<<(ostream &os, cls &obj) {
    os << obj.a << "-" << obj.b << endl;
    return os;
}

istream &operator>>(istream &is, cls &obj) {
    is >> obj.a >> obj.b;
    return is;
}

int main() {
    cls obj(1, 2);

    cout << obj;
    cin >> obj;
    cout << obj;

    return 0;
}

如代码所示,友元函数通过 friend 关键字指定,在这个类中通过 friend 关键字指定了两个友元函数,分别是输入运算符的重载和输出运算符的重载。则此时这两个重载函数虽然不是类的成员函数,但却可以访问类对象的私有成员。

友元函数有下面这些特性:

  • 友元函数可以访问类的私有成员但它不是类的成员函数
  • 友元函数不能用 const 修饰,原因显而易见,友元函数没有 this 指针。
  • 友元函数可以在类定义的任何地方声明,不受访问限定符限制
  • 一个函数可以是多个类的友元函数。

友元类

友元类的概念类似友元函数,假如一个类是另一个类的友元类,则相当于这个类中的所有成员函数都是另一个类的友元函数

class Date;  // 前置声明
class Time {
friend class Date;  // 声明 Date 类为 Time 类的友元类,则相当于 Date 类中所有的成员函数都是 Time 类的友元函数
public:
    Time(int hour, int minute, int second)
        : _hour(hour)
        , _minute(minute)
        , _second(second) {}
private:
    int _hour, _minute, _second;
};

class Date {
public:
    Date(int year = 1900, int month = 1, int day = 1) 
        : _year(year)
        , _month(month)
        , _minute(minute) {}
    void SetTimeOfDate(int hour, int minute, int second) {
        // 直接访问 Time 类的私有成员
        _t._hour = hour;
        _t._minute = minute;
        _t._second = second;
    }
private:
    int _year, _month, _day;
    Time _t;
}
  • 友元类是单向的,不具有交换性。上面的例子中,Date 类是 Time 类的友元类,则 Date 类中的函数可以直接访问 Time 类的私有成员。但 Time 类不是 Date 类的友元类。
  • 友元关系不能传递。如果 B 是 A 的友元类,C 是 B 的友元类,不代表 C 是 A 的友元类。
最后修改:2021 年 11 月 21 日
如果觉得我的文章对你有用,请随意赞赏