C++ 类的友元

【例1】
将数据与处理数据的函数封装在一起,构成类,既实现了数据的共享又实现了隐藏,无疑是面向对象程序设计的一大优点。但是封装并不总是绝对的。现在考虑一个简单的例子,就是Point类,每一个Point类的对象代表一个“点”。如果需要一个函数来计算任意两点之间的距离,这个函数该怎样设计呢?

如果将计算距离的函数设计为类外的普通函数,就不能体现这个函数与“点”之间的联系,而且类外的函数也不能之间引用“点”的坐标(私有成员),这样计算很不方便。

那么设计Point类的成员函数应该怎样设计呢?从语法的角度这不难实现,但是不好理解。因为距离是点与点之间的一种关系,它既不属于每一个单独的点,也不属于整个Point类。也就是说无论把距离函数设计为非静态成员还是静态成员都会影响程序的可读性。

之前在类的组合中,通过Point的两个对象组合成Line(线段)类,具有计算线段长度的功能,但是Line类的实质是对线段的抽象。如果我们经常需要计算任意两点之间的距离,那么每次计算两点之间距离的时候都要先构造一个线段,这样既麻烦又影响程序的可读性。

这种情况下,需要一个在Point类外,但与Point类有特殊关系的函数。

【例2】

class A
{
public:
	void display() { cout << x << endl; }
	int getX() { return x; }
private:
	int x;
};
class B
{
	void set(int i);
	void display();
private:
	A a;
};

这是组合类的情况,类B中内嵌了类A的对象,但是B的成员函数却无法直接访问A的私有成员x。从数据安全性角度来说,这无疑是最安全的,内嵌的部件相当于一个黑盒。但是使用起来有些不方便,例如,按如下形式实现B的成员函数set,会引起编译错误:

void B::set(int i)
{
	a.x = i;
}

由于A的对象内嵌于B中,如何能让B的函数直接访问A的私有数据呢?

C++为上述两个例子中的需求提供了语法支持,就是友元关系。

友元关系提供了不同类或对象的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。

通俗的说,友元关系就是一个类主动声明哪些其他类或函数是它的朋友,进而给它们提供对本类的访问特许。也就是说,通过友元关系,一个普通函数或者类的成员函数可以访问封装于另一个类中的数据。从一定程度上将,友元是对数据隐藏和封装的破坏。但是为了数据共享,提高程序的效率和可读性,很多情况下这种小的破坏也是必要的,关键是一个度的问题,要在共享和封装之间找到一个恰当的平衡。。

在一个类中,可以利用关键字 friend 将其他函数或类声明为友元。 如果友元是一般函数或类的成员函数,称为友元函数;如果友元是一个类,则称为友元类,友元类的所有成员函数都自动称为友元函数。

1.友元函数

友元函数是在类中用关键字修饰的非成员函数。友元函数可以是一个普通的函数,也可以是其他类的成员函数。虽然友元函数不是本类的成员函数,但是在友元函数的函数体中可以通过对象名访问类的私有成员和保护成员。

【例】在介绍类的组合时,使用了Point类组合构成的Line类计算线段的长度。现在将采用友元函数来实现更一般的功能:计算任意两点之间的距离。屏幕上的点仍然用Point类来描述,两点之间的距离用普通函数dist来计算。计算过程中,函数dist需要访问Point类的私有数据成员x和y,为此将dist声明为Point类的友元函数。

#include<iostream>
using namespace std;
 
class Point//Point类的定义
{
public://外部接口
	Point(int x = 0,int y=0):x(x),y(y){}
	int getX() { return x; }
	int getY() { return y; }
	friend float dist(Point& p1, Point& p2);//友元函数声明
private://私有数据成员
	int x, y;
};

float dist(Point& p1, Point& p2)//友元函数实现
{
	double  x = p1.x - p2.x;//通过对象访问Point类的私有数据成员
	double  y = p1.y - p2.y;
	return static_cast<float>(sqrt(x * x + y * y));
}

int main()//主函数
{
	Point myp1(1, 1), myp2(4, 5);//定义Point类的对象
	cout << "两点之间的距离为:";
	cout << dist(myp1, myp2) << endl;//计算两点之间的距离
	return 0;
}

