C++ 类的友元

news2024/12/26 21:49:15

【例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类的顺序不能改变。因为,只有当一个类的定义已经被看到时,它的成员函数才能被声明为另一个类的友元。

成员函数的声明必须在它的友元成员函数声明之前

如:
X类中的成员函数是Y类中的友元函数:
(1)先定义X类,声明成员函数,不能在声明成员函数的时候定义该函数,要在两个类都定义结束之后再定义实现该成员函数。
(2)再定义Y类,声明X类中的成员函数为该类的友元成员函数。
(3)最后定义X类中的成员函数。
【注意】X类中的成员函数是Y类中的友元函数,则X类中的成员函数可以访问Y类的私有和保护数据成员

【例】

class Object;

class Int
{
private:
	int value;
public:

	Int(int x = 0) :value(x) {}
	~Int() {}
	friend void Object::Print( Int& it);//注册为类的友元函数
};

void Object::Print(Int& it)
{
	cout << it.value << endl;
}
class Object
{
public:
	void Print(Int& it);
};


int main()
{
	Int a(10);
	Object obj;
	obj.Print(a);
	return 0;
}

上例中,当一个成员函数还没有在某个具体的类中声明时,就在一开始定义的类中声明了某个类中的成员函数为这个类的友元成员函数,编译则不会通过。改写后如下:

class Int;
class Object
{
public:
	void Print(Int& it);
};

class Int
{
private:
	int value;
public:

	Int(int x = 0) :value(x) {}
	friend void Object::Print( Int& it);//注册为类的友元函数
};

void Object::Print(Int& it)
{
	cout << it.value << endl;
}

int main()
{
	Int a(10);
	Object obj;
	obj.Print(a);
	return 0;
}

运行结果:
在这里插入图片描述
【补充】友元函数都没有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类的友元。

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

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

相关文章

《零基础入门学习Python》第075讲:GUI的终极选择:Tkinter12

Tkinter 的基本组件我们已经介绍得七七八八了&#xff0c;剩下的一些我们在这节课全部都会讲解完毕。 &#xff08;一&#xff09;Message组件 Message&#xff08;消息&#xff09;组件是 Label 组件的变体&#xff0c;用于显示多行文本消息。众所周知&#xff0c;我们的Lab…

简单的Kubernetes集群二进制方式部署

Kubernetes二进制方式部署 一&#xff1a;操作系统初始化配置&#xff08;所有机子&#xff09;关闭防火墙关闭selinux关闭swap根据规划设置主机名在master添加hosts调整内核参数时间同步 二&#xff1a;部署 etcd 集群1.准备签发证书环境#准备cfssl证书生成工具生成Etcd证书编…

在SIP 语音呼叫中出现单通时要怎么解决?

在VoIP的环境中&#xff0c;特别是基于SIP通信的环境中&#xff0c;我们经常会遇到一些非常常见的问题&#xff0c;例如&#xff0c;单通&#xff0c;注册问题&#xff0c;回声&#xff0c;单通等。这些问题事实上都有非常直接的排查方式和解决办法&#xff0c;用户可以按照一定…

Quartz中集群模式源码级解析

