news 2026/9/3 19:46:08

C++友元详解:友元函数与重载__运算符

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++友元详解:友元函数与重载__运算符

C++友元详解:友元函数与重载<<运算符

在C++中,友元(friend)是一种特殊的访问权限机制,它允许外部函数或类访问当前类的私有成员。今天我们就来深入探讨友元函数,并通过一个完整的示例演示如何使用友元函数重载运算符,特别是重载<<运算符来实现自定义对象的输出。


一、为什么需要友元?

通常情况下,类的私有成员只能通过公有成员函数访问,这种封装性保证了数据的安全性。但在某些场景下,这种限制显得过于严格,例如:

  • 重载双目运算符时,如果左侧操作数不是本类对象,就无法使用成员函数版本。
  • 希望将输出运算符<<重载,以便直接用cout << 对象的方式打印对象内容。

这时候,友元就派上了用场。


二、友元的三种形式

C++支持三种友元:

  1. 友元函数
  2. 友元类
  3. 友元成员函数

本文重点介绍友元函数,其他两种将在后续文章中讲解。


三、何时需要友元函数?

来看一个典型的例子:为Time类重载乘法运算符,使其支持Time对象乘以一个double值。

成员函数版本的问题

假设我们有如下成员函数:

Time Time::operator*(doublen)const;

那么下面的语句可以正常工作:

A=B*2.75;// 相当于 A = B.operator*(2.75)

但下面的语句却无法通过编译:

A=2.75*B;// 左侧不是Time对象,无法调用成员函数

因为左侧操作数必须是调用对象,而2.75不是Time类型。

非成员函数的思路

我们可以使用非成员函数来解决顺序问题:

Timeoperator*(doublem,constTime&t);

这样2.75 * B就会匹配到这个函数。但问题来了:非成员函数不能直接访问类的私有数据!

此时就需要友元函数——它既不是成员函数,却拥有和成员函数一样的访问权限。


四、创建友元函数

第一步:在类声明中添加原型,前面加上关键字friend

friendTimeoperator*(doublem,constTime&t);

注意:

  • friend只出现在类声明中的原型里。
  • 该函数不是成员函数,不能用成员运算符调用。
  • 但它与成员函数具有相同的访问权限。

第二步:编写函数定义(不加friend,也不加类限定符)

Timeoperator*(doublem,constTime&t){Time result;longtotalminutes=t.hours*m*60+t.minutes*m;result.hours=totalminutes/60;result.minutes=totalminutes%60;returnresult;}

现在就可以使用2.75 * B了。

更巧妙的实现

其实我们可以让友元函数直接调用成员函数版本,从而避免直接访问私有数据:

