【C++ | 友元(friend)】友元函数、友元类、友元成员函数详解及例子代码

news2025/2/27 22:56:20

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍 🍭
⏰发布时间⏰:

本文未经允许,不得转发!!!

目录

  • 🎄一、概述
  • 🎄二、友元函数
  • 🎄三、友元类
  • 🎄四、友元成员函数
  • 🎄五、友元的其他关系
    • ✨5.1 让两个类互为友元
    • ✨5.2 共同友元
  • 🎄六、总结



在这里插入图片描述

🎄一、概述

一般来说,访问私有类成员的唯一方法是使用类方法。C++使用友元(friend)来避开这种限制。

C++的友元是为了解决这样的问题:有时需要类外部的函数来访问私有成员。这个问题在学习C++运算符时就会遇到。

本文主要介绍C++的三种友元实现,以及了解怎样编写自己的友元:

  • 友元函数;
  • 友元类;
  • 友元成员函数。

在这里插入图片描述

🎄二、友元函数

友元函数:一般是在类内声明为友元(friend)的全局函数。声明后,该函数可以访问类的私有成员。

为什么需要友元函数?
在实现类的二元运算符时,大部分情况可以将运算符函数写成类的成员函数,然后让类的对象作为左操作数去调用该运算符函数,如:CB = CA + 1;。但是,如果需要实现等式CB = 1 + CA,就无法调用该函数,而且这种形式也没法用成员函数去实现。这时就需要在类外部实现该函数,而且该函数还需要访问类的私有成员,而这样的函数只有该类的友元函数。关于这段描述不清楚的可以看下面举例的代码。

怎样声明、定义友元函数?
我们以CDate类为例:
1、将友元函数的原型放在类声明中,并在原型声明前加上关键字 friend
2、编写友元函数定义,因为它不是类的成员函数,所以不需要加类名作用域。

class CDate
{
	friend CDate operator+(int day, const CDate &date);	// 友元函数声明
	...
};
CDate operator+(int day, const CDate &date)	// 友元函数定义
{
	CDate temp = date;
	temp.m_day += day;
	cout << "Calling operator+(int, CDate)" << ", temp=" << &temp << endl;
	return temp;
}

友元函数例子完整代码

// g++ 18_friend_fun.cpp 
#include <iostream>
using namespace std;

class CDate
{
	friend CDate operator+(int day, const CDate &date);	// 友元函数声明
public:
	CDate(int year, int mon, int day);	// 构造函数声明
	CDate(const CDate& date);			// 拷贝构造函数声明
	CDate operator+(int day);			// 加号运算符声明

private:
	int m_year;
	int m_mon;
	int m_day;
};

// 构造函数定义
CDate::CDate(int year, int mon, int day)
{
	m_year = year;
	m_mon = mon;
	m_day = day;
	cout << "Calling Constructor" << ", this=" << this <<endl;
}

// 拷贝构造函数定义
CDate::CDate(const CDate& date)
{
	m_year = date.m_year;
	m_mon = date.m_mon;
	m_day = date.m_day;
	cout << "Calling Copy Constructor" << ", this=" << this << ", Copy Data" <<endl;
}

// 加号运算符定义
CDate CDate::operator+(int day)
{
	CDate temp = *this;
	temp.m_day += day;
	cout << "Calling operator+(int)" << ", this=" << &temp << endl;
	return temp;
}

// 友元函数定义
CDate operator+(int day, const CDate &date)
{
	CDate temp = date;
	temp.m_day += day;
	cout << "Calling operator+(int, CDate)" << ", temp=" << &temp << endl;
	return temp;
}

int main()
{
	CDate date(2024,6,17);
	CDate CB = CA + 1;
	CB = 1 + CA;	// 如果没有实现友元函数,则这句报错
	return 0;
}

运行结果如下,可以看到分别调用了operator+(int)operator+(int, CDate) 函数。
在这里插入图片描述


在这里插入图片描述

🎄三、友元类

友元类:一般是在类内声明为友元(friend)的类。声明后,友元类的所有成员函数函数都可以访问类的私有成员。

什么时候需要定义友元类?
假如程序要定义一个 空调类(CAirCond) 和一个 遥控器类(CRemote),这两个类存在一定的关系,但空调和遥控器显然不是继承的关系。而遥控器又可以改变空调的状态,也就是说 遥控器类 可以访问 空调类 的私有成员。这时就需要将 遥控器类 声明为 空调类 的友元类。

