C++友元类

友元类

友元类的使用

友元不仅仅适合于友元函数,还可以将类作为友元,在这种情况下,友元类的所有方法都可以访问原始类的私有方法和保护成员,什么时候去使用友元类呢?

两个类之间不存在包含和所属关系,但是我们某一个类中的方法想要调用另一个类中的私有成员和保护成员,这就需要使用到友元类

我们可以通过一个例子来看看友元类的用法,这是例子是电视机和遥控器的例子,遥控器并不是电视机的一部分,它们也不是包含关系,但是遥控器却可以改变电视机的状态,因此我们可以将遥控器类作为电视机类的一个友元

我们可以先编写一个电视机的类作为原始类,其中包含电视机开关状态,频道,音量以及信号源,然后再编写一个遥控器类作为电视机类的友元类,这样遥控器类的所有方法都可以访问电视机类的私有成员和保护成员,这就是友元类的方法

头文件

#ifndef _TV_h
#define _TV_h

#include<iostream>
using namespace std;
//电视机类
class Tv
{
        private:
                enum{off,on};//开关
                int state;
                enum{minval, maxval = 20};//音量
                int volume;
                enum{channelmin = 1, channelmax = 100};//频道
                int channel;
                enum{tv, dvd};
                int input;//信号原
        public:
                Tv(int s = off):state(s),volume(5),channel(2),input(tv){}
                //切换电视开关模式
                void onoff(){ state = (state == on) ? off:on; }
                //增大音量
                bool volup();
                //减少音量
                bool voldown();
                //增加频道
                void chanup();
                //减少频道
                void chandown();
                //改变信号
                void set_input(){input = (input == tv) ? dvd : tv;}
                //显示电视机设置
                void show()const;
                //友元类,遥控器类,也是友元类的声明
                friend class Remote;


};
//遥控器类
class Remote
{
        private:
                //模式
                int mode;
        public:
                Remote(int m = Tv::tv):mode(m){}
                //切换Tv类中的模式
                void onoff(Tv &t){t.onoff();}
                //音量增减
                bool volup(Tv &t){return t.volup();}
                bool voldown(Tv &t){return t.voldown();}
                //频道切换
                void chanup(Tv & t){t.chanup();}
                void chandown(Tv & t){t.chandown();}
                //设置频道,通过友元类去访问原始类中的保护数据
                void set_channel(Tv &t , int m){t.channel = m;}
                //设置信号源
                void set_input(Tv &t){t.set_input();}
};

#endif

函数定义

#include"TV.h"

//增大音量
bool Tv::volup()
{
        if(volume < maxval)
        {
                volume++;
                return true;
        }
        else
                return false;
}
//减少音量
bool Tv::voldown()
{
        if(volume > minval)
        {
                volume--;
                return true;
        }
        else
                return false;
}
//增加频道
void Tv::chanup()
{
        if(channel < channelmax)
                channel++;
        else
                channel = 1;
}
//减少频道
void Tv::chandown()
{
        if(channel > channelmin)
                channel--;
        else
                channel = 100;
}
//显示电视机设置
void Tv::show()const
{
        cout<<"Tv is "<< (state == off ? "off":"on" )<<endl;
        if(state == on)
        {
                cout<<"Volume is "<<volume<<endl;
                cout<<"Channel is "<<channel<<endl;
                cout<<"Inpiut is "<<( input == tv ? "tv" : "dvd")<<endl;
        }

}

 我们可以编写一个测试程序,看看友元类能否修改我们原始类中的私有成员和保护成员

#include"TV.h"

int main()
{
        Tv panda;
        cout<<"Initial setting for Panda TV:\n";
        panda.show();

        panda.onoff();
        panda.show();
        cout<<"-------------------------\n";

        Remote rm;
        rm.set_channel(panda,50);
        rm.set_input(panda);
        panda.show();
        return 0;
}

程序运行结果如下:

