C++ 友元函数(手把手讲解)

更新时间:

💡一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论

  • 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...点击查看项目介绍 ;
  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;

截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观

什么是友元函数?

在C++面向对象编程中,友元函数(Friend Function)是一个特殊的函数,它被授予访问类的私有(private)和保护(protected)成员的权限。虽然友元函数不属于该类的成员函数,但它能突破类的访问权限限制,直接访问类的非公有成员。

可以将友元函数想象为一个被信任的外部访客:就像某位朋友拥有你家备用钥匙,能自由出入但需遵守使用规则一样,友元函数被类明确授权后,可以访问其内部数据。这种机制虽然强大,但也需要谨慎使用,以避免破坏类的封装性。

友元函数的基本语法

友元函数的声明必须在类的定义中使用friend关键字,并置于类的私有或公有部分(通常放在私有部分更合理,以强调其特殊性)。语法格式如下:

class Class_Name {  
private:  
    friend 返回类型 Function_Name(参数列表);  
    // 或者  
    friend class Another_Class; // 声明友元类  
};  

示例1:友元函数访问私有成员

#include <iostream>  
using namespace std;  

class Box {  
private:  
    double width;  
public:  
    void setWidth(double w) { width = w; }  
    friend double getPrivateWidth(Box box); // 声明友元函数  
};  

// 友元函数定义,位于类外部  
double getPrivateWidth(Box box) {  
    return box.width; // 直接访问私有成员  
}  

int main() {  
    Box box1;  
    box1.setWidth(10.0);  
    cout << "Width: " << getPrivateWidth(box1) << endl;  
    return 0;  
}  

在上述代码中,getPrivateWidth()函数被声明为Box类的友元,因此可以直接访问其私有成员width

友元函数的特性与限制

1. 非成员函数身份

友元函数不属于类的成员函数,因此它不拥有this指针,无法直接调用类的其他成员函数。

2. 权限范围

友元函数可以访问类的所有成员(私有、保护、公有),但不能直接修改类的静态成员变量,除非通过对象实例操作。

3. 友元关系不可继承

如果子类继承父类,友元关系不会被继承。例如,如果Parent类将函数foo()声明为友元,Child类不会自动获得该权限。

4. 友元函数的局限性

  • 无法访问私有静态成员:除非通过对象实例显式访问。
  • 无法重载友元函数:若多个友元函数名称相同但参数不同,需在类中单独声明。

友元函数的典型应用场景

尽管友元函数可能破坏封装性,但在某些情况下,它是解决问题的合理选择。以下是几个常见场景:

场景1:运算符重载

当需要重载运算符(如+==)且操作涉及两个不同类的对象时,友元函数可以访问两个类的私有成员。例如:

class Complex {  
private:  
    double real, imag;  
public:  
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}  
    friend Complex operator+(const Complex&, const Complex&);  
};  

Complex operator+(const Complex& c1, const Complex& c2) {  
    return Complex(c1.real + c2.real, c1.imag + c2.imag);  
}  

场景2:跨类协作

当两个类需要共享内部数据时,友元函数可避免频繁调用公有接口,提高代码简洁性。例如:

class Account {  
private:  
    double balance;  
    friend void Bank::updateInterest(Account&); // 声明友元类的成员函数  
};  

class Bank {  
public:  
    void updateInterest(Account& acc) {  
        acc.balance += acc.balance * 0.05; // 直接访问私有成员  
    }  
};  

场景3:输入输出操作

若需自定义输入输出流操作符(如<<>>),友元函数能直接访问类的私有成员:

class Date {  
private:  
    int day, month, year;  
public:  
    friend ostream& operator<<(ostream&, const Date&);  
};  

ostream& operator<<(ostream& os, const Date& d) {  
    os << d.day << "-" << d.month << "-" << d.year;  
    return os;  
}  

友元函数的注意事项与最佳实践

1. 慎用友元函数

友元函数会削弱类的封装性,因为它允许外部代码直接操作内部数据。建议仅在必要时使用,例如运算符重载或跨类协作等场景。

2. 替代方案优先

在可能的情况下,优先通过公有接口或成员函数实现功能。例如,若需访问私有成员,可以考虑:

  • 为私有成员提供公有访问器(Getter)和修改器(Setter)。
  • 将函数设计为类的成员函数(若逻辑属于该类)。

3. 友元类的使用

除了友元函数,C++还支持友元类(Friend Class)。友元类的所有成员函数都自动成为另一个类的友元。例如:

class A {  
private:  
    int secret;  
    friend class B; // 声明友元类  
};  

class B {  
public:  
    void revealSecret(A& a) {  
        cout << "Secret: " << a.secret << endl; // 合法访问  
    }  
};  

但需注意,友元类的权限范围更广,可能带来更高的风险。

4. 谨慎处理静态成员

友元函数不能直接访问类的私有静态成员,需通过对象实例或类名访问。例如:

class MyClass {  
private:  
    static int count;  
    friend void printCount(); // 声明友元函数  
};  

void printCount() {  
    cout << "Count: " << MyClass::count << endl; // 正确方式  
}  

实战案例:复数类与运算符重载

以下案例演示如何通过友元函数实现复数类的运算符重载:

#include <iostream>  
using namespace std;  

class Complex {  
private:  
    double real, imag;  
public:  
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}  
    friend Complex operator+(const Complex&, const Complex&);  
    friend Complex operator*(const Complex&, const Complex&);  
    friend ostream& operator<<(ostream&, const Complex&);  
};  

// 重载加法运算符  
Complex operator+(const Complex& c1, const Complex& c2) {  
    return Complex(c1.real + c2.real, c1.imag + c2.imag);  
}  

// 重载乘法运算符  
Complex operator*(const Complex& c1, const Complex& c2) {  
    return Complex(c1.real * c2.real - c1.imag * c2.imag,  
                   c1.real * c2.imag + c1.imag * c2.real);  
}  

// 重载输出运算符  
ostream& operator<<(ostream& os, const Complex& c) {  
    os << "(" << c.real << ", " << c.imag << "i)";  
    return os;  
}  

int main() {  
    Complex c1(3, 4), c2(1, 2);  
    Complex sum = c1 + c2;  
    Complex product = c1 * c2;  
    cout << "Sum: " << sum << endl;  
    cout << "Product: " << product << endl;  
    return 0;  
}  

输出结果:

Sum: (4, 6i)  
Product: (1, 10i)  

通过友元函数,复数类的运算符重载得以简洁实现,同时保持了类的封装性(realimag仍为私有成员)。

总结

C++友元函数是一个强大但需谨慎使用的工具。它允许外部函数访问类的私有成员,适用于运算符重载、跨类协作等场景。然而,过度使用会破坏封装性,降低代码的可维护性。

在实际开发中,开发者应权衡利弊:

  • 优先使用公有接口,仅在必要时引入友元函数。
  • 明确友元函数的用途,确保其确实解决了设计上的瓶颈。
  • 文档化友元关系,帮助团队理解代码的访问权限逻辑。

通过合理运用友元函数,开发者可以在保持类设计优雅的同时,实现灵活的功能扩展。

最新发布