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)
通过友元函数,复数类的运算符重载得以简洁实现,同时保持了类的封装性(real
和imag
仍为私有成员)。
总结
C++友元函数是一个强大但需谨慎使用的工具。它允许外部函数访问类的私有成员,适用于运算符重载、跨类协作等场景。然而,过度使用会破坏封装性,降低代码的可维护性。
在实际开发中,开发者应权衡利弊:
- 优先使用公有接口,仅在必要时引入友元函数。
- 明确友元函数的用途,确保其确实解决了设计上的瓶颈。
- 文档化友元关系,帮助团队理解代码的访问权限逻辑。
通过合理运用友元函数,开发者可以在保持类设计优雅的同时,实现灵活的功能扩展。