c++中对类的访问通过访问修饰符进行控制,类对象不能访问private
和protected
(子类中可以)修饰的变量和函数。有时候这种限制过于严格,因此,C++提供了另一种形式的访问权限——友元。
1.友元函数
通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。可以将友元理解为“类的好朋友”。简单来说,一个类的友元可以访问其私有属性和方法。
1.2.创建友元函数
- Step1.创建友元函数时,首先在类中定义友元函数的原型,定义时必须加关键字
friend
:
friend void func();
- Step2.在类的外部实现函数定义,在实现函数定义时,由于不是成员函数,因此不需要使用作用域解析符,同时不需要friend:
void func()
{
......
}
1.3.友元函数使用场景
当需要为类重载二元运算符时,就需要使用友元函数来实现。如常见的重载<<
运算符:
//Time2.h
#include <iostream>
class Time
{
//......
public:
friend std::ostream & operator<<(std::ostream &os,const Time & t);
};
//Time2.cpp
std::ostream & operator<<(std::ostream &os,const Time &t) {
os << t.hour << "h" << t.minute << "m" << std::endl;
return os;
}
2.友元类
除了让函数成为类的友元之外,还可以将类作为友元。
如果将A类声明为B类的友元类,则A类的所有方法都可以访问B类的私有成员和保护成员。
2.1.声明一个友元类
给一个类声明一个友元类时,使用friend
关键字,如:
class B
{
private:
int age;
friend class A;
};
class A
{
void set_age(B &b,int t){b.age = t;}
};
此时A类将成为B类的友元类,这也意味着B类中的所有私有成员都可以在A类中访问.
2.2.作用
友元类的所有方法都可以访问原始类的私有成员和保护成员。
2.3.友元成员函数
可以让一个类的成员函数称为另一个类的友元,但使用友元成员函数时,必须要注意类的声明和定义顺序。下面我们一步步来看如何声明定义。
首先按照传统方式:
class B
{
private:
int age;
//成员友元函数
friend void A::set_age(B &b,int t);
};
class A
{
void set_age(B &b,int t){b.age = t;}
};
然而,当编译到友元成员函数时,由于A类没有声明,因此编译失败。所以这种方式不可取,那么采用第二种方式,我们先声明A类:
class A
{
void set_age(B &b,int t){b.age = t;}
};
class B
{
private:
int age;
//成员友元函数
friend void A::set_age(B &b,int t);
};
当编译器执行到set_age()
函数时,由于没有定义B类,因此也将编译失败,所以这种方式也不可取。
对于这种场景,需要使用前向声明(Forward declaration).
class B;
class A
{
//void set_age(B &b,int t){b.age = t;}
void set_age(B &b,int t);
};
class B
{
private:
int age;
//成员友元函数
friend void A::set_age(B &b,int t);
};
inline void A::set_age(B &b,int t){b.age = t;}
从这个例子中也看出,如果需要继续使用内联函数,则需要在类声明完成后显式使用inline
关键字定义。由于B类使用了前向声明,但实际上还未声明,因此不能在友元函数中直接使用B类成员或函数,所以,set_age()
只进行声明,而将定义放在B类声明之后。
最后来对友元成员函数的设计进行下总结:
假设需要A类成员函数作为B类的友元:
- 1.首先前向声明B类;
- 2.其次声明A类,并声明成员函数,但不能定义成员函数;
- 3.接下来定义B类,包括对于A类成员函数的友元声明;
- 4.最后定义A类成员函数,此时他才可以使用B累的成员。
让整个类成为友元不需要前向声明,是因为友元语句本身已经指出它是一个类。
2.4.函数重载和友元
如果一个类想把一组重载函数声明为它的友元,则需要对这组函数中的每个函数分别声明。