【C++】友元--最全解析(友元是什么?我们应该如何理解友元?友元可以应用在那些场景?)

目录

一、前言

 二、友元是什么?

 三、友元的感性理解和分类

🥝友元的感性理解 

🍋友元的三种分类

✨友元 --- 全局函数

✨友元 --- 成员函数

✨友元 --- 类

 四、友元函数的应用场景 

🍍操作符重载 :"<<"  与  ">>"

五、友元的注意事项

🍋 友元函数的注意事项

🍇 友元类的注意事项

 六、友元的优缺点

🍓友元 -- 优点

🍉友元 -- 缺点

七、共勉 


一、前言

       C++编程语言中,友元函数(Friend Function)是一种特殊的函数,具有访问类中私有成员和保护成员的权限,尽管它不是类的成员函数。友元函数的存在使得类的设计更加灵活,能够在需要时授予外部函数访问类的私有成员的能力。本文将详细介绍C++中的友元函数,包括其定义、使用场景、优缺点以及示例

 二、友元是什么?

       1️⃣: 友元是在一个类中声明的一个非成员函数,但在类的内部声明该函数为友元。这意味着该函数可以访问该类的私有成员,包括私有变量和私有函数
      2️⃣: 友元的声明通常位于类的声明中,但其实现则位于类外部。 

 三、友元的感性理解和分类

🥝友元的感性理解 

    上述对友元的描述可能比较抽象,大家难以理解,我们可以通过一个生活小案例来感性理解一下

  • 在我们的日常生活中,假设大家都住在别墅社区里面, 在每栋别墅里面都是房间的,像客厅、卧室、厨房、洗手间,每家每户基本都有,我们可以将这些私人的房屋称为你的 ---- 私人区域

  • 那在一个小区中,除了挨家挨户的的私人领域外,一定会存在公共区域,在这些公共区域中,会有一些公共场所,例如像篮球场、咖啡馆、游泳馆、小卖部或是健身器材等等,我们可以将这些所有人都可以访问的地方称为 -----  公共区域

  • 所以 篮球场、健身区域就相当于 ---- 公共区域,大家都可以来玩,你的家就相当于 ----- 私人领域,只有你能进去。
  •  但是千防万防你都很难防住 ---- 隔壁老王 ---- 去你家

      所以在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术隔壁老王

  • 友元的目的:就是让一个函数或者类 访问另一个类中的私有成员
  • 友元的关键字friend 

 🍋友元的三种分类

 ✨友元 --- 全局函数

 首先,我们要定义一个社区类公共成员变量为----公共区域,私有成员变量为---家

// 创建一个社区的类
// 一个社区里面有大家可以一起活动的 公共区域 也有自己的私人空间 家
class Community
{public:// community 的构造函数 ,给成员变量 赋 初始值Community(){PublicAreas = "公共区域";home = "家";}string PublicAreas;  // 公共区域
private:string home;  // 家
};

然后定义一个全局函数 laoWang(),用来访问Community类中的私有成员

void laowang(Community& communtiy)
{// 访问了私人区域cout << "隔壁老王 全局函数 正在偷偷潜入 你家:(引用传递)" << communtiy.home << endl;// 访问了公有区域cout << "隔壁老王 全局函数 正在公共区域 打球:(引用传递)" << communtiy.PublicAreas << endl;
}

 此时我们来测试一个看是否可以成功

 此时就需要隔壁老王----友元函数出手啦

 关键代码

friend void laowang(Community& communtiy);

在Community类中声明友元函数,告诉编译器 laoWang 全局函数是 Community类 的好朋友,可以访问Community对象的私有成员

// 创建一个社区的类
// 一个社区里面有大家可以一起活动的 公共区域 也有自己的私人空间 家
class Community
{friend void laowang(Community& communtiy);public:// community 的构造函数 ,给成员变量 赋 初始值Community(){PublicAreas = "公共区域";home = "家";}string PublicAreas;  // 公共区域
private:string home;  // 家
};

下面给出全局函数做友元访问类的私有成员的完整示例代码