怎样声明、定义友元类?
1、在类中使用关键字 friend声明友元类;
2、编写友元类声明和定义;

class CAirCond	// 空调
{
	friend class CRemote; // 声明友元类
	...
};
class CRemote	// 遥控器
{
	...
};

友元类例子完整代码

// g++ 18_friend_class.cpp 
#include <iostream>
using namespace std;

class CAirCond	// 空调
{
	friend class CRemote; // 声明友元类
public:
	enum{OFF, ON};
	CAirCond(){state=OFF; temperature=26;}
	void setTemperature(int temp){temperature = temp;}
	void show()
	{
		cout << "air: state=" << (state==ON?"ON":"OFF") << ", temperature=" << temperature << endl;
	}
private:
	int state;	// 开关状态
	int temperature;// 温度
};

class CRemote	// 遥控器
{
public:
	CRemote(int mode=0){m_mode=mode;}
	void AirCondOn(CAirCond &air){air.state = CAirCond::ON;}
	void AirCondOff(CAirCond &air){air.state = CAirCond::OFF;}
	void setTemperature(CAirCond &air, int temp){air.setTemperature(temp);}
private:
	int m_mode;	// 0-制冷、1-制热
};

int main()
{
	CAirCond airConditioner;
	airConditioner.show();
	cout << endl;
	
	CRemote remote;
	remote.AirCondOn(airConditioner);
	airConditioner.show();
	cout << endl;
	
	remote.setTemperature(airConditioner,23);
	airConditioner.show();
	cout << endl;
	
	remote.AirCondOff(airConditioner);
	airConditioner.show();
	cout << endl;
	return 0;
}

运行结果如下:
在这里插入图片描述


在这里插入图片描述

🎄四、友元成员函数

友元成员函数:一般是在类内声明为友元(friend)的其他类的成员函数。声明后,友元成员函数可以访问类的私有成员。

什么时候需要定义友元成员函数?
如果某个类只有一两个成员函数需要访问本类的私有成员,可以只是将这一两个成员函数声明为本类的友元成员函数,而不用声明整个类为友元。例如,上个小节的 CRemote类 只有两个成员函数会访问 CAirCond类 的私有成员,可以只是声明这两个成员函数为 CRemote类 的友元。待会会给出这样操作的例子代码。

怎样声明、定义友元成员函数?
友元成员函数的声明、定义会有些复杂,下面以上个小节的 CAirCond 类、CRemote类 为例,分三步说明:

  • 1、在 CAirCond类 中使用关键字 friend声明友元成员函数,需要加上类名作用域CRemote::
    class CAirCond	// 空调
    {
    	friend void CRemote::AirCondOn(CAirCond &air); // 声明友元成员函数
    	friend void CRemote::AirCondOff(CAirCond &air);
    	...
    };
    
  • 2、将友元成员函数所属类(CRemote)的完整声明写在本类(CAirCond)的前面,因为使用了友元成员函数所属类的成员,所以需要其完整声明前置,否则会报错。
    并且 CRemote类 完整声明里不能使用 CAirCond 的成员,否则又需要将 CAirCond 类的完整声明放到 CRemote 类前面,会造成无解的循环,所以只能将上个小节在 CRemote 类声明的一些内联函数移动到类外去实现;
    class CRemote	// 遥控器
    {
    public:
    	CRemote(int mode=0){m_mode=mode;}
    	void AirCondOn(CAirCond &air);
    	void AirCondOff(CAirCond &air);
    	void setTemperature(CAirCond &air, int temp);
    private:
    	int m_mode;	// 0-制冷、1-制热
    };
    class CAirCond	// 空调
    {
    	friend void CRemote::AirCondOn(CAirCond &air); // 声明友元成员函数
    	friend void CRemote::AirCondOff(CAirCond &air);
    	...
    };
    
  • 3、将 CAirCond 类声明放在友元成员函数所属类的前面,因为所属类 CRemote 用到了 CAirCond 引用的参数:
    class CAirCond;
    class CRemote	// 遥控器
    {
    public:
    	CRemote(int mode=0){m_mode=mode;}
    	void AirCondOn(CAirCond &air);
    	void AirCondOff(CAirCond &air);
    	void setTemperature(CAirCond &air, int temp);
    private:
    	int m_mode;	// 0-制冷、1-制热
    };
    class CAirCond	// 空调
    {
    	friend void CRemote::AirCondOn(CAirCond &air); // 声明友元成员函数
    	friend void CRemote::AirCondOff(CAirCond &air);
    	...
    };
    

