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;}elsereturn false;
}
//减少音量
bool Tv::voldown()
{if(volume > minval){volume--;return true;}elsereturn false;
}
//增加频道
void Tv::chanup()
{if(channel < channelmax)channel++;elsechannel = 1;
}
//减少频道
void Tv::chandown()
{if(channel > channelmin)channel--;elsechannel = 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:// 设置默认队列大小为10enum{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_SIZEQueue(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.hqwc.cn/news/642050.html

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

相关文章

【C++】:构造函数和析构函数

目录 前言一&#xff0c;构造函数**1.1 什么是构造函数****1.2 构造函数的特性**1.3 总结 二&#xff0c;析构函数**2.1 什么是析构函数****2.2 析构函数的特性****2.3 总结** 前言 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并…

电机控制专题(一)——最大转矩电流比MTPA控制

文章目录 电机控制专题(一)——最大转矩电流比MTPA控制前言理论推导仿真验证轻载1Nm重载30Nm 总结 电机控制专题(一)——最大转矩电流比MTPA控制 前言 MTPA全称为Max Torque Per Ampere&#xff0c;从字面意思就可以知道MTPA算法的目的是一个寻优最值问题&#xff0c;可以从以…

06:HAL----定时器

前言&#xff1a; 每来一个TIM 时钟CNT计数器就记一个数&#xff0c;记到某一个程度就会产生溢出。然后ARR就会装载到CNT计数器里面 一:TIM 1:介绍 TIM&#xff08;Timer&#xff09;定时器 定时器可以对输入的时钟进行计数&#xff0c;并在计数值达到设定值时触发中断 16位计…

基于CAPL的HEX文件解析

&#x1f345; 我是蚂蚁小兵&#xff0c;专注于车载诊断领域&#xff0c;尤其擅长于对CANoe工具的使用&#x1f345; 寻找组织 &#xff0c;答疑解惑&#xff0c;摸鱼聊天&#xff0c;博客源码&#xff0c;点击加入&#x1f449;【相亲相爱一家人】&#x1f345; 玩转CANoe&…

武汉大学博士,华为上班5年多,月薪多少。。。

最近&#xff0c;一位来自武汉大学的博士研究生透露了自己在华为公司工作五年后的薪酬情况。 据他透露&#xff0c;他在2018年加入华为时的月薪为2.4万&#xff0c;随着时间的推移&#xff0c;到了2023年&#xff0c;他的月薪已经增长至4.4万&#xff01;此外&#xff0c;他还透…

IntelliJ IDEA2020下使用Maven构建Scala 项目

1.创建maven文件 2.进入pom.xml导入依赖 <!--添加spark的依赖--><dependency><groupId>org.apache.spark</groupId><artifactId>spark-core_2.12</artifactId><version>3.2.1</version></dependency><!--添加scala依…

OpenHarmony开源鸿蒙NEXT星河版内核嵌入式编程

一、前景提要 2024年1月18日&#xff0c;华为放出HarmonyOS NEXT 鸿蒙星河版开发者预览版本&#xff08;不是HarmonyOS NEXT版&#xff0c;是HarmonyOS NEXT星河版&#xff09;&#xff0c;首次提到用鸿蒙内核&#xff08;暂命名&#xff09;取代了Linux内核。 该内核源码还未放…

电厂水泵远程自动化控制系统解决方案介绍

水泵将原动机的机械能或其他外部能量传送给液体&#xff0c;使液体能量增加&#xff0c;可以用于电力供水系统、城市供水系统、石油化工系统、农业水利系统等等行业&#xff0c;水泵远程自动化监测控制系统&#xff0c;可实时监测电厂水泵的运行状态&#xff0c;实现对水电厂排…

电力调度自动化中智能电网技术的应用

电力调度自动化中智能电网技术的应用 在现代电网的现代化发展和电网重组工作中起着关键作用,由于此项技术开发时间短,目前还没有形成一个相对清晰的概念,但此技术在未来的电网发展工作中的地位已得到了一些国家的认可。由于智能电网具有良好的兼容性以及交互性等优势,一经推出就…

XML OR MYsql 报错:Could not create connection to database server.

mybaits文件少复制了一个部分&#xff0c;或缺少部分&#xff1a; 添加至表头即可解决 代码&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE configuration SYSTEM "http://mybatis.org/dtd/mybatis-3-config.dtd" &g…

SpringMVC中的文件上传和中英文名称文件下载

一、文件上传 前端&#xff1a; <% page language"java" contentType"text/html;charsetUTF-8"pageEncoding"UTF-8"%> <! DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4…

【Elasticsearch】Elasticsearch 从入门到精通(一):基本介绍

《Elasticsearch 从入门到精通》共包含以下 2 2 2 篇文章&#xff1a; Elasticsearch 从入门到精通&#xff08;一&#xff09;&#xff1a;基本介绍Elasticsearch 从入门到精通&#xff08;二&#xff09;&#xff1a;基础使用 &#x1f60a; 如果您觉得这篇文章有用 ✔️ 的…