126.友元函数在类内部声明还是内外?

126.友元函数在类内部声明还是内外?

1.什么是友元

  • 友元是一种授权机制:允许外部函数 / 其他类,访问某个类的 private /protected 成员。
  • 友元不是类的成员,只是被 “破例允许访问”。

2.友元函数

友元函数是 C++ 中一种打破类封装限制的机制,允许一个非成员函数访问类的 private 或 protected 成员。

2.1 核心概念

  • 友元函数不是类的成员函数,但被类授予特殊访问权限。
  • 它可以像普通函数一样调用,只是能访问类的私有 / 保护成员。
  • 友元关系是单向的:类 A 把函数 f 声明为友元,不代表 f 所在的作用域也能访问 A 的其他成员。

2.2 声明与定义

(1)声明位置

友元函数的声明必须放在类内部(可以在 public/private/protected 任意区段,位置不影响访问权限):

class MyClass {
private:
    int data;
public:
    MyClass(int d) : data(d) {}
    // 声明友元函数:可以访问 MyClass 的私有成员
    friend void printData(const MyClass& obj);
};

(2)定义位置

友元函数的定义必须放在类外部(全局作用域或命名空间内):

// 友元函数定义:可以直接访问 obj.data
void printData(const MyClass& obj) {
    std::cout << "Data: " << obj.data << std::endl;
}

(3)调用方式

和普通函数一样调用,不需要通过对象:

int main() {
    MyClass obj(42);
    printData(obj); // 直接调用友元函数
    return 0;
}

2.3常见使用场景

场景 1:重载流插入 / 提取运算符(operator<< / operator>>)

这是友元函数最经典的用途 —— 因为运算符重载必须是非成员函数才能让左操作数是流对象(如 std::cout)。

重载流插入 / 提取运算符定义成友元的原因

必须用友元,根本原因是:流运算符重载时,流对象(cout/cin)必须在左边,而 this 指针默认是左操作数,冲突了,所以只能写成非成员函数;写成非成员又要访问私有成员,就必须用友元。

  • 左操作数是流对象:ostream / istream
  • 右操作数是自定义类对象
class Point {
private:
    int x, y;
public:
    Point(int x_, int y_) : x(x_), y(y_) {}
    // 友元重载 << 运算符,用于输出 Point 对象
    friend std::ostream& operator<<(std::ostream& os, const Point& p);
};

std::ostream& operator<<(std::ostream& os, const Point& p) {
    os << "(" << p.x << ", " << p.y << ")";
    return os;
}

// 使用:
Point p(3, 4);
std::cout << p << std::endl; // 输出 (3, 4)

场景 2:两个类之间的协作函数

当两个类需要紧密交互时,友元函数可以避免暴露过多 public 接口:

class B; // 前向声明

class A {
private:
    int a;
public:
    A(int a_) : a(a_) {}
    friend int addAandB(const A& aObj, const B& bObj);
};

class B {
private:
    int b;
public:
    B(int b_) : b(b_) {}
    friend int addAandB(const A& aObj, const B& bObj);
};

// 友元函数可以同时访问 A 和 B 的私有成员
int addAandB(const A& aObj, const B& bObj) {
    return aObj.a + bObj.b;
}

场景 3:模板类的友元函数

模板类中声明友元函数需要注意语法:

template <typename T>
class MyTemplate {
private:
    T data;
public:
    MyTemplate(T d) : data(d) {}
    // 友元函数模板
    template <typename U>
    friend void printTemplate(const MyTemplate<U>& obj);
};

template <typename U>
void printTemplate(const MyTemplate<U>& obj) {
    std::cout << obj.data << std::endl;
}

2.4 关键特性与注意事项

(1)友元关系的特性

  • 单向性:类 A 把函数 f 声明为友元,f 能访问 A 的私有成员,但 A 不能访问 f 所在类的私有成员(除非 f 所在类也把 A 声明为友元)。
  • 不传递性:A 是 B 的友元,B 是 C 的友元 ≠ A 是 C 的友元。
  • 不继承性:子类不会继承父类的友元函数权限,父类的友元函数不能直接访问子类的私有成员。

(2)封装与友元的平衡

  • 友元函数破坏了类的封装性,应尽量少用。
  • 优先使用 public 接口,只有在必须访问私有成员(如运算符重载、紧密协作类)时才使用友元。

(3)声明与定义的顺序

  • 如果友元函数需要访问类的成员,类的定义必须在友元函数的定义之前。
  • 可以先在类内声明友元函数,再在类外定义。

2.5 友元函数 vs 成员函数

特性 友元函数 成员函数
是否属于类 ❌ 不是类的成员 ✅ 是类的成员
访问权限 ✅ 可访问类的 private/protected ✅ 可访问类的所有成员
调用方式 普通函数调用:func(obj) 对象调用:obj.func()或ptr->func()
this指针 ❌ 没有this指针 ✅ 隐含this指针
用途 运算符重载、跨类协作、特殊访问 类的核心行为与操作

2.6 一句话总结

友元函数是类的 “特殊访客”,不是成员却能访问私有成员;常用于运算符重载和跨类协作,需谨慎使用以避免破坏封装。

posted @ 2023-08-02 21:38  CodeMagicianT  阅读(748)  评论(0)    收藏  举报