// 全局函数做--- 友元// 创建一个社区的类
// 一个社区里面有大家可以一起活动的 公共区域 也有自己的私人空间 家
class Community
{friend void laowang(Community& communtiy);public:// community 的构造函数 ,给成员变量 赋 初始值Community(){PublicAreas = "公共区域";home = "家";}string PublicAreas;  // 公共区域
private:string home;  // 家
};void laowang(Community& communtiy)
{// 访问了私人区域cout << "隔壁老王 全局函数 正在偷偷潜入 你家:(引用传递)" << communtiy.home << endl;// 访问了公有区域cout << "隔壁老王 全局函数 正在公共区域 打球:(引用传递)" << communtiy.PublicAreas << endl;
}int main()
{// 创建一个社区对象Community community;laowang(community);return 0;
}

✨友元 --- 成员函数

 我们首先声明一个Community类,防止在下面的Laowang类中,编译器不认识Community

//前置类的声明
class Community;

然后定义Laowang类,采用成员函数在类内声明,类外定义的方式

class Laowang
{
public:void visit();
private:};void Laowang::visit()
{Community _community;cout << "隔壁老王类正在访问:" << _community.PublicAreas << endl;cout << endl;cout << "隔壁老王类正在访问:" << _community.home << endl;
}

下面给出Community类的定义 

class Community
{
public:// 构造函数Community(){PublicAreas = "公共区域";home = "你家";}string PublicAreas;// 私有成员
private:string home;
};

 同样的,现在还没有声明友元,因此类中的成员函数还不能访问另一个类的私有成员

 此时就需要隔壁老王----友元函数出手啦

 关键代码 

friend void Laowang::visit();

在Community类中声明友元函数告诉编译器, Laowang 类中的 visit()函数 是Community类的好友,可以访问 Community类的私有区域(你家)

class Community
{// 告诉编译器, Laowang 类中的 visit()函数 是Community类的好友,可以访问 Community类的私有区域(你家)friend void Laowang::visit();
public:// 构造函数Community(){PublicAreas = "公共区域";home = "你家";}string PublicAreas;// 私有成员
private:string home;
};

 下面给出成员函数做友元访问类的私有成员的完整示例代码

// 成员函数 做 友元//前置类的声明
class Community;class Laowang
{
public:void visit();
private:};class Community
{// 告诉编译器, Laowang 类中的 visit()函数 是Community类的好友,可以访问 Community类的私有区域(你家)friend void Laowang::visit();
public:// 构造函数Community(){PublicAreas = "公共区域";home = "你家";}string PublicAreas;// 私有成员
private:string home;
};void Laowang::visit()
{Community _community;cout << "隔壁老王类正在访问:" << _community.PublicAreas << endl;cout << endl;cout << "隔壁老王类正在访问:" << _community.home << endl;
}int main()
{Laowang lw;lw.visit();return 0;
}

✨友元 --- 类

 我们首先声明一个Laowang类,防止在下面的Community 类中,编译器不认识Laowang类 

// 前置类的声明
class Laowang;

 然后定义 Community 类

class Community
{
public:// 创建 构造函数Community(){// 初始化赋值PublicAreas = "公共区域";home = "你家";}string PublicAreas;  // 公共区域private:string home;         // 家
};

 然后定义Laowang类,采用成员函数在类内声明,类内定义的方式。(采用成员函数在类内声明,类外定义也可以)

// 定义 一个老王类
class Laowang
{
public:// 构造函数Laowang(){}// 参观函数,用于老王进入你家-----访问 Community中的属性void visit(){cout << "隔壁老王类正在访问:" << _community.PublicAreas << endl;cout << endl;cout << "隔壁老王类正在访问:" << _community.home << endl;}private://自定义类Community _community;
};

 同样的,现在还没有声明友元,因此类中的成员函数还不能访问另一个类的私有成员

 此时就需要隔壁老王----友元函数出手啦

 关键代码

friend class Laowang;

 在Community类中声明友元函数告诉编译器, Laowang类是Community类的好朋友,可以访问Community类的私有成员(你家)

class Community
{// 告诉编译器,Laowang类是Community类的好朋友,可以访问Community类的私有成员(你家)friend class Laowang;public:// 创建 构造函数Community(){// 初始化赋值PublicAreas = "公共区域";home = "你家";}string PublicAreas;  // 公共区域private:string home;         // 家
};

下面给出 类 做友元访问类的私有成员的完整示例代码

