【030】C++类和对象之友元(friend)详解

news2024/11/8 7:41:28

C++类和对象之友元(friend)详解

  • 引言
  • 一、友元概述
  • 二、友元的语法
  • 三、友元的应用举例
    • 3.1、普通全局函数作为类的友元
    • 3.2、类的某个成员函数作为另一个类的友元
    • 3.3、整个类作为另一个类的友元
  • 四、友元的注意事项
  • 五、友元案例
  • 总结

引言


💡 作者简介:专注于C/C++高性能程序设计和开发,理论与代码实践结合,让世界没有难学的技术。包括C/C++、Linux、MySQL、Redis、TCP/IP、协程、网络编程等。
👉
🎖️ CSDN实力新星,社区专家博主
👉
🔔 专栏介绍:从零到c++精通的学习之路。内容包括C++基础编程、中级编程、高级编程;掌握各个知识点。
👉
🔔 专栏地址:C++从零开始到精通
👉
🔔 博客主页:https://blog.csdn.net/Long_xu


🔔 上一篇:【029】C++静态成员和 this 指针详解

一、友元概述

C++中,友元(friend)是一种机制,它允许一个类的非成员函数或另一个类访问该类的私有成员。友元可以在类定义中声明,在类定义外部实现。

类的主要特点之一是数据隐藏,即类的私有成员无法在类的外部作用域之外访问。但是有时候需要在类的外部访问类的私有成员,因此出现了友元函数;友元函数是一种特权函数,允许访问私有成员。可以把一个全局函数、或者类的成员函数、甚至整个类声明为友元。

需要注意的是,友元机制破坏了类的封装性,因此应该谨慎使用。如果过度使用友元机制,会导致代码难以维护和扩展。

二、友元的语法

使用friend关键字声明为友元。声明一个友元函数或友元类的语法如下:

(1)友元函数的声明语法。

class ClassName {
private:
    // 私有成员
public:
    friend ReturnType FunctionName(ParameterList); // 声明友元函数
};

其中,ClassName为当前类名,ReturnType为友元函数的返回类型,FunctionName为友元函数名,ParameterList为参数列表。

(2)友元类的声明语法。

class ClassName1 {
private:
    // 私有成员
public:
    friend class ClassName2; // 声明友元类
};

其中,ClassName1为当前类名,ClassName2为被声明为友元的类名。

需要注意的是,在程序中定义和实现了一个被声明为该类的友元的全局函数或其他类中的成员函数时,在该函数前需要加上 friend ClassName::FunctionName() 来指定这个函数是属于哪个类。例如:

class MyClass {
private:
    int privateMember;

public:
    friend void friendFunction(MyClass obj);
};

void friendFunction(MyClass obj) {
    cout << "The value of private member is: " << obj.privateMember;
}

int main() {
    MyClass myObj;
    
    myObj.privateMember = 10;
    
    friendFunction(myObj); // 调用友元函数
    
    return 0;
}

如果将 friendFunction() 函数定义放在 MyClass 类外面,则需要使用以下语法:

void friend MyClass::friendFunction(MyClass obj) { ... }

三、友元的应用举例

3.1、普通全局函数作为类的友元

#include <iostream>
#include <string>
using namespace std;
class Room {
	friend int visiting(Room &room);
private:
	string bedRoom;//私有
public:
	string setingRoom;//公共
public:

	Room(string bedRoom, string setingRoom)
	{
		this->bedRoom = bedRoom;
		this->setingRoom = setingRoom;
	}
};

// 普通全局函数
int visiting(Room &room)
{
	cout << room.setingRoom << endl;
	cout << room.bedRoom << endl;//如果没有设置友元则无法访问
	return 0;
}

int main()
{
	Room room("Bed", "seting");
	visiting(room);
	return 0;
}

3.2、类的某个成员函数作为另一个类的友元

#include <iostream>
#include <string>
using namespace std;

class Room;// 向前声明,只能说明类名称