根据程序运行的结果可以看出,在我们调用遥控器类的两个成员函数时,      rm.set_channel(panda,50); rm.set_input(panda);成功的修改了电视机类的私有成员

前向声明及限定友元

在我们上面的程序中,我们的友元类中只有void set_channel(Tv &t , int m){t.channel = m;}这个成员函数是去访问了原始类的私有成员,而其他的成员函数都是通过访问原始类的公有接口去访问原始类的私有成员,因此我们可以只将遥控器类中的void set_channel函数定义为友元,而其他的不需要,因为其他类都是通过公有接口去访问的私有数据

即我们只需要友元类中的set_channel函数定义为友元函数,而其他的函数不需要,因此,我们可以进行如下的修改

在TV类中

class Tv
{
    ....
    public:
            friend void Remote::set_channel(Tv & t, int m);
    .....
}

注意:

在我们上面的代码中,我们这样进行修改会出现两个问题,即编译器无法识别Remote

而我们上面的代码中的friend class Remote,这里虽然编译器也不认识Remote,但是因为有关键字friend class ,所以编译器会将其看做是一个友元类的声明,所以不会进行报错

我们可以将两个类的定义顺序交换,即将遥控器类放在电视机类定义的上面,但是,这样又会导致编译器无法识别遥控器中Tv,这样就导致了一个循环依赖,解决这个问题的方法就是使用前向声明,即在我们的Remote类之前声明Tv类

class Tv;
class Remote
{
.....
};
class Tv
{
....
};

这样编译器就能够识别Tv是一个类了,但是我们在的Remote的定义中又使用了Tv类中的常量,Tv类是在Remote类后面定义,虽然编译器知道Tv是一个类,但是不知道Tv类中有什么

这样又会出现错误,因此就可以将Tv类中的常量声明也放入到Remote类中,即如下

但是这样又导致了问题,即我们在Remote类中使用了Tv类中的成员函数,而Tv类并没有进行定义,即没有定义而先进行使用,因此再次对代码进行修改,我们不再在Remote类中将函数实现,而是在Remote中仅仅进行函数的声明,将函数声明放在末尾,因为在头文件中进行定义的函数都是内联函数,因此我们加上关键字inline,这样就得到了最后的结果

#ifndef _TV_h
#define _TV_h

#include<iostream>
using namespace std;
//声明Tv类
class Tv;
//遥控器类
class Remote
{
        private:
                //模式
                enum{off,on};//开关
                enum{minval, maxval = 20};//音量
                enum{channelmin = 1, channelmax = 100};//频道
                enum{tv, dvd};//信号原
                int mode;
        public:
                Remote(int m = tv):mode(m){}
                //切换Tv类中的模式
                void onoff(Tv &t);
                //音量增减
                bool volup(Tv &t);
                bool voldown(Tv &t);
                //频道切换
                void chanup(Tv & t);
                void chandown(Tv & t);
                //设置频道,通过友元类去访问原始类中的保护数据
                void set_channel(Tv &t , int m);
                //设置信号源
                void set_input(Tv &t);

};
//电视机类
class Tv
{
        private:
                int state;
                int volume;
                int channel;
                int input;
                enum{off,on};//开关
                enum{minval, maxval = 20};//音量
                enum{channelmin = 1, channelmax = 100};//频道
                enum{tv, dvd};//信号原
        public:
                Tv(int s = off):state(s),volume(5),channel(2),input(tv){}
                //切换电视开关模式
                void onoff(){ state = (state == on) ? off:on; }
                //增大音量
                bool volup();
                //减少音量
                bool voldown();
                //增加频道
                void chanup();
                //减少频道
                void chandown();
                //改变信号
                void set_input(){input = (input == tv) ? dvd : tv;}
                //显示电视机设置
                void show()const;
                //友元类,遥控器类
                friend void Remote::set_channel(Tv &t, int m);

};
//切换Tv类中的模式
inline void  Remote::onoff(Tv &t){t.onoff();}
//音量增减
inline bool Remote::volup(Tv &t){return t.volup();}
inline bool Remote::voldown(Tv &t){return t.voldown();}
//频道切换
inline void Remote::chanup(Tv & t){t.chanup();}
inline void Remote::chandown(Tv & t){t.chandown();}
//设置频道,通过友元类去访问原始类中的保护数据
inline void Remote::set_channel(Tv &t , int m){t.channel = m;}
//设置信号源
inline void Remote::set_input(Tv &t){t.set_input();}


