首页 > 编程笔记

C++友元函数和友元类(入门必读)

C++ 是面向对象的编程语言,封装是 C++ 重要的特性之一,用于隐藏对象的内部实现细节。然而在某些情况下,我们可能需要突破封装的界限,本节要讲的友元函数和友元类就是这样一种机制。

所谓友元(friend),简单理解就是允许特定的非成员函数访问一个类的私有成员(private 和 protected 修饰的成员)。友元机制允许一个类将其非公有成员的访问权限授予指定的函数或类。

友元的声明以关键字 friend 开始,它只能出现在类定义的内部。友元声明可以出现在类中的任何地方,并且友元不受其声明出现部分的访问控制的影响。

友元函数

友元函数不是类的成员函数,但它可以访问类的私有成员。

举个简单的例子:
#include <iostream>

class Circle {
private:
    double radius;

public:
    Circle(double r) : radius(r) {}

    // 声明友元函数
    friend double calculateArea(Circle c);
};

// 友元函数的实现
double calculateArea(Circle c) {
    return 3.14159 * c.radius * c.radius;
}

int main() {
    Circle circle(5);
    std::cout << "Area of circle: " << calculateArea(circle) << std::endl;

    return 0;
}
运行结果为:

Area of circle: 78.5397

在这个例子中,calculateArea() 函数虽然不是 Circle 类的成员,但第 11 行将它指定为 Circle 类的友元,所以它可以访问 Circle 类的私有成员 radius。

友元类

除了普通函数,类和类之间也可以指定友元关系。如果一个类需要访问另一个类的私有成员,可以使用友元类。

举个简单的例子:
#include <iostream>

class Rectangle;

class Square {
private:
    int side;

public:
    Square(int s) : side(s) {}

    // 声明友元类
    friend class Rectangle;
};

class Rectangle {
private:
    int width, height;

public:
    Rectangle(Square s) {
        width = s.side;  // 可以访问 Square 类的私有成员
        height = s.side;
    }

    int area() {
        return width * height;
    }
};

int main() {
    Square square(4);
    Rectangle rectangle(square);

    std::cout << "Area of rectangle: " << rectangle.area() << std::endl;

    return 0;
}
运行结果为:

Area of rectangle: 16

在这个例子中,Rectangle 类是 Square 类的友元,那么 Rectangle 类中的所有成员函数都可以访问 Square 类的私有成员。

当然,如果不需要类中所有的成员函数都具有这个特权,可以只为类中的某几个函数设置友元关系。

例如修改上面的程序,只将 Rectangle 类中的 area() 成员函数设置为 Square 的友元,即将第 13 行代码修改为:
friend int Rectangle::area();
此时会发现,编译器提示 Rectangle 类的构造函数无法访问 Square 类的私有成员。

完整实例

定义了一个简单的类 A,A 有一个私有的成员 id。Manager 类中的 adjustID() 成员函数和运算符 << 通过友元声明来访问 A 的私有成员。
#include <iostream>

// 前置声明
class A;

// 定义 Manager 类
class Manager {
public:
    void adjustID(A& a);
};

// 定义 class A
class A {
private:
    int id;
public:
    // 声明 Manager类中的 adjustID() 成员函数为友元函数
    friend void Manager::adjustID(A& a);

    // 声明 << 运算符为友元函数
    friend std::ostream& operator<<(std::ostream& out, const A& a);
};

void Manager::adjustID(A& a) {
    static int index = 0;
    a.id = index;
    index++;
}

// 重载 << 运算符
std::ostream& operator<<(std::ostream& out, const A& a) {
    out << "ID : " << a.id << std::endl;
    return out;
}

int main() {
    A a1, a2, a3, a4;
    Manager mgr;
    mgr.adjustID(a1);
    mgr.adjustID(a2);
    mgr.adjustID(a3);
    mgr.adjustID(a4);
    std::cout << a1 << a2 << a3 << a4;
    return 0;
}
运行结果为:

ID : 0
ID : 1
ID : 2
ID : 3

示例中,A 类的成员 id 是 private 限制的,因此正常情况下外部是不能访问这个成员的。为了让 Manager类中的 adjustID() 成员函数和操作符 << 具有访问权限,在定义 A 类的时候增加了友元声明。

总结

友元函数和友元类提供了一种方式,使得某些特定的外部函数或类能够访问类的私有或保护成员。虽然这破坏了封装的原则,但在某些特定场合下,这是必要的。

推荐阅读