Timeoperator*(doublem,constTime&t){returnt*m;// 调用成员函数 operator*(double)}

这样既保持了友元关系(作为接口的一部分),又利用了已有的成员函数。


五、常用的友元:重载<<运算符

重载<<运算符是友元函数最经典的应用之一。我们希望实现:

cout<<trip;// trip是Time对象

为什么必须是友元?

如果使用成员函数重载<<,第一个操作数必须是Time对象,那就得写成:

trip<<cout;// 太反直觉了!

所以只能用非成员函数,而且通常需要访问私有数据,因此友元是理想选择。

第一个版本:输出内容

voidoperator<<(ostream&os,constTime&t){os<<t.hours<<" hours, "<<t.minutes<<" minutes";}

这样cout << trip;就能工作了。

第二个版本:支持链式输出

上面的版本无法实现类似cout << "Time: " << trip << endl;的链式输出,因为operator<<返回void。要支持链式,必须返回ostream对象的引用。

ostream&operator<<(ostream&os,constTime&t){os<<t.hours<<" hours, "<<t.minutes<<" minutes";returnos;}

现在就可以愉快地链式输出了:

cout<<"Trip time: "<<trip<<" (Tuesday)\n";

扩展到文件输出

由于ostream是基类,这个函数也能用于ofstream对象,将数据写入文件:

ofstreamfout("savetime.txt");fout<<trip;

六、完整代码示例

下面给出一个完整的Time类定义,包含友元函数重载*<<

mytime3.h

#ifndefMYTIME3_H_#defineMYTIME3_H_#include<iostream>classTime{private:inthours;intminutes;public:Time();Time(inth,intm=0);voidAddMin(intm);voidAddHr(inth);voidReset(inth=0,intm=0);Timeoperator+(constTime&t)const;Timeoperator-(constTime&t)const;Timeoperator*(doublen)const;// 友元函数:重载 * 以支持 double * TimefriendTimeoperator*(doublem,constTime&t){returnt*m;// 调用成员函数}// 友元函数:重载 <<friendstd::ostream&operator<<(std::ostream&os,constTime&t);};#endif

mytime3.cpp

#include"mytime3.h"Time::Time():hours(0),minutes(0){}Time::Time(inth,intm):hours(h),minutes(m){}voidTime::AddMin(intm){minutes+=m;hours+=minutes/60;minutes%=60;}voidTime::AddHr(inth){hours+=h;}voidTime::Reset(inth,intm){hours=h;minutes=m;}Time Time::operator+(constTime&t)const{Time sum;sum.minutes=minutes+t.minutes;sum.hours=hours+t.hours+sum.minutes/60;sum.minutes%=60;returnsum;}Time Time::operator-(constTime&t)const{Time diff;inttot1=t.minutes+60*t.hours;inttot2=minutes+60*hours;diff.minutes=(tot2-tot1)%60;diff.hours=(tot2-tot1)/60;returndiff;}Time Time::operator*(doublemult)const{Time result;longtotalminutes=hours*mult*60+minutes*mult;result.hours=totalminutes/60;result.minutes=totalminutes%60;returnresult;}std::ostream&operator<<(std::ostream&os,constTime&t){os<<t.hours<<" hours, "<<t.minutes<<" minutes";returnos;}

usetime3.cpp(测试程序)

#include<iostream>#include"mytime3.h"intmain(){usingstd::cout;usingstd::endl;Timeaida(3,35);Timetosca(2,48);Time temp;cout<<"Aida and Tosca:\n";cout<<aida<<"; "<<tosca<<endl;temp=aida+tosca;cout<<"Aida + Tosca: "<<temp<<endl;temp=aida*1.17;cout<<"Aida * 1.17: "<<temp<<endl;cout<<"10.0 * Tosca: "<<10.0*tosca<<endl;return0;}

运行结果

Aida and Tosca: 3 hours, 35 minutes; 2 hours, 48 minutes Aida + Tosca: 6 hours, 23 minutes Aida * 1.17: 4 hours, 11 minutes 10.0 * Tosca: 28 hours, 0 minutes

七、总结

  • 友元函数提供了成员函数与外部函数之间的桥梁,使得非成员函数也能访问类的私有成员。
  • 常用于重载运算符,特别是当左侧操作数不是本类对象时(如double * Time)或需要与标准输出流配合时(如重载<<)。
  • 友元函数不是成员函数,但在类中声明时需加friend关键字,定义时则不加。
  • 重载<<时应返回ostream&以支持链式输出,这是C++中非常实用的惯用法。

掌握友元,让你的C++类接口更加灵活、自然!


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/30 20:04:29

功率半导体(以 IGBT 为主)关键静态参数详解与正确测试方法

功率半导体(以 IGBT 为主)关键静态参数详解与正确测试方法 给出实际工程中最常用的测试方法(基于 JEDEC/IEC 标准、Keysight/Keysight B1505A、Tektronix 等设备实践)。所有参数均针对 IGBT(绝缘栅双极晶体管),而非普通 BJT。 1. VCE(sat) — 集电极-发射极饱和电压(…

作者头像 李华
网站建设 2026/8/30 23:41:57

PDF 专业电子签章工具 v5.2 丨高效 PDF 签章办公工具

PDF 专业电子签章工具 v5.2 是专为专业商务场景打造的高效办公工具&#xff0c;主打 PDF 电子签章与骑缝章制作核心功能&#xff0c;支持批量操作、模板保存和 300DPI 高质量输出&#xff0c;可智能适配矢量与扫描 PDF&#xff0c;轻松满足商务合同等专业签章的多样化办公需求&…

作者头像 李华
网站建设 2026/8/30 19:30:18

【毕业设计】SpringBoot+Vue+MySQL web多媒体素材管理系统平台源码+数据库+论文+部署文档

摘要 随着数字化时代的快速发展&#xff0c;多媒体素材的管理需求日益增长。传统的素材管理方式通常依赖于本地存储或简单的文件管理系统&#xff0c;这种方式不仅效率低下&#xff0c;还存在数据冗余、检索困难、安全性不足等问题。尤其是在教育、广告、媒体等行业中&#xff…

作者头像 李华
网站建设 2026/8/30 8:17:11

实测才敢推!9个AI论文平台测评:本科生毕业论文写作全攻略

在当前学术写作日益依赖AI工具的背景下&#xff0c;本科生群体面临着选题困难、文献整理繁琐、格式规范不熟等多重挑战。为了帮助同学们更高效地完成毕业论文&#xff0c;笔者基于2026年的实测数据与真实用户反馈&#xff0c;对市面上主流的AI论文平台进行了系统测评。本次评测…

作者头像 李华
网站建设 2026/8/30 8:17:13

在Python中如何定义函数?

在Python中&#xff0c;函数是组织好的&#xff0c;可重复使用的&#xff0c;用来实现单一&#xff0c;或相关联功能的代码块&#xff0c;它可以提高应用的模块性&#xff0c;和代码的重复利用率。那么Python如何定义一个函数?以下是具体内容介绍。在Python中定义函数有多种方…

作者头像 李华