运行结果及分析:
在这里插入图片描述
在Point类中只声明了友元函数的原型,友元函数的定义在类外。可以看到在友元函数中通过使用对象名直接访问了Point类中的私有数据成员x和y,这就是友元关系的关键所在。对于计算任意两点之间的距离这个问题来说,使用友元与使用类的组合相比,可以使程序具有更好的可读性。当然,如果是要表示线段,无疑是使用组合类Line类更为恰当。这就说明对于同一个问题,虽然语法上可以有多个解决方案,但应该根据问题的实质,选择一种比较直接地反映问题域的本来面目的方案,这样程序才会有更高的可读性。

友元函数不仅可以是一个普通函数,也可以是另外一个类中的成员函数。友元成员函数的使用和一般友元函数的使用基本相同,只是要通过相应的类或对象名进行访问。

2.友元成员函数

class Date;//前向引用声明
//类Time的成员函数中使用了Date类的对象,而此时Date类尚未被完整定义
class Time
{
public:
	Time(int a=0, int b=0, int c=0) :hour(a), min(b), se(c) {}
	void display(Date a);//只有public的成员函数才能成为其它类的友元函数
private:
	int hour, min, se;
};

class Date
{
public:
	Date(int a, int b, int c) :year(a), month(b), day(c) {}
	friend void Time::display(Date a);//将类Time的成员函数display()声明为类Date的友元函数

private:
	int year, month, day;
};


void Time::display(Date a)//Time的成员函数display()的实现
{
	cout << a.year << "年" << a.month << "月" << a.day << "日    ";
	cout << hour <<":"<< min <<":" << se << endl;
}

int main() {

	
	Date d1(2023, 8, 2);
	Time t1(13, 12, 23);
	
	t1.display(d1);//将Date类的对象d1作为函数display()的参数,然后用Time类的对象t1访问友元成员函数display
	return 0;
}

运行结果及分析:
在这里插入图片描述
在程序中display函数是Time类的成员函数,在Date类中将Time类的成员函数display声明为友元成员函数,所以在display函数的实现的时候,将Date类的对象作为display函数的实参,否则无法访问Date类对象的私有数据成员,在display函数体中访问Date类的数据时必须通过对象名去访问例如a.year;

【注意】Date类 和Time类的顺序不能改变。因为,只有当一个类的定义已经被看到时,它的成员函数才能被声明为另一个类的友元。

【补充】友元函数都没有this指针,所以要用类作为形参来写。

3.友元类

同友元函数一样,一个类可以将另一个类声明为友元类。若A类为B类的友元类,则A类的所有成员函数都是B类的友元函数,都可以访问B类的私有和保护成员。
声明友元类的语法形式如下:

class B
{
	...//B类的成员声明
	friend class A;//声明A类为B类的友元类
};

声明友元类,时建立类与类之间的联系,实现类与类之间数据共享的一种途径。
【例】B类是A类的友元类,则B类成员函数都为A类的友元函数,所以B类的成员函数可以直接访问A的私有成员。

class A
{

private:
	int data;
public:
	void display()
	{
		cout << "data = " << data << endl;
	}
	friend class B; // 将B类声明是A类的友元类
};

class B
{
public:
	void change(int x, A& a)//引用
	{
		a.data = x;//通过A类的对象a访问A类的私有数据成员data
		a.display();通过A类的对象a访问A类的成员函数display
	}
};

int main()
{
	A a;//A类对象a
	B b;//B类对象b
	b.change(50, a);//以50和A类对象a作为B类成员函数change的参数,通过B类对象b去调用函数change

	return 0;
}

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

总结

1.友元关系不能传递
B类是A类的友元,C类是B类的友元,C类和A类之间,如果没有声明,就没有任何关系,不能进行数据共享。

2.友元关系是单向的
如果声明B类是A类的友元,B类的成员函数就可以访问A类的私有和保护数据,但A类的成员函数不可以访问B类的私有和保护数据。

3.友元关系是不被继承的
如果B类是A类的友元,B类的派生类并不会自动成为A类的友元。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