#endif

改写之后的头文件与之前的头文件相比,改写后的Remote类中只有一个函数是Tv类的友元,而之前的头文件中, Remote类中的所有成员函数都是Tv类的友元

互为友元的关系

假设我们技术的进步,不光能通过遥控器控制电视机,电视机也是反馈信息给我们的遥控器,让它们成为一种交互式的器件,这样我们就可以通过C++的编程,也就是可以通过让类彼此成为对方的友元来实现这个功能,即不光Remote是Tv类的友元,Tv类也是Remote类的友元,对于这样的情况,我们需要使用Remote对象的Tv方法,就需要将Tv类的原型放在Remote类的声明之前,并且将Tv类的定义放在Remote类声明之后,让编译器能够有足够的信息去编译这个方法,即如下所示

class Tv;
{
    friend class Remote;
    public:
        void buzz(Remote & rm);
        ....
};
class Remote
{

    friend class Tv;
    public:
            void bool volup(Tv & t){t.volup();}
       ....
};
inline void Tv::buzz(Remote & rm)
{
......
}

由于Remote类的声明在Tv类声明的后面,所以可以在Remote类声明中定义

void bool volup(Tv & t){t.volup();,但是Tv中的buzz方法的定义必须在Remote类的后面

嵌套类

在C++中,我们可以将一个类的声明放在另一个类中,在另一个类中声明的类就被称为嵌套类,它通过提供提供新的类型类作用域来避免名称的混乱,包含类的成员函数可以创建和使用被嵌套类的对象,仅仅当类的声明位于公有部分时,才能在包含类的外面使用嵌套类,并且必须使用作用域解析运算符,对类进行嵌套与对类的包含不同,包含意味着将类对象作为另一个类的成员,而对类进行嵌套不创建类成员,而仅仅是定义了一种类型,该类型仅仅在包含嵌套类声明中的类中有效

我们可以使用一个队列的例子来看看嵌套类的使用方法

// 模板类定义,用于实现队列结构
template <class Item>  
class Queue
{
	private:
		// 设置默认队列大小为10
		enum{Q_SIZE = 10};  
		// 内部类,定义队列节点结构
		class Node  // 定义节点类
		{
			public:
				// 节点存储的数据
				Item item;  
				// 指向下一个节点的指针
				Node *next;  
				// 构造函数,用传入的参数初始化节点
				Node(const Item &t) : item(t), next(NULL){}  
		};
		// 队列头节点
		Node *front;  
		// 队列尾节点
		Node *rear;  
		// 当前队列中的元素数量
		int items;  
		// 队列的最大容量
		const int qsize;  
	public:
		// 构造函数,接受队列大小参数,默认大小为Q_SIZE
		Queue(int qs = Q_SIZE);  
		// 析构函数,用于清理队列中的所有节点
		~Queue();  
		// 检查队列是否为空
		bool isempty() const;  
		// 检查队列是否已满
		bool isfull() const;  
		// 返回当前队列中的元素数量
		int queuecount() const;  
		// 将一个元素添加到队列尾部
		bool enqueue(const Item &item);  
		// 从队列头部移除一个元素
		bool dequeue(Item &item);  
};


在这串代码中,我们使用了嵌套类的方法,使用一个类作为队列的节点,嵌套类的优点在于它们可以封装在外部类的作用域内,同时又能够直接访问外部类的私有成员,在这个例子中,Node 类被用来表示队列中的节点,它与 Queue 类紧密相关,因此适合作为其内部类

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

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

相关文章

HTML中的文档声明

前言 什么是<!DOCTYPE>&#xff1f;是否需要在 HTML5 中使用&#xff1f;什么是严格模式与混杂模式&#xff1f; 文档声明概念 HTML 文档通常以文档声明开始&#xff0c;该声明的作用是帮助浏览器确定其尝试解析和显示的 HTML 文档类型。 <!DOCTYPE html>文档声…

科技渔业,智慧守护:4G+北斗太阳能定位终端准确定位,防拆卸报警,夯实渔业管理水平

如何高效地管理渔船&#xff0c;有效监控禁渔区域&#xff0c;4G北斗太阳能定位终端应运而生&#xff0c;成为渔业管理的重要应用工具。 我国作为全球渔业的重要国家&#xff0c;渔业一直是沿海地区传统的支柱产业&#xff0c;对经济的繁荣和民生的稳定起着至关重要的作用。因…

STC15L2K60S2-28I-LQFP44 单片机芯片 STC宏晶

STC15L2K60S2-28I-LQFP44 规格信息&#xff1a; 产品类型STC(宏晶) UART/USART2 额定特性- SPI1 USB Device0 USB Host/OTG0 PWM3 I2C&#xff08;SMBUS/PMBUS&#xff09;0 LCD0 工作电压2.4V ~ 3.6V EEPROM 尺度1KB Ethernet0 A/D8x10bit CAN0 D/A3x10bit CPU…

微服架构基础设施环境平台搭建 -(六)Kubesphere 部署Redis服务 设置访问Redis密码

微服架构基础设施环境平台搭建 -&#xff08;六&#xff09;Kubesphere 部署Redis服务 & 设置访问Redis密码 微服架构基础设施环境平台搭建 系列文章 微服架构基础设施环境平台搭建 -&#xff08;一&#xff09;基础环境准备 微服架构基础设施环境平台搭建 -&#xff08;二…

苍穹外卖学习笔记(4.套餐管理,店铺营业状态设置)

目录 一、Redis1、redis在java中的运用 二、店铺营业状态设置1、需求分析设计2、代码设计3、测试 三、套餐管理1、需求设计分析2、代码设计3、测试 一、Redis 具体的redis基本操作就不多再介绍&#xff0c;本节主要学习redis在java中的运用。 1、redis在java中的运用 具体…

Linux之安装Nginx

目录 传送门前言一、快速安装二、反向代理语法1、基本语法2、location语法1. 基本语法2. 匹配规则3. 修饰符4. 权重5. 嵌套location6. 其他指令7.案例 三、配置反向代理 传送门 SpringMVC的源码解析&#xff08;精品&#xff09; Spring6的源码解析&#xff08;精品&#xff0…

【数据结构】AVL树(万字超详细 附动图)

一、前言 二、AVL树的性质 三、AVL树节点的定义 四、AVL树的插入 五、AVL树的平衡调整 六、AVL树的验证 6.1 验证有序 6.2 验证平衡 七、AVL树的删除 八、AVL树的性能和代码 一、前言 还没有学习过二叉搜索树的同学可以移步 【数据结构】二叉搜索树-CSDN博客https:/…

《LINUX运维从入门到精通 》:运维入门者的自学用书

作者简介&#xff1a;一名云计算网络运维人员、每天分享网络与运维的技术与干货。 公众号&#xff1a;网络豆云计算学堂 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a; 网络豆的主页​​​​​ 写在前面 《Linux运维从入门到精通》从初学者角度…

免费GPU Google Colab保姆级使用攻略,实现数据集快速下载

Colab&#xff08;Colaboratory&#xff09;是一个基于云的交互式开发环境&#xff0c;专为数据科学、机器学习和人工智能任务设计。Colab 由 Google 提供&#xff0c;允许用户在浏览器中直接编写、运行、共享和协作处理 Python代码&#xff0c;特别是那些涉及大数据处理、计算…

CV每日论文--2024.4.22

1、BLINK: Multimodal Large Language Models Can See but Not Perceive 中文标题&#xff1a;BLINK&#xff1a;多模态大型语言模型可以看到但无法感知 简介&#xff1a;我们引入了Blink&#xff0c;这是一个全新的多模态语言模型&#xff08;LLMs&#xff09;基准&#xff0…

2024年城市规划与土木建筑国际学术会议(ICUPCA 2024)

2024年城市规划与土木建筑国际学术会议(ICUPCA 2024) 2024 International Conference on Urban Planning and Civil Architecture 一、【会议简介】 2024年城市规划与土木建筑国际学术会议&#xff0c;将汇集全球顶尖专家学者进行深入探讨。 这次会议的主题为“未来城市与土木…

上位机图像处理和嵌入式模块部署(树莓派4b开机启动脚本)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 编写好程序之后&#xff0c;一般要求程序开机启动后就可以运行。所以这个时候&#xff0c;我们一般就会把程序流程放在开发板的启动脚本当中。如果…

Web入门-Tomcat

黑马程序员JavaWeb开发教程 文章目录 一、简介1、Web服务器2、Tomcat 二、基本使用三、入门程序解析 一、简介 1、Web服务器 对HTTP协议操作进行封装&#xff0c;简化web程序开发部署Web项目&#xff0c;对外提供网上信息浏览服务 2、Tomcat 概念&#xff1a;Tomcat是Apach…

入门指南:从零开始学习ReactJS

&#x1f482; 个人网站:【 摸鱼游戏】【神级代码资源网站】【工具大全】&#x1f91f; 一站式轻松构建小程序、Web网站、移动应用&#xff1a;&#x1f449;注册地址&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交…

Kafak详解(1)

简介 消息队列 为什么要有消息队列 图-1 消息队列的使用 消息队列 1)消息Message&#xff1a;网络中的两台计算机或者两个通讯设备之间传递的数据。例如说&#xff1a;文本、音乐、视频等内容。 2)队列Queue&#xff1a;一种特殊的线性表(数据元素首尾相接)&#xff0c;特…