文章目录 案例搭建 案例搭建 创建一个JOB实现类 package org.quartz.examples.example13;import org.quartz.*;import java.util.Date;/*** This job has the same functionality of SimpleRecoveryJob except that this job implements is stateful, in that it* will have …

Spring框架——IOC配置文件方式

Spring框架的概述和入门 目录 Spring框架的概述和入门 什么是Spring框架 Spring框架的特点 Spring框架的IOC核心功能快速入门 Spring框架中的工厂&#xff08;了解&#xff09; Spring 创建Bean对象的三种方式 Spring框架的Bean管理的配置文件方式 Spring框架中标签的配…

Token与Cookie、Session登录机制

Cookie 背景 Web 的兴起&#xff08;所谓交互式就是你不光可以浏览&#xff0c;还可以登录&#xff0c;发评论&#xff0c;购物等用户操作的行为&#xff09;&#xff0c;单纯地浏览 web 已经无法满足人们的要求&#xff0c;比如随着网上购物的兴起&#xff0c;需要记录用户的…

寻找丢失数字:数学与位运算的解密之旅

本篇博客会讲解力扣“268. 丢失的数字”的解题思路&#xff0c;这是题目链接。 注意进阶中的描述&#xff1a;你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题&#xff1f;这里我会讲解两种思路&#xff0c;它们的时间复杂度是O(N)&#xff0c;空间复杂度是O(1)…

STM32F1基于标准库ST7735 1.8‘‘LCD显示DHT11数据

STM32基于标准库ST7735 1.8‘’LCD显示DHT11数据 &#x1f4cd;HAL库驱动可以参考&#xff1a;《STM32基于HAL工程读取DHT11数据》&#x1f33c;显示效果&#xff1a; &#x1f33b;ST7735 128x160 1.8’LCD屏幕 &#x1f4cc;屏幕资料和相关驱动可以参考《1.8寸TFT LCD128…

JDK各版本重要变革

各版本更新详情 JDK8(LTS)--2014/3 语法层面 lambda表达式(重要特色之一) 一种特殊的匿名内部类,语法更加简洁允许把函数作为一个方法的参数,将代码象数据一样传递&#xff0c;即将函数作为方法参数传递基本语法: <函数式接口> <变量名> (参数...) -> { 方法…

迷你主机中的战斗机 Intel NUC 12 Serpent Canyon拆解

千呼万唤始出来&#xff0c;新一代游戏和创作者性能怪兽 mini主机 NUC 12 Serpent Canyon&#xff08;巨蛇峡谷终于发售了&#xff0c;以超紧凑的 2.5 升尺寸提供用户所需的所有性能和创新功能。NUC 12 Enthusiast 还首次将 Intel Deep Link 引入桌面&#xff0c;使 CPU 和 GPU…

类的继承和super关键字的使用(JAVA)

继承 所有的OOP语言都会有三个特征&#xff1a; 封装&#xff08;点击可跳转&#xff09;&#xff1b;继承&#xff1b;多态 为什么会有继承呢&#xff1f;可以先看下面的例子&#xff1a; 上面这两个类中的代码很相似因为它们只有最后一个方法不同其它的都相同&#xff0c;这样…

DbVisualizer Pro Crack

DbVisualizer Pro Crack DbVisualizer是适用于开发人员、DBA和分析师的通用数据库工具。它是最终的解决方案&#xff0c;因为相同的工具可以在访问各种数据库的所有主要操作系统上使用。支持的数据库Amazon Redshift、DB2 LUW、Exasol、H2、Informix、JavaDB/Derby、Microsoft …

【项目 进程10】2.21 alarm函数 2.22setitimer定时器函数

2.21 alarm函数 #include <unistd.h> unsigned int alarm(unsigned int seconds);功能&#xff1a;设置定时器&#xff08;闹钟&#xff09;。函数调用&#xff0c;开始倒计时&#xff0c;当倒计时为0的时候&#xff0c; 函数会给当前的进程发送一个信号&#xff1a;SIG…

C++中内存的动态管理

我们在C语言中了解到可以在栈区动态开辟空间&#xff0c;并且用完要进行释放&#xff0c;防止内存泄漏。 引入 C中也有可以进行动态开辟空间和释放空间的操作符new 、delete&#xff0c;虽然C中也可以用malloc、calloc、realloc、free函数&#xff0c;但是C中引入了类&#x…

宋浩概率论笔记(二)随机变量

本章节内容较多&#xff0c;是概率论与数理统计中最为重要的章节&#xff0c;对于概率密度和分布函数的理解与计算要牢牢掌握&#xff0c;才能在后期的学习中更得心应手。

小研究 - 微服务系统服务依赖发现技术综述(一)

微服务架构得到了广泛的部署与应用, 提升了软件系统开发的效率, 降低了系统更新与维护的成本, 提高了系统的可扩展性. 但微服务变更频繁、异构融合等特点使得微服务故障频发、其故障传播快且影响大, 同时微服务间复杂的调用依赖关系或逻辑依赖关系又使得其故障难以被及时、准确…

【Azure上云项目实战】 合规性的身份验证与访问控制:在 Azure 中实现符合 PCI DSS 要求的架构设计

文章目录 一、开篇写在前面二、项目背景及介绍三、Azure PCI DSS 项目架构及组件四、身份验证、访问控制4.1 三层防御控制4.2 三层部署结构 五、跳板机六、与 PCI DSS 要求的关系七、该篇总结&#xff08;重要&#xff09;写在文末 一、开篇写在前面 各位博客阅读者们以及对云…

【A200】Ubuntu18.04 + ROS-Melodic + 比业电子VISIOSCAN雷达 评测

大家好&#xff0c;我是虎哥&#xff0c;朋友介绍&#xff0c;有一款单线激光雷达&#xff0c;25米的检测距离&#xff0c;有80HZ的扫描频率&#xff0c;而且角度分辨率最高可以到0.1&#xff0c;这个参数我确实没有见过&#xff0c;所以立刻着手从厂家那申请到了VISIOSCAN雷达…

Android的Handler消息通信详解

目录 背景 1. Handler基本使用 2. Handler的Looper源码分析 3. Handler的Message以及消息池、MessageQueue 4. Handler的Native实现 4.1 MessageQueue 4.2 Native结构体和类 4.2.1 Message结构体 4.2.2 消息处理类 4.2.3 回调类 4.2.5 ALooper类 5. 总结&…

轻量级目标检测模型NanoDet-Plus微调、部署(保姆级教学)

前言 NanoDet-Plus是超快速、高精度的轻量级无锚物体检测模型&#xff0c;github项目文件。可以在移动设备上实时检测。其主要特点是 超轻量&#xff1a;模型文件仅980KB(INT8)、1.8MB(FP16)超快&#xff1a;移动ARM CPU上97fps&#xff08;10.23ms&#xff09;高精度&#xf…