阿里云服务器免费试用及搭建WordPress网站

文章目录 前言一、免费试用1、选择使用产品2、进行产品配置3、远程连接阿里云服务器①、重置实例密码②、SecureCRT 远程链接③、Workbench 远程链接二、搭建 WordPress 网站1、开放搭建 WordPress 需要的端口2、搭建 LAMP 环境①、Linux 系统升级和更新源②、安装 Apache2③、…

LAXCUS分布式操作系统引领科技潮流,进入百度首页

信息源自某家网络平台&#xff0c;以下原样摘抄贴出。 随着科技的飞速发展&#xff0c;分布式操作系统做为通用基础平台&#xff0c;为大数据、高性能计算、人工智能提供了强大的数据和算力支持&#xff0c;已经成为了当今计算机领域的研究热点。近日&#xff0c;一款名为LAXCU…

云原生势不可挡,如何跳离云原生深水区?

云原生是云计算领域一大热词&#xff0c;伴随云原生概念而来的是数字产业迎来井喷、数字变革来临、数字化得以破局以及新一波的技术红利等等。云原生即“云”原生&#xff0c;顾名思义是让“应用”最大程度地利用云的能力&#xff0c;发挥云价值的最佳路径。具体来说&#xff0…

通向架构师的道路之apache性能调优

一、总结前一天的学习 在前两天的学习中我们知道、了解并掌握了Web Server结合App Server实现单向Https的这样的一个架构。这个架构是一个非常基础的J2ee工程上线布署时的一种架构。在前两天的教程中&#xff0c;还讲述了Http服务 器、App Server的最基本安全配置&#xff08;…

解决单节点es索引yellow

现象 单节点的es&#xff0c;自动创建索引后&#xff0c;默认副本个数为1&#xff0c;索引状态为yellow 临时解决 修改副本个数为0 永久解决 方法1、修改elasticsearch.yml文件&#xff0c;添加配置并重启es number_of_replicas&#xff1a;副本分片数&#xff0c;默认…

【疑难解决】EasyCVR告警消息生成后,合成录像不显示的原因排查

有用户反馈&#xff0c;视频监控汇聚平台EasyCVR对接了摄像头告警信息&#xff0c;但是平台没有告警信息上来。 技术人员配合项目现场排查&#xff0c;TSINGSEE青犀视频安防监控平台EasyCVR平台端已经配置了告警预案&#xff0c;并且也开启了告警开关&#xff0c;用户的配置流程…

docker简单web管理docker.io/uifd/ui-for-docker

要先pull这个镜像docker.io/uifd/ui-for-docker 这个软件默认只能使用9000端口&#xff0c;别的不行&#xff0c;因为作者在镜像制作时已加入这一层 刚下下来镜像可以通过docker history docker.io/uifd/ui-for-docker 查看到这个端口已被 设置 如果在没有设置br0网关时&…

【NLP概念源和流】 05-引进LSTM网络(第 5/20 部分)

一、说明 在上一篇博客中,我们讨论了原版RNN架构,也讨论了它的局限性。梯度消失是一个非常重要的缺点,它限制了RNN对较短序列的建模。香草 RNN 在相关输入事件和目标信号之间存在超过 5-10 个离散时间步长的时间滞时无法学习。这基本上限制了香草RNN在许多实际问题上的应用,…

编程:必备技能还是浪费时间?

当下&#xff0c;学习编程正变得越来越受欢迎&#xff0c;许多人都在探讨这个话题&#xff0c;但仍有很多人产生疑问&#xff1a;学习编程是否有必要&#xff1f;我们可以从学习编程的好处和应用领域来进行分析。好处方面&#xff0c;乔布斯曾说&#xff1a;“人人都应该学习编…

新闻稿发布中,首发来源和转载是什么意思?

一秒推小编告诉您&#xff0c;在新闻稿发布中&#xff0c;首发来源和转载是两个常用的词语&#xff0c;它们有着不同的含义和使用场合。#新闻稿发布# 首发来源指的是原创的、第一次发布该条新闻的媒体或媒体机构。比如&#xff0c;如果一家新闻机构发布了一则新闻稿&#xff0c…