class Good {
public:
	int visiting01(Room &room);
	int visiting02(Room &room);
};
class Room {
	friend int Good::visiting02(Room &room);
private:
	string bedRoom;//私有
public:
	string setingRoom;//公共
public:

	Room(string bedRoom, string setingRoom)
	{
		this->bedRoom = bedRoom;
		this->setingRoom = setingRoom;
	}
};

// 成员函数
int Good::visiting01(Room &room)
{
	cout << room.setingRoom << endl;
	//cout << room.bedRoom << endl;// error
	return 0;
}

int Good::visiting02(Room &room)
{
	cout << room.setingRoom << endl;
	cout << room.bedRoom << endl;//如果没有设置友元则无法访问
	return 0;
}

int main()
{
	Room room("Bed", "seting");
	Good good;
	good.visiting01(room);
	good.visiting02(room);
	return 0;
}

3.3、整个类作为另一个类的友元

#include <iostream>
#include <string>
using namespace std;

class Room;// 向前声明,只能说明类名称
class Good {
public:
	int visiting01(Room &room);
	int visiting02(Room &room);
};
class Room {
	friend class Good;
private:
	string bedRoom;//私有
public:
	string setingRoom;//公共
public:

	Room(string bedRoom, string setingRoom)
	{
		this->bedRoom = bedRoom;
		this->setingRoom = setingRoom;
	}
};

// 成员函数
int Good::visiting01(Room &room)
{
	cout << room.setingRoom << endl;
	cout << room.bedRoom << endl;// OK
	return 0;
}

int Good::visiting02(Room &room)
{
	cout << room.setingRoom << endl;
	cout << room.bedRoom << endl;//如果没有设置友元则无法访问
	return 0;
}

int main()
{
	Room room("Bed", "seting");
	Good good;
	good.visiting01(room);
	good.visiting02(room);
	return 0;
}

四、友元的注意事项

  1. 友元函数可以访问类的私有成员和保护成员,因此需要谨慎使用。过度使用友元函数可能会破坏类的封装性,导致代码难以维护。

  2. 友元关系不能被继承。即使是派生类也不能访问基类中声明为友元的函数或类。

  3. 友元关系是单向的。如果A是B的友元,那么B并不一定是A的友元。

  4. 在定义友元函数时,需要在函数名前加上关键字“friend”,同时在类定义中声明该函数。

  5. 友元关系不具备传递性。即如果A是B的友元,B是C的友元,并不意味着A也是C的友元。

  6. 可以将一个类声明为另一个类的友元,这样就可以让该类中所有成员都能够访问另一个类中的私有成员和保护成员。

  7. 如果一个函数需要访问多个类中的私有成员和保护成员,可以将其声明为这些类中任意一个类的友元即可。

五、友元案例

设计一个电视机类,电视机属性有:

  • 开机和关机;
  • 音量;
  • 频道;
  • 操作音量的方法;
  • 操作频道的方法。

电视机只能逐一调整频道,不能指定频道。添加遥控类,遥控类除了拥有电视机 已有功能,再添加根据输入调台功能。

将遥控器类作为电视机类的友元类。

class TV;

class Remote {
private:
	TV *p;
public:
	Remote(TV *p)
	{
		this->p = p;
	}
	void onOrOff();
	void upVolume();
	void downVolume();
	void upChannel();
	void downChannel();
	void showTV();
	void setChannel(int num);
};


class TV {
	friend class Remote;
	enum{OFF,ON};
	enum{minVol,maxVol=100};
	enum{minChannel,maxChannel=28};
private:
	int status;
	int volume;
	int channel;
public:
	TV()
	{
		status = OFF;
		volume = minVol;
		channel = minChannel;
	}
	void onOrOff();
	void upVolume();
	void downVolume();
	void upChannel();
	void downChannel();
	void showTV();
};

void TV::onOrOff()
{
	status = (status == OFF ? ON : OFF);
}