// 类 做 友元// 前置类的声明
class Laowang;class Community
{// 告诉编译器,Laowang类是Community类的好朋友,可以访问Community类的私有成员(你家)friend class Laowang;public:// 创建 构造函数Community(){// 初始化赋值PublicAreas = "公共区域";home = "你家";}string PublicAreas;  // 公共区域private:string home;         // 家
};// 定义 一个老王类
class Laowang
{
public:// 构造函数Laowang(){}// 参观函数,用于老王进入你家-----访问 Community中的属性void visit(){cout << "隔壁老王类正在访问:" << _community.PublicAreas << endl;cout << endl;cout << "隔壁老王类正在访问:" << _community.home << endl;}private://自定义类Community _community;
};int main()
{// 创建一个 老王 的对象Laowang lw;lw.visit();return 0;
}

 四、友元函数的应用场景 

🍍操作符重载 :"<<"  与  ">>"

当需要重载类的操作符(如<<、>>、+、-等)时,友元函数可以访问私有成员,实现合适的操作。 

接下来,就写个自定义类型的<< 和 >>的重载来演示友元函数:

  • >> 流提取
  • << 流插入

C++里cout和cin是全局的对象包含在<iostream>的,cinistream类型对象coutostream类型 对象 

C++中,内置类型是直接支持cout流插入<<和cin流提取>>的,并且其可以自动识别类型。其原因是库里面已经把这些内置类型的给重载了: 

 而自定义类型就不能直接用>>或<<,因此,我们需要手写这两个的运算符重载。

❓问题:现在我们尝试去重载 operator<< ,然后发现我们没办法将 operator<< 重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this 指针默认是第一个参数也就是左操作数了。但是实际使用中 cout 需要是第一个形参对象,才能正常使用。所以我们要将 operator<< 重载成全局函数。但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理。

  •  我们拿 -- 日期类 -- 来举例说明
// 输出输入 流  <<  >>  
class Date
{//友元函数friend ostream& operator<<(ostream& out, const Date& d);//流插入 <<friend istream& operator>>(istream& in, Date& d);//流提取 >>public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
//流插入 <<..
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "-" << d._month << "-" << d._day << endl;return out;
}
//流提取 >>
istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}
int main()
{Date d1;cin >> d1;cout << endl;cout << "输出今天的日期:"<<d1;
}

五、友元的注意事项

🍋 友元函数的注意事项

1️⃣:友元函数可访问类的私有和保护成员,但不是类的成员函数

2️⃣:友元函数不能用const修饰

  •         因为友元函数只是一个全局函数,不属于类的成员函数,所以它没有隐藏的this指针,而const修饰的就是this指针,只有非静态的成员函数才能用const修饰

3️⃣:友元函数可以在类定义的任何地方声明,不受类访问限定符限制
4️⃣:一个函数可以是多个类的友元函数

  • 比如说一个函数需要访问多个类中的私有成员,那可以在那几个类中设置这个函数为他们的友元函数,这样就都可以访问了

🍇 友元类的注意事项

1️⃣:友元关系是单向的,不具有交换性

  • 比如上述Community类和Laowang类,在Community类中声明Laowang类为其友元类,那么可以在Laowang类中直接访问Community类的私有成员变量,但想在Community类中访问Laowang类中私有的成员变量则不行。

2️⃣:友元关系不能传递

  • 如果B是A的友元,C是B的友元,则不能说明C时A的友元。

 六、友元的优缺点

 🍓友元 -- 优点

1、访问私有成员:
主要作用是允许外部函数或类访问另一个类的私有成员,从而实现对类的细粒度控制。
2、操作符重载:
当需要重载类的操作符(如<<、>>、+、-等)时,友元函数可以访问私有成员,实现合适的操作。
3、提高效率:
在某些情况下,使用友元函数可以提高程序的执行效率,因为它可以直接访问类的私有成员,而不需要通过访问器函数(getter和setter)。

🍉友元 -- 缺点

 1、破坏封装性:
友元函数可以突破类的封装性,使得类的私有成员可以被外部函数直接访问,可能会降低代码的安全性和可维护性。(友元也破环了类的隐藏与封装,所以必须慎用 (牺牲安全,提高效率))
2、难以维护:
当程序变得复杂时,友元函数的使用可能会导致代码变得难以理解和维护。

3、友元不能 继承,交换,传递