友元类例子完整代码

// g++ 18_friend_member_fun.cpp 
#include <iostream>
using namespace std;

class CAirCond;

class CRemote	// 遥控器
{
public:
	CRemote(int mode=0){m_mode=mode;}
	void AirCondOn(CAirCond &air);
	void AirCondOff(CAirCond &air);
	void setTemperature(CAirCond &air, int temp);
private:
	int m_mode;	// 0-制冷、1-制热
};

class CAirCond	// 空调
{
	friend void CRemote::AirCondOn(CAirCond &air); // 声明友元成员函数
	friend void CRemote::AirCondOff(CAirCond &air);
public:
	enum{OFF, ON};
	CAirCond(){state=OFF; temperature=26;}
	void setTemperature(int temp){temperature = temp;}
	void show()
	{
		cout << "air: state=" << (state==ON?"ON":"OFF") << ", temperature=" << temperature << endl;
	}
private:
	int state;	// 开关状态
	int temperature;// 温度
};

inline void CRemote::AirCondOn(CAirCond &air){air.state = CAirCond::ON;}
inline void CRemote::AirCondOff(CAirCond &air){air.state = CAirCond::OFF;}
inline void CRemote::setTemperature(CAirCond &air, int temp){air.setTemperature(temp);}

int main()
{
	CAirCond airConditioner;
	airConditioner.show();
	cout << endl;
	
	CRemote remote;
	remote.AirCondOn(airConditioner);
	airConditioner.show();
	cout << endl;
	
	remote.setTemperature(airConditioner,23);
	airConditioner.show();
	cout << endl;
	
	remote.AirCondOff(airConditioner);
	airConditioner.show();
	cout << endl;
	return 0;
}

运行结果如下:
在这里插入图片描述


在这里插入图片描述

🎄五、友元的其他关系

✨5.1 让两个类互为友元

有时候 类A 需要访问 类B 的私有成员,而 类B 也需要访问 类A 的私有成员,这时可以让这两个类互相成为对方的友元类。我们修改友元那个例子的代码如下,让 CAirCond类、CRemote类 互为友元:

// g++ 18_friend_class_each_other.cpp 
#include <iostream>
using namespace std;
class CRemote;
class CAirCond	// 空调
{
	friend class CRemote; // 声明友元类
public:
	enum{OFF, ON};
	CAirCond(){state=OFF; temperature=26;}
	void setTemperature(int temp){temperature = temp;}
	void show()
	{
		cout << "air: state=" << (state==ON?"ON":"OFF") << ", temperature=" << temperature << endl;
	}
	void setRemoteMode(CRemote &remote, int mode);
private:
	int state;	// 开关状态
	int temperature;// 温度
};

class CRemote	// 遥控器
{
	friend class CAirCond;
public:
	CRemote(int mode=0){m_mode=mode;}
	void AirCondOn(CAirCond &air){air.state = CAirCond::ON;}
	void AirCondOff(CAirCond &air){air.state = CAirCond::OFF;}
	void setTemperature(CAirCond &air, int temp){air.setTemperature(temp);}
private:
	int m_mode;	// 0-制冷、1-制热
};

void CAirCond::setRemoteMode(CRemote &remote, int mode){remote.m_mode=mode;}

int main()
{
	CAirCond airConditioner;
	airConditioner.show();
	cout << endl;
	
	CRemote remote;
	remote.AirCondOn(airConditioner);
	airConditioner.show();
	cout << endl;
	
	remote.setTemperature(airConditioner,23);
	airConditioner.show();
	cout << endl;
	
	remote.AirCondOff(airConditioner);
	airConditioner.show();
	cout << endl;
	return 0;
}

✨5.2 共同友元

需要使用友元的另一种情况是,函数需要访问两个类的私有数据。从逻辑上看,这样的函数应是每个类的成员函数,但这是不可能的。它可以是一个类的成员,同时是另一个类的友元,但有时将函数作为两个类的友元更合理。下面使用伪代码举例:

class A{
	friend void ChangeAB(CA &a, CB &b);
	...
}
class B{
	friend void ChangeAB(CA &a, CB &b);
	...
}
void ChangeAB(CA &a, CB &b)
{
	...
}