void TV::upVolume()
{
	if (volume >= maxVol)
	{
		cout << "音量已经最大啦" << endl;
		return;
	}
	volume++;
}
void TV::downVolume()
{
	if (volume < minVol)
	{
		cout << "音量已经最小啦" << endl;
		return;
	}
	volume--;
}

void TV::upChannel()
{
	if (channel >= maxChannel)
	{
		cout << "频道已经最大啦" << endl;
		return;
	}
	channel++;
}
void TV::downChannel()
{
	if (channel < minChannel)
	{
		cout << "频道已经最小啦" << endl;
		return;
	}
	channel--;
}

void TV::showTV()
{
	cout << "电视机状态:" << (status == OFF ? "关" : "开") << endl;
	cout << "电视频道:" << channel << endl;
	cout << "电视音量:" << volume << endl;
	cout << "------------------------------------" << endl;
}

// -----------------------------------------
void Remote::onOrOff()
{
	p->onOrOff();
}

void Remote::upVolume()
{
	p->upVolume();
}
void Remote::downVolume()
{
	p->downVolume();
}

void Remote::upChannel()
{
	p->upChannel();
}
void Remote::downChannel()
{
	p->downVolume();
}

void Remote::showTV()
{
	p->showTV();
}

void Remote::setChannel(int num)
{
	if (num >= TV::minChannel && num<=TV::maxChannel)
	{
		p->channel = num;
		return;
	}
	cout << "输入的频道不合法" << endl;
}


int main()
{
	TV tv;
	tv.showTV();
	
	tv.onOrOff();
	tv.upChannel();
	tv.upChannel();
	tv.upVolume();
	tv.showTV();
	tv.onOrOff();

	cout << "使用遥控器:" << endl;
	Remote rtv(&tv);
	rtv.showTV();

	rtv.onOrOff();
	rtv.upChannel();
	rtv.upChannel();
	rtv.downChannel();
	rtv.upVolume();
	rtv.showTV();
	rtv.onOrOff();

	return 0;
}

输出:

电视机状态:关
电视频道:0
电视音量:0
------------------------------------
电视机状态:开
电视频道:2
电视音量:1
------------------------------------
使用遥控器:
电视机状态:关
电视频道:2
电视音量:1
------------------------------------
电视机状态:开
电视频道:4
电视音量:1
------------------------------------

总结

  1. 友元函数。

友元函数是一个普通的非成员函数,但它被声明为某个类的友元。这意味着该函数可以访问该类的所有私有成员和保护成员。声明方式为在类定义中使用“friend”关键字,例如:

class MyClass {
  friend void myFunction();
};
  1. 友元类。

友元类是指某个类A将另一个类B声明为自己的友元,在这种情况下,B可以访问A的私有成员和保护成员。声明方式为在类定义中使用“friend”关键字并加上要作为友元的类名,例如:

class MyClass {
  friend class MyFriendClass;
};
  1. 友元成员函数。

友元成员函数是指某个函数被声明为另一个类A的友元,并且该函数所属于另一个与A无关的类B。这意味着该函数可以访问A对象中所有私有成员和保护成员。声明方式为在B类定义中使用“friend”关键字和A类型参数,例如:

class MyClassA {
  friend void MyClassB::myFunction(MyClassA&);
};
  1. 友元作用域。

友元的作用域是在声明它的类中,而不是在被声明为友元的函数或类中。因此,在同一作用域中可以使用相同名称的友元,但它们分别属于不同的类。

  1. 友元与继承。

子类不能直接访问父类的私有成员,但如果将子类声明为父类的友元,则子类就可以访问父类的私有成员和保护成员。这种情况下,应该谨慎考虑是否破坏了封装性。

在这里插入图片描述

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

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

相关文章

大数值使用BitSet存储导致的内存溢出