七、共勉 

     以下就是我对 友元--最全解析 的理解,如果有不懂和发现问题的小伙伴,请在评论区说出来哦,同时我还会继续更新对 C++ 的理解,请持续关注我哦!!!   

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

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

相关文章

Oracle 可传输表空间(Transportable Tablespace)

在数据归档、备份、测试等场景&#xff0c;我们经常需要将数据从一个系统移动到另一个系统&#xff0c;一个较常用的方案是数据的导出/导入&#xff08;export/import&#xff09;&#xff0c;但是在数据量较大的场景&#xff0c;此方案可能比较耗时。而可传输表空间是一种以文…

在Linux系统中搜索当前路径及其子目录下所有PDF文件中是否包含特定字符串

目录标题 方法一&#xff1a;pdfgrep方法二&#xff1a;使用find和xargs与pdftotext&#xff08;将PDF转换为文本&#xff09;组合&#xff0c;然后用grep搜索 方法一&#xff1a;pdfgrep pdfgrep -ri "rockchip" .方法二&#xff1a;使用find和xargs与pdftotext&am…

消息队列选型(RabbitMq、RocketMq、Kafaka)

文章目录 前言RabbitMq优点缺点 RocketMq优点缺点 Kafaka优点缺点 总结 前言 当引入消息队列时&#xff0c;常见的选择包括ActiveMQ、Kafka、RabbitMQ和RocketMQ。然而&#xff0c;近年来&#xff0c;ActiveMQ的活跃度已经下降&#xff0c;很多公司已经不再使用这款消息队列中…

wangEditor集成Word导入(富文本编辑器)

wangEditor集成Word导入(富文本编辑器)&#xff0c;wangEditor – 支持word上传的富文本编辑器&#xff0c;WANGEDITOR实现WORD图片自动转存&#xff0c;JAVA中将WORD转换为HTML导入到WANGEDITOR编辑器中&#xff08;解决图片问题&#xff0c;样式&#xff0c;非常完美&#xf…

视频怎么做成二维码分享?微信扫码播放视频的操作技巧

随着互联网的快速发展&#xff0c;现在大家喜欢通过扫描二维码的方式来获取信息&#xff0c;比如现在通过扫码看视频就是很常见的一种方式。生成二维码的方式可以实现内容传递的简单化&#xff0c;而且可以有效的降低制作者的成本&#xff0c;提升用户的体验效果。 现在使用比…

【matlab 代码的python复现】 Matlab实现的滤波器设计实现与Python 的库函数相同实现Scipy

实现一个IIR滤波器的设计 背景 Matlab 设计的滤波器通常封装过于完整,虽然在DSP中能够实现更多功能的滤波器设计但是很难实现Python端口的实现。 我们以一段原始的生物电信号EEG信号进行处理。 EEG信号 1.信号获取 EEG信号通常通过头皮电极,经过多通道采样芯片采样,将获…

解决: 0x803f7001 在运行Microsoft Windows 非核心版本的计算机上,运行“ slui.exe 0x2a 0x803f7001 “以显示错误文本,激活win10步骤流程。

一. 解决 0x803F7001在运行Microsoft Windows非核心版本的计算机错误 首先&#xff0c;按下winR打开"运行",输入 regedit 后回车&#xff0c;打开注册表。   然后再注册表下输入地址HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\SoftwareProt…

代码随想录算法训练营DAY28|C++回溯算法Part.4|93.复原IP地址、78.子集、90.子集II

文章目录 93.复原IP地址思路确定非法的范围树形结构 伪代码 78.子集思路伪代码实现CPP代码 90.子集II思路CPP代码用used去重的办法用set去重的版本不使用used数组、set的版本 93.复原IP地址 力扣题目链接 文章讲解&#xff1a;93.复原IP地址 视频讲解&#xff1a;回溯算法如何分…

docker容器技术篇:安装与配置flannel

Docker安装与配置flannel flannel是什么&#xff1f; Flannel是CoreOS团队针对Kubernetes设计的一个网络规划服务&#xff0c;简单来说&#xff0c;它的功能是让集群中的不同节点主机创建的Docker容器都具有全集群唯一的虚拟IP地址&#xff1b;通俗的将就是通过flannel接管dock…

粤嵌—2024/4/22—两数之和 || - 输入有序数组