《golang设计模式》第一部分·创建型模式-03-建造者模式(Builder)

文章目录 1. 概念1.1 角色1.2 类图 2. 代码示例2.1 设计2.2 代码2.3 类图 1. 概念 1.1 角色 Builder&#xff08;抽象建造者&#xff09;&#xff1a;给出一个抽象接口&#xff0c;以规范产品对象的各个组成成分的建造。ConcreteBuilder&#xff08;具体建造者&#xff09;&a…

05 Ubuntu下安装.deb安装包方式安装vscode,snap安装Jetbrains产品等常用软件

使用deb包安装类型 deb包指的其实就是debian系统&#xff0c;ubuntu系统是基于debian系统的发行版。 一般我们会到需要的软件官网下载deb安装包&#xff0c;然后你既可以采用使用“软件安装”打开的方法来进行安装&#xff0c;也可以使用命令行进行安装。我推荐后者&#xff…

装修小程序,开启装修公司智能化服务的新时代

随着数字化时代的来临&#xff0c;装修小程序成为提升服务质量和效率的关键工具。装修小程序旨在为装修公司提供数字化赋能、提高客户满意度的智慧装修平台。通过装修小程序&#xff0c;装修公司能够与客户进行在线沟通、展示设计方案、提高服务满意度等操作。 装修小程序的好处…

spring boot 服务健康检测返回OUT_OF_SERVICE,导致服务无法成功注册到consul

一、背景 健康检测接口返回OUT_OF_SERVICE curl -X GET http://192.168.5.53:8085/mgm/health{"status":"OUT_OF_SERVICE"}从日志启动看&#xff0c;没有任何报错信息&#xff1b;而且jvm进程也启动成功。 关键的一点信息是&#xff0c;服务的swagger地…

Linux之 Ubuntu 安装常见服务 (二) Tomcat

安装TomCat 服务 1、安装JDK环境 https://www.oracle.com/java/technologies/downloads/ 下载的官网 wget https://download.oracle.com/java/20/latest/jdk-20_linux-x64_bin.deb (sha256) 使用dpkg进行软件安装时&#xff0c;提示&#xff1a;dpkg&#xff1a;处理软件包XX…

若依打印sql

官方issue 自动生成的代码&#xff0c;sql日志怎么没有打印 在ruoyi-admin中的application.yml配置如下。 # 日志配置&#xff0c;默认 logging:level:com.ruoyi: debugorg.springframework: warn#添加配置com.ying: debug输出sql

express学习笔记6 - 用户模块

新建router/user.js const express require(express) const routerexpress.Router() router.get(/login, function(req, res, next) {console.log(/user/login, req.body)res.json({code: 0,msg: 登录成功})})module.exportsrouter 在router/user.js引入并使用 const us…

DevOps系列文章之 Docker 安装 NFS 服务器

Docker 安装 NFS 服务器 环境&#xff1a; 192.186.2.105 NFS 服务器 192.168.2.106 Client 客户端 安装 一、服务器端 https://github.com/f-u-z-z-l-e/docker-nfs-server 1、创建目录 mkdir /nfsdata mkdir -p /docker/nfs/2、启动脚本 vim start.sh# 内容 docker run …

NFTScan | 07.24~07.30 NFT 市场热点汇总

欢迎来到由 NFT 基础设施 NFTScan 出品的 NFT 生态热点事件每周汇总。周期&#xff1a;2023.07.24~ 2023.07.30 NFT Hot News 01/数据&#xff1a;Azuki 地板价跌破 5 枚 ETH&#xff0c;过去 7 日跌幅逾 20% 7月24日&#xff0c; Blur数据显示&#xff0c;Azuki地板价已跌破…

MySQL数据备份与还原

一、数据备份 1、使用mysqldump命令备份 mysqldump命令将数据库中的数据备份成一个文本文件。表的结构和表中的数据将存储在生成的文本文件中。 mysqldump命令的工作原理很简单。它先查出需要备份的表的结构&#xff0c;再在文本文件中生成一个CREATE语句。然后&#xff0c;将表…