背景&#xff1a; 在日常的工作中&#xff0c;使用Redis的bitmap统计每天的登录用户数&#xff0c;使用java的BitSet进行统计总数或者与或非等操作时&#xff0c;我们可以看到BitSet/Redis的Bitmap操作的身影&#xff0c;他们也的确能减少内存的使用量以及操作的性能&#xff…

[HTML/CSS/JS]作品案例--笔记1

一、头部导航栏代码 html代码 <!-- 第一部分 导航栏 登录 注册 卡片点击 切换 --><div class"nav-containers"><ul class"ul-one"><li class"li-one color-white">首页</li><li class"li-one"&g…

下峰锁定,行情未尽,筹码峰真的不会骗人吗?

在学习筹码分布的时候&#xff0c;经常可以看到这样的顺口溜&#xff1a;“上峰不死&#xff0c;下跌不止&#xff1b;下峰锁定&#xff0c;行情未尽”。简单解释一下就是&#xff1a;下跌行情中&#xff0c;如果上密集峰未被充分消耗&#xff0c;那么就没有新的行情产生&#…

【Unity3D】雾效

1 前言 屏幕深度和法线纹理简介中对深度和法线纹理的来源、使用及推导过程进行了讲解&#xff0c;激光雷达特效中讲述了一种重构屏幕像素点世界坐标的方法&#xff0c;本文将介绍使用深度纹理重构屏幕像素点在相机坐标系下的坐标计算方法&#xff0c;并使用重构后的坐标模拟雾…

EventBus

EventBus 文章目录 EventBus1.EventBus的作用2.关于EventBus的概述3.EventBus的使用方法4.EventBus的黏性事件5.EventBus的源码EventBus的构造方法getDefault()源码EventBus()源码 订阅者注册register()源码findSubscriberMethods()源码findUsingInfo()源码findUsingReflection…

TCP的三次握手与四次挥手

TCP的三次握手与四次挥手 1.网络分层 网络分层代表硬件协议/技术特性应用层HTTP,DNS,FTP,SMTP,Telnet协议等应用程序实现的,规定应用程序的数据格式传输层TCP/UDP协议负责两主机之间的数据正确传输主机系统内核实现的网络层路由器IP协议负责地址管理和路由选择(确定对应主机)…

合宙Air724UG Cat.1模块硬件设计指南--SDIO接口

SDIO接口 简介 SDIO(Secure Digital Input and Output)全称为安全数字输入输出接口&#xff0c;在协议上和SPI类似是一种串行的硬件接口&#xff0c;通信的双方一个作为HOST&#xff0c;另一端是Device&#xff0c;所有的通信都是由HOST端发送命令开始的&#xff0c;Device端只…

SpringCloud Alibaba入门3之nacos服务搭建

在前一章的基础上开发:SpringCloud Alibaba入门之用户子模块开发_qinxun2008081的博客-CSDN博客 一、下载nacos-server 从https://github.com/alibaba/nacos/releasesopen in new window 下载nacos-server发行版。 二、启动nacos 进入%path%\nacos\bin文件夹,执行cmd命令st…

阿里组织变革新阶段:蓄力拉弓,一箭向前

自3月28日宣布“16N”分拆、5月18日宣布分业务启动独立融资或上市计划以来&#xff0c;阿里持续推动着这场史无前例的组织变革落地&#xff0c;谋求更高质量发展。 6月20日&#xff0c;阿里巴巴控股集团董事会主席兼CEO张勇通过全员信宣布&#xff0c;他将于今年9月10日卸任现…

[进阶]网络通信:端口和协议

端口 标记正在计算机设备上运行的应用程序的&#xff0c;被规定为一个 16位的二进制&#xff0c;范围是 0~65535。 分类 周知端口&#xff1a;0~1023&#xff0c;被预先定义的知名应用占用&#xff08;如&#xff1a;HTTP占用 80&#xff0c;FTP占用21&#xff09;注册端口&…

java中抽象类和抽象方法