在这里插入图片描述

🎄六、总结

本文介绍了C++的友元函数、友元类、友元成员函数、其他友元关系,以及使用例子介绍了如何声明、定义、使用。

关于C++的友元又几个注意点:

  • 1、友元的声明仅仅指定了访问的权限, 而非一个通常意义上的函数声明。
  • 2、友元声明只能出现在类定义的内部,但是在类内出现的具休位置不限。一般,最好在类定义开始或结束前的位置集中声明友元。
  • 3、如果类中使用到其他类的成员,则需要将被使用的类的完整声明前置。
  • 4、友元不能被继承。
    在这里插入图片描述
    如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1841433.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

「iOS」UI——无限轮播图实现与UIPageControl运用

「OC」UI 文章目录 「OC」UI无限轮播图的实现以及UIPageControl的实际运用明确要求简单滚动视图的实现UIPageControl的实现设置NSTimer实现自动移动补充实现 进行无限滚动视图的修改思路实现 完整代码展示 无限轮播图的实现以及UIPageControl的实际运用 明确要求 我们要实现一…

LabVIEW与数字孪生

LabVIEW与数字孪生技术在工业自动化、智慧城市、医疗设备和航空航天等领域应用广泛&#xff0c;具备实时数据监控、虚拟仿真和优化决策等特点。开发过程中需注意数据准确性、系统集成和网络安全问题&#xff0c;以确保数字孪生模型的可靠性和有效性。 经典应用&#xff1a;LabV…

算法人生(23):跟着“生成对抗网络”思维走出“拖延”

生成对抗网络&#xff08;GANs&#xff09;是一种深度学习模型&#xff0c;其核心思想是通过两个神经网络——生成器和判别器的对抗过程来学习数据分布&#xff0c;进而生成新的、类似真实数据的样本。它基本原理基于一个博弈论框架&#xff0c;其中生成器尝试生成尽可能逼真的…

Solkane 冷媒性能计算软件-管路计算

下载 制冷管道设计 制冷管路的压降会降低制冷量&#xff0c;增大功耗。但不同部分的管路允许的压降的数量级是不同的。 制冷管路的压降不是唯一的考虑因素&#xff0c;制冷剂的流速往往比压降更重要。 制冷系统中&#xff0c;压缩机、阀、汽液分离器或其他附件上的连接件的尺…

地下管线管网三维建模系统MagicPipe3D

地下管网是保障城市运行的基础设施和“生命线”。随着实景三维中国建设的推进&#xff0c;构建地下管网三维模型与地上融合的数字孪生场景&#xff0c;对于提升智慧城市管理至关重要&#xff01;针对现有三维管线建模数据差异大、建模交互弱、模型效果差、缺乏语义信息等缺陷&a…

swagger下载文件名中文乱码、swagger导出文件名乱码、swagger文件导出名称乱码、解决swagger中文下载乱码bug

文章目录 一、场景描述&#xff1a;swagger导出文件名称乱码二、乱码原因三、解决方法3.1、方法一、在浏览器中输入地址下载3.2、方法二、swagger升级为2.10.0及以上 四、可能遇到的问题4.1、DocumentationPluginsManager.java:152 一、场景描述&#xff1a;swagger导出文件名称…

时间复杂度的相关概念

1. 统计时间增长趋势 时间复杂度分析统计的不是算法运行时间&#xff0c;而是算法运行时间随着数据量变大时的增长趋势&#xff0c;也就是算法运行时间与输入数据的关系。 // 算法 A 的时间复杂度&#xff1a;常数阶 function algorithm_A(n) {console.log(0); } // 算法 B 的…

反激开关电源EMI电路选型及计算

EMI &#xff1a;开关电源对电网或者其他电子产品的干扰 EMI &#xff1a;传导与辐射 共模电感的滤波电路&#xff0c;La和Lb就是共模电感线圈。这两个线圈绕在同一铁芯上&#xff0c;匝数和相位都相 同(绕制反向)。 这样&#xff0c;当电路中的正常电流&#xff08;差模&…

快速搭建Jenkins自动化集成cicd工具

一、简介 jenkins是一款优秀的自动化持续集成运维工具&#xff0c;可以极大的简化运维部署的步骤。 传统的项目部署需要手动更换最新的项目代码&#xff0c;然后打包并运行到服务器上。 使用Jenkins可以自动化实现&#xff0c;当代码编写完成并提交到git后&#xff0c;Jenki…

