首页 > 编程笔记 > C++笔记 阅读:98

C++友元函数和友元类的用法(非常详细)

C++ 程序中,通过 public、protected和private 设置各个类成员的访问权限,可以有效实现数据和行为的封装,从而保护数据和行为的安全,防止外界的非法访问。

然而,这种严格的访问控制有时也会限制合理的访问需求。例如,某些函数或类可能需要访问类的私有或受保护成员,但由于它们不是类的成员或派生类,因此无法直接访问这些隐藏的信息。

在这些情况下,C++ 提供了一种灵活的解决方案—友元机制。利用 friend 关键字,我们可以将特定的函数或类声明为类的友元。一旦成为友元,这些函数或类就能够访问类的私有和受保护成员,即使它们不属于该类。

友元机制为严格的访问控制提供了一种例外,允许我们对值得信任的外部函数或类开放访问权限。这就像在现实世界中,我们可能会给予特定的人访问我们私人空间的权限一样。例如,一个非类成员的函数可能需要频繁地访问类的私有数据以进行计算;或者一个新的类可能因为业务逻辑的需要,必须访问另一个类的私有方法。

C++友元函数

友元函数实际上是一个定义在类外部的普通函数,它不属于任何类。

当使用 friend 关键字在类的定义中声明这个函数后,该函数就成为类的友元函数,之后可以不受类成员访问控制的限制,直接访问类的隐藏信息。

在类中声明友元函数的语法格式如下:
class 类名
{
    friend 返回值类型 函数名(形式参数列表);
    // 类的其他声明和定义
};
友元函数的声明与类的普通成员函数的声明类似,只不过在函数声明前加上了 friend 关键字进行修饰,并且函数定义在类的外部,并不属于这个类。

友元函数的声明不受访问控制的影响,既可以放在类的私有部分,也可以放在类的公有部分,两者没有区别。另外,一个函数可以同时是多个类的友元函数,只需在各个类中分别声明即可。

C++友元类

友元类是一种特殊的类,它定义在某个类之外,但通过使用 friend 关键字被赋予了访问该类私有成员或受保护成员的能力。这种机制允许友元类的所有成员函数访问原本受限的类成员,从而在保持封装性的同时提供必要的访问权限。

成为友元类之后,该类的所有成员函数都相当于成为友元函数,这意味着它们可以访问宿主类的私有和受保护数据。这种设计模式在某些情况下非常有用,但也需要谨慎使用,以避免破坏封装性。

在 C++ 中,声明友元类的语法格式如下:
class 类名
{
    friend class 友元类名;
    // 类的其他声明和定义
};
友元类的声明与友元函数的声明类似,这里不再赘述。需要注意的是两个类之间的相互关系。如果我们希望 A 类能够访问 B 类的隐藏信息,那么在 B 类中将 A 类声明为它的友元类即可。这表明 A 类被 B 类认证为值得信赖的“朋友”,从而能够访问 B 类的隐藏信息。

实例演示友元函数和友元类的用法

为了更好地理解友元的作用,下面来看一个实际的例子。

假设在定义的 Teacher 类中有一个成员变量 m_nSalary,记录了老师的工资信息。工资信息是个人隐私,因此需要保护。我们将它的访问控制级别设置为受保护,只有 Teacher 类自己及其派生类可以访问:
class Teacher
{
    // ...
    // 受保护的工资信息
protected:
    int m_nSalary;
};
将 m_nSalary 设置为受保护,可以很好地保护数据安全。但是,在某些特殊情况下,我们需要外界访问这个成员。

例如,税务局(用 TaxationDep 类表示)需要查老师的工资收入,自然有权访问 Teacher 类中 m_nSalary 这个受保护的成员;或者学校需要通过 AdjustSalary() 函数来调整老师的工资,老师自然乐意通过该函数来访问 m_nSalary 这个受保护的成员。

在这种情况下,我们可以把 TaxationDep 类和 AdjustSalary() 函数声明为 Teacher 类的友元,使它们能够访问 Teacher 类的隐藏信息:
// 拥有友元的 Teacher 类
class Teacher
{
    // 声明 TaxationDep 类为友元类
    friend class TaxationDep;
    // 声明 AdjustSalary() 函数为友元函数
    friend int AdjustSalary(Teacher* teacher);
    // 其他类的定义
protected:
    int m_nSalary; // 受保护的成员
};

// 在类的外部定义的友元函数
int AdjustSalary(Teacher* teacher)
{
    // 在 Teacher 类的友元函数中访问它的受保护成员 m_nSalary
    // 读取受保护的 m_nSalary,判断老师的工资是否低于 1000 元
    if (teacher != nullptr && teacher->m_nSalary < 1000)
    {
        // 修改受保护的 m_nSalary
        teacher->m_nSalary += 500; // 涨工资
    }
    return teacher->m_nSalary;
}

// 友元类
class TaxationDep
{
    // 类的其他定义
public:
    void CheckSalary(Teacher* teacher)
    {
        // 在 Teacher 类的友元类中,访问它的受保护成员 m_nSalary
        if (teacher != nullptr && teacher->m_nSalary > 1000)
        {
            cout << "这位老师应该交税" << endl;
        }
    }
};
可以看到,当 Teacher 类利用 friend 关键字将 AdjustSalary() 函数和 TaxationDep 类声明为它的友元之后,这些友元可以直接访问 Teacher 类中受保护的成员 m_nSalary。这就相当于为友元打开了一个“后门”,使其能够绕过访问控制这道保护墙,直接访问类的隐藏信息。

虽然友元可以带来一定的便利,但“开后门”毕竟不是一件“正大光明”的事情。因此,在使用友元时,应注意以下几点:

友元并不会破坏封装性

在友元函数或者友元类中,我们可以直接访问类的受保护或私有成员。虽然这种“后门”可能会引发对类隐藏信息泄露和封装性破坏的担忧,但实际上,合理使用友元不仅不会破坏封装性,反而会增强封装性。

在面向对象程序设计中,我们强调“高内聚、低耦合”的设计原则。当一个类的不同成员变量具有不同的生命周期时,为了保持类的“高内聚”,我们经常需要将这些具有不同生命周期的成员变量分割成两个类。在这种情况下,被分割的两个部分通常需要直接存取彼此的数据。实现这种需求的最安全方法是将这两个类声明为彼此的友元。

但是,一些开发者误认为友元破坏了类的封装性,因此倾向于通过提供公有的 get() 和 set() 成员函数来使这两个部分可以彼此访问对方的数据。实际上,这种做法反而破坏了封装性。在大多数情况下,这些 get() 和 set() 成员函数和公有数据一样糟糕:它们只隐藏了私有数据的名称,却没有隐藏对私有数据的访问。

友元机制允许我们只向必要的类公开己类的隐藏数据,例如 Teacher 类只是向 TaxationDep 类公开它的隐藏数据(只允许税务官查看工资数据)。这种方式比使用公有的 get()/set() 成员函数来让所有人都能访问工资要安全隐蔽得多。

相关文章