【React】Day6

项目搭建 基于CRA创建项目 CRA是一个底层基于webpack快速创建React项目的脚手架工具 # 使用npx创建项目 npx create-react-app react-jike# 进入到项 cd react-jike# 启动项目 npm start调整项目目录结构 -src-apis 项目接口函数-assets 项目资源文件&…

CANN 开发工具介绍

1、ATC工具 ATC&#xff08;Ascend Tensor Compiler&#xff09;是异构计 算架构CANN体系下的模型转换工具&#xff0c; 它可 以将开源框架的网络模型以及Ascend IR定义 的单算子描述文件&#xff08;json格式&#xff09;转换为昇腾 AI处理器支持的.om格式离线模型。 2、精度…

数新大数据平台迁移解决方案

随着企业的发展和数字化转型的不断深入&#xff0c;企业数据平台建设过去很多年&#xff0c;技术和架构过于落后&#xff0c;原有的大数据平台越来越难以满足业务需求。而在新的技术架构大数据平台的升级过程中&#xff0c;对数据和任务迁移的一致性、完整性有很高的要求&#…

JS绘制电流闪烁流动效果

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>电流闪动动效</title><style>.sd1 {dis…

linux信号机制分析

概念 信号递达&#xff1a;实际执行信号的处理动作就是信号递达 信号未决&#xff1a;信号从产生到递达之间的状态就是信号未决&#xff08;未决就是没有解决&#xff09; 收到某信号后&#xff0c;把未决信号集中的此信号置为1&#xff08;1表示未解决的信号&#xff09;&a…