[C++][数据结构][图][下][最短路径]详细讲解

目录 1.最短路径1.单源最短路径 -- Dijkstra算法2.单源最短路径 -- Bellman-Ford算法3.多源最短路径 -- Floyd-Warshall算法原理 1.最短路径 最短路径问题&#xff1a;从在带权有向图G中的某一顶点出发&#xff0c;找出一条通往另一顶点的最短路径&#xff0c;最短也就是沿路径…

linux中“PXE高效批量装机”

在大规模的 Linux 应用环境中&#xff0c;如 Web 群集、分布式计算等&#xff0c;服务器往往并不配备光驱设备&#xff0c;在这种情况下&#xff0c;如何为数十乃至上百台服务器裸机快速安装系统呢&#xff1f;传统的 USB光驱、移动硬盘等安装方法显然已经难以满足需求。 PXE …

Javase.抽象类和接口

抽象类和接口 【本节目标】1.抽象类1.1抽象类的概念1.2 抽象类语法1.3 抽象类特性1.4 抽象类的作用 2. 接口2.1 接口的概念2.2 语法规则2.3 接口使用2.4 接口特性2.5 实现多个接口2.6 接口间的继承2.7 接口使用实例2.8Clonable 接口和深拷贝2.9 抽象类和接口的区别 3. Object类…

C#的Switch语句2(case后的值与模式匹配)

文章目录 switch语法结构case具体的值枚举值字符串const关键字 如果没有匹配的值default语句不一定要在最后 模式匹配与C的差异-case穿透&#xff08;Fall-through&#xff09;下一篇文章 switch语法结构 基础的语法结构&#xff0c;在上一篇文章已经写了&#xff0c;具体请看…

Pyshark——安装、解析pcap文件

1、简介 PyShark是一个用于网络数据包捕获和分析的Python库&#xff0c;基于著名的网络协议分析工具Wireshark和其背后的libpcap/tshark库。它提供了一种便捷的方式来处理网络流量&#xff0c;适用于需要进行网络监控、调试和研究的场景。以下是PyShark的一些关键特性和使用方…

顺势而为:雷军、小米与创业成功的深层思考

一、引言 在当今快速发展的商业环境中&#xff0c;成功的企业家如马云和雷军&#xff0c;都以其独特的商业智慧和不懈的勤奋精神赢得了业界的尊重。然而&#xff0c;当我们深入探讨他们的成功之道时&#xff0c;会发现一个更为核心的因素——“顺势而为”。本文将基于雷军对不…

HTML静态网页成品作业(HTML+CSS+JS)——我的家乡福州介绍网页(3个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;使用Javacsript代码实现图片轮播&#xff0c;共有3个页面。 二、作品…

Linux mongodb安装及简单使用

说明&#xff1a;本文章主要是对mongodb的单击安装 1.创建文件夹&#xff0c;准备安装包 cd /user/local mkdir tools 2.解压mongodb包 mkdir mongodb tar -xvf mongodb-linux-x86_64-rhel70-5.0.11.tgz -C mongodb 3.进入解压目录 cd mongodb cd mongodb-linux-x86_64-…

spark 整合 yarn

spark 整合 yarn 1、在master节点上停止spark集群 cd /usr/local/soft/spark-2.4.5/sbin ./stop-all.sh 2、spark整合yarn只需要在一个节点整合, 可以删除node1 和node2中所有的spark文件 分别在node1、node2 的/usr/local/soft目录运行 rm -rf spark-2.4.…

千脑计划:模拟人类大脑皮层,开启AI新纪元

随着科技的飞速发展&#xff0c;人工智能已成为当今时代的热门话题。然而&#xff0c;目前主流的深度神经网络虽然取得了显著成就&#xff0c;但也面临着能耗高、稳定性差等问题。为了解决这些挑战&#xff0c;一项名为“千脑计划”的宏伟项目应运而生&#xff0c;旨在通过模仿…

Nacos配置中心不可用会有什么影响

服务端&#xff1a; Nacos的数据存储接口 com.alibaba.nacos.config.server.service.DataSourceService 有两种实现&#xff1a; 如果指定了mysq 作为数据库&#xff0c;则必须使用 mysql 如果是 集群方式部署Nacos&#xff0c;则必须使用mysql 如果是单例方式部署 并且 没…