代码实现&#xff1a; 双指针 /*** Note: The returned array must be malloced, assume caller calls free().*/ int* twoSum(int *numbers, int numbersSize, int target, int *returnSize) {int *res malloc(sizeof(int) * 2);*returnSize 2;int i 0, j numbersSize - 1…

C++学习进阶版(二):与文件相关的函数用法

目录 1、读取文件的指定行 &#xff08;1&#xff09;main函数中直接读 &#xff08;2&#xff09;封装成函数 ① 无返回值类型 ② 直接返回读取的内容 2、求文件的行数 3、文件内容读取成一个字符串 1、读取文件的指定行 &#xff08;1&#xff09;main函数中直接读 …

如何选择全链路监控系统?CAT、SkyWalking、Pinpoint哪个更适合?

如果服务器上没有应用还会造成硬件瓶颈吗&#xff1f;显然是不会的&#xff0c;呈现出来的硬件瓶颈绝大多数是表象问题&#xff0c;我们往往需要在系统应用上寻找问题的根因。而寻找系统问题的根因&#xff0c;对于系统链路监控也是必不可少的&#xff0c; 前面我们也写了几篇…

光伏无人机吊装技术的优势及应用前景

近年来随着政府对光伏业务的不断宣传和推进&#xff0c;不少区域因地制宜引进了光伏发电项目&#xff0c;但是部分区域由于交通不便利&#xff0c;给材料运输工作带来了巨大的考验。目前&#xff0c;无人机吊装技术运输材料解决这一问题。 今天带大家看下光伏无人机吊装技术有…

STM32G030F6P6TR ST意法

STM32G030F6P6TR是ST(意法半导体)一款基于高性能ArmCortex-M032位RISC内核&#xff0c;工作频率高达64MHz的32位MCU微控制器。代理销售ST(意法半导体)全系列IC电子元器件-中芯巨能为您提供STM32G030F6P6TR(ST 32位MCU)引脚图及中文参数介绍等内容。 STM32G030F6P6TR的中文参数 …

Linux进程和任务管理

目录 一.程序和进程的关系 程序 进程 线程 线程与进程的区别 二.查看进程信息ps 方法一 常用选项 方法二 三.TOP动态查看进程信息 进程信息区各列解释 top常用命令 系统查看命令总结 查看进程信息pgrep 查看进程树pstree 四.控制进程 进程的启动方式 进程的前…

Java面试八股之重写一个对象的equals方法,需要考虑哪些问题

重写一个对象的equals方法&#xff0c;需要考虑哪些问题 遵守equals()约定&#xff1a; 重写equals()方法应遵循Object类中定义的equals()方法约定&#xff0c;即实现自反性&#xff08;x.equals(x)始终为true&#xff09;、对称性&#xff08;若x.equals(y)为true&#xff0…

【电控笔记5.5】psms规格参数

规格参数 转矩常数Kt与反电动势常数Ke Kt:没安培电流产生多少转矩量 Ke或Kb:每单位转速产生的反电动势电压 反电动势是梯形波,Kt=Ke;正弦波则不相等 q轴电流与反电动势都领先转子磁通链90

谷粒商城学习笔记

1.系统架构 2.环境准备 21.安装Linux 1.VirtualBox: https://download.virtualbox.org/virtualbox/6.0.10/VirtualBox-6.0.10-132072-Win.exe 2.安装 Vagrant 1).Vagrant 下载地址: https://releases.hashicorp.com/vagrant/2.2.5/vagrant_2.2.5_x86_64.msi https://www…

7.Prism框架之对话框服务

文章目录 一. 目标二. 技能介绍① 什么是Dialog?② Prism中Dialog的实现方式③ Dialog使用案例一 (修改器)④ Dialog使用案例2(异常显示窗口) 一. 目标 1. 什么是Dialog?2. 传统的Dialog如何实现?3. Prism中Dialog实现方式4. 使用Dialog实现一个异常信息弹出框 二. 技能介…

Proxyman Premium for Mac:网络调试利器,开发者首选!

Proxyman Premium for Mac是一款功能强大的网络调试和分析工具&#xff0c;专为开发者和测试人员打造。这款软件以其出色的性能和丰富的功能&#xff0c;帮助用户在网络开发和调试过程中更有效地分析和拦截网络请求&#xff0c;进行必要的修改和重发&#xff0c;从而进行更深度…