文章目录 前言 一、抽象类和抽象方法是什么&#xff1f; 1.抽象类 2.抽象方法 二、使用方法 1.实操展示 2.注意事项 总结 前言 苹果这个具体的水果&#xff0c;它具有的属性为&#xff0c;红色&#xff1b;它具有的方法为&#xff0c;被啃。那么&#xff0c;水果&#…

system Verilog 验证测试平台编写指南——读书笔记(持续更新)

第一章 验证导论 1、基本测试平台的功能 测试平台的用途在于确定待测设计的正确性。包含下列步骤&#xff1a; &#xff08;1&#xff09;产生激励。 &#xff08;2&#xff09;把激励施加到DUT上. &#xff08;3&#xff09;捕捉响应。 &#xff08;4&#xff09;检验正…

【Visual Studio】开发 Qt 时右键没有自动添加 slots 槽的功能,使用 C++ 语言,配合 Qt 开发串口通信界面

知识不是单独的&#xff0c;一定是成体系的。更多我的个人总结和相关经验可查阅这个专栏&#xff1a;Visual Studio。 文章目录 Ref. 基于 Visual Studio 环境下使用 Qt&#xff0c;发现右键没有自动添加槽的功能&#xff0c;需要自己想办法。我看网上介绍了不止一种方法&#…

Micrometer实战

Micrometer 为基于 JVM 的应用程序的性能监测数据收集提供了一个通用的 API&#xff0c;支持多种度量指标类型&#xff0c;这些指标可以用于观察、警报以及对应用程序当前状态做出响应。 前言 可接入监控系统 监控系统的三个重要特征&#xff1a; 维度&#xff08;Dimensio…

事务的历史与SSI——PostgreSQL数据库技术峰会成都站分享

前言 PostgreSQL数据库技术峰会成都站 近期&#xff08;2023年6月17日&#xff09;&#xff0c;由中国开源软件推进联盟PG分会发起的“PostgreSQL数据库技术峰会成都站”圆满举行。我也有幸作为演讲嘉宾参加了此次峰会&#xff0c;收获很多。 &#xff08;分会回顾和所有pp…

Qt编写监控实时显示和取流回放工具(回放支持切换进度)

一、前言 现在各个监控大厂做的设备&#xff0c;基本上都会支持通过rtsp直接取流显示&#xff0c;而且做的比较好的还支持通过rtsp回放取流&#xff0c;基本上都会约定一个字符串的规则&#xff0c;每个厂家都是不一样的规则&#xff0c;比如回放对应的rtsp地址还要带上时间范…

Java8 List集合如何指定打印分隔符

目录 背景方法一&#xff1a;String.join&#xff08;推荐&#xff09;方法二&#xff1a;Collectors.joining总结 背景 无论是在学习还是日常的应用开发过程中&#xff0c;我们经常会需要使用分隔符将 List 集合打印出来。 如下所示&#xff1a; import java.util.Arrays;pub…

数据结构与算法基础(青岛大学-王卓)(5)

叮叮咚咚&#xff0c;新一期来袭&#xff0c;我还在吃桃子&#xff0c;吃桃子&#xff0c;吃桃子。。。串和python的字符串差不多,数组和广义表像是python的list 文章目录 串(string) - 字符串概念及术语串的类型定义存储结构&#xff08;同线性表&#xff09;串的模式匹配算法…

leetcode46. 全排列(回溯算法-java)

全排列 leetcode46. 全排列题目描述解题思路代码演示 回溯算法专题 leetcode46. 全排列 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;https://leetcode.cn/problems/permutations 题目描述 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有…

关于平差中误差方程 v=Bx-l 的探讨

文章目录 Part.I IntroductionPart.II 误差方程的探讨Chap.I 符号表示Chap.II 误差方程的含义Chap.III 误差方程的其他形式Chap.IV 平差的大致流程 Part.III 误差方程的表示形式 Part.I Introduction 在平时阅读文献或者整理笔记时&#xff0c;经常会看到各种各样的有关误差方…