C++面向对象编程之五:友元(friend)

news2024/9/27 12:07:03

C++中,允许一个类的非共有成员被这个类授予友元(friend)关系的全局函数另一个类,或另一个类中的成员函数访问。友元不是一个类中的成员,所以它们不受声明出现部分的访问权限(public,protected,private)影响。

友元函数

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

友元类

友元类是在类中用关键字friend修饰的另一个类的声明。那么这个友元类的所有成员函数都是这个类的友元函数,在友元类的成员函数体内都可以通过对象名访问这个类的私有成员和保护成员。

语法:

全局函数做友元

void spell(){}
class Monster
{   
    //全局函数做友元
    friend void spell();
};

类做友元(友元类)

class Skill{};
class Monster
{
    //友元类
    friend class Skill;
};

类的成员函数做友元

class Skill
{
    void spell(){}
};
class Monster
{
    //类的成员函数做友元
    friend void Skill::spell();
};

example:全局函数做Monster类的友元

#include <iostream>
using namespace std;

enum SKILL_TYPE
{
    BLOOD = 0
};

class Monster;

//施法
void spellSkill(Monster &src, Monster &dest, const SKILL_TYPE skillType, const int skillVal);

class Monster
{
    friend void spellSkill(Monster &src, Monster &dest, const SKILL_TYPE skillType, 
    const int skillVal); //全局函数做友元
    public:
    Monster():m_monsterId(0), m_name("怪物"), m_blood(0)
    {
    }
    Monster(const int monsterId, const string name, const int blood):
    m_monsterId(monsterId), m_name(name), m_blood(blood)
    {
    }
    Monster(const Monster &m):
    m_monsterId(m.m_monsterId), m_name(m.m_name), m_blood(m.m_blood)
    {
    }
    ~Monster()
    {
    }
    
    void spell(Monster &dest)
    {
        spellSkill(*this, dest, BLOOD, 1000);
    }

    private:
    int m_monsterId; //怪物id
    string m_name; //怪物名字
    int m_blood; //血量
};

void spellSkill(Monster &src, Monster &dest, const SKILL_TYPE skillType, const int skillVal)
{
    switch (skillType)
    {
        case BLOOD:
        {
            int blood = 0;
            //dest
            dest.m_blood -= skillVal; //因为Skill类是Monster类的友元,所以可以直接访问Monster类的非共有属性
            if (dest.m_blood < 0)
                dest.m_blood = 0;

            //src
            src.m_blood += skillVal; //因为Skill类是Monster类的友元,所以可以直接访问Monster类的私有成员变量m_blood

            //因为Skill类是Monster类的友元,所以可以直接访问Monster类的私有成员变量m_name
            cout << src.m_name << " 攻击了 " << dest.m_name << endl; 
            cout << src.m_name << "的血量增加到:" << src.m_blood << endl;
            cout << dest.m_name << "的血量减少到 " << dest.m_blood << endl;
            break;
        }
        default:
            cout << "技能类型未处理:" << skillType << endl;
    }
}

int main(int argc, char *argv[])
{
    Monster m1(10001, "雪女", 10000);
    Monster m2(10001, "紫衣仙子", 20000);

    m1.spell(m2);

    return 0;
}

example:Skill类做Monster类的友元

#include <iostream>
using namespace std;

enum SKILL_TYPE
{
    BLOOD = 0
};

class Monster;

class Skill
{
    public:
    Skill():m_skillType(BLOOD), m_val(500)
    {
    }
    Skill(const int skillType, const int val):m_skillType(skillType), m_val(val)
    {
    }
    Skill(const Skill &s):m_skillType(s.m_skillType), m_val(s.m_val)
    {
    }
    ~Skill()
    {
    }

    //施法
    void spell(Monster &src, Monster &dest);

    private:
    int m_skillType; //技能类型
    int m_val;
};

class Monster
{
    friend class Skill; //友元类
    public:
    Monster():m_monsterId(0), m_name("怪物"), m_blood(0), m_skill(BLOOD, 1000)
    {
    }
    Monster(const int monsterId, const string name, const int blood, const int skillType, const int skillVal):
    m_monsterId(monsterId), m_name(name), m_blood(blood), m_skill(skillType, skillVal)
    {
    }
    Monster(const Monster &m):
    m_monsterId(m.m_monsterId), m_name(m.m_name), m_blood(m.m_blood), m_skill(m.m_skill)
    {
    }
    ~Monster()
    {
    }
    
    void spell(Monster &dest)
    {
        m_skill.spell(*this, dest);
    }

    private:
    int m_monsterId; //怪物id
    string m_name; //怪物名字
    int m_blood; //血量
    Skill m_skill; //技能
};

//施法
void Skill::spell(Monster &src, Monster &dest)
{
    switch (m_skillType)
    {
        case BLOOD:
        {
            int blood = 0;
            //dest
            dest.m_blood -= m_val; //因为Skill类是Monster类的友元,所以可以直接访问Monster类的非共有属性
            if (dest.m_blood < 0)
                dest.m_blood = 0;

            //src
            src.m_blood += m_val; //因为Skill类是Monster类的友元,所以可以直接访问Monster类的私有成员变量m_blood

            //因为Skill类是Monster类的友元,所以可以直接访问Monster类的私有成员变量m_name
            cout << src.m_name << " 攻击了 " << dest.m_name << endl; 
            cout << src.m_name << "的血量增加到:" << src.m_blood << endl;
            cout << dest.m_name << "的血量减少到 " << dest.m_blood << endl;
            break;
        }
        default:
            cout << "技能类型未处理:" << m_skillType << endl;
    }
}

int main(int argc, char *argv[])
{
    Monster m1(10001, "雪女", 10000, BLOOD, 1000);
    Monster m2(10001, "紫衣仙子", 20000, BLOOD, 1000);

    m1.spell(m2);

    return 0;
}

example:Skill类的成员函数做Monster类的友元

#include <iostream>
using namespace std;

enum SKILL_TYPE
{
    BLOOD = 0
};

class Monster;

class Skill
{
    public:
    Skill():m_skillType(BLOOD), m_val(500)
    {
    }
    Skill(const int skillType, const int val):m_skillType(skillType), m_val(val)
    {
    }
    Skill(const Skill &s):m_skillType(s.m_skillType), m_val(s.m_val)
    {
    }
    ~Skill()
    {
    }

    //施法
    void spell(Monster &src, Monster &dest);

    private:
    int m_skillType; //技能类型
    int m_val;
};

class Monster
{
    friend void Skill::spell(Monster &src, Monster &dest); //Skill类的成员函数做友元
    public:
    Monster():m_monsterId(0), m_name("怪物"), m_blood(0), m_skill(BLOOD, 1000)
    {
    }
    Monster(const int monsterId, const string name, const int blood, const int skillType, const int skillVal):
    m_monsterId(monsterId), m_name(name), m_blood(blood), m_skill(skillType, skillVal)
    {
    }
    Monster(const Monster &m):
    m_monsterId(m.m_monsterId), m_name(m.m_name), m_blood(m.m_blood), m_skill(m.m_skill)
    {
    }
    ~Monster()
    {
    }
    
    void spell(Monster &dest)
    {
        m_skill.spell(*this, dest);
    }

    private:
    int m_monsterId; //怪物id
    string m_name; //怪物名字
    int m_blood; //血量
    Skill m_skill; //技能
};

//施法
void Skill::spell(Monster &src, Monster &dest)
{
    switch (m_skillType)
    {
        case BLOOD:
        {
            int blood = 0;
            //dest
            dest.m_blood -= m_val; //因为Skill类是Monster类的友元,所以可以直接访问Monster类的非共有属性
            if (dest.m_blood < 0)
                dest.m_blood = 0;

            //src
            src.m_blood += m_val; //因为Skill类是Monster类的友元,所以可以直接访问Monster类的私有成员变量m_blood

            //因为Skill类是Monster类的友元,所以可以直接访问Monster类的私有成员变量m_name
            cout << src.m_name << " 攻击了 " << dest.m_name << endl; 
            cout << src.m_name << "的血量增加到:" << src.m_blood << endl;
            cout << dest.m_name << "的血量减少到 " << dest.m_blood << endl;
            break;
        }
        default:
            cout << "技能类型未处理:" << m_skillType << endl;
    }
}

int main(int argc, char *argv[])
{
    Monster m1(10001, "雪女", 10000, BLOOD, 1000);
    Monster m2(10001, "紫衣仙子", 20000, BLOOD, 1000);

    m1.spell(m2);

    return 0;
}

为什么要用友元

如果需要在某个全局函数,某一个类或某一个类中的成员函数访问另一个类的私有或保护成员变量,又要求提高代码的执行效率,减少系统开销,我们可以选择让某个全局函数,某一个类或某一个类中的成员函数为另一个类的友元(friend),但这会破坏另一个类的封装性。所以在实际的开发过程中,我们应该按照实际需求选择是否用友元。

友元的特性

  1. 单向性:比如上面的例子中,Skill类是Monster类的友元,但Monster类不是Skill类的友元

  1. 友元不能被继承:比如上面的例子中Skill类是Monster类的友元,假如SceneSkill类是Skill类的子类,SceneSkill类不是Monster类的友元。

  1. 一般情况下,用友元函数重载<<,>>操作符

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

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

相关文章

Binder通信原理与弊端解析

Binder 定义 简单来说&#xff0c;Binder 就是用来Client 端和 Server 端通信的。并且 Client 端和 Server 端 可以在一个进程也可以不在同一个进程&#xff0c;Client 可以向 Server 端发起远程调用&#xff0c;也可以向Server传输数据&#xff08;当作函数参数来传&#xff…

USART_GetITStatus与 USART_GetFlagStatus的区别

文章目录共同点不同点USART_GetITStatus函数详解USART_GetFlagStatus函数共同点 都能访问串口的SR寄存器 不同点 USART_GetFlagStatus(USART_TypeDef USARTx, uint16_t USART_FLAG)&#xff1a;* 该函数只判断标志位&#xff08;访问串口的SR寄存器&#xff09;。在没有使能…

TwinCAT3中ModbusTCP Server和C# Client连接

目录 一、硬件环境 1、设置PLC的ip地址 2、ModbusTCP软件安装 3、PLC操作系统防火墙设置 4、网络助手连接PLC 二、创建PLC工程 1、创建寄存器读写变量 2、添加ModbusTCP授权 3、激活和运行工程 三、ModbusTCP数据协议说明 1、写单个寄存器 2、读寄存器 &#xff08;1&…

反转链表相关的练习(下)

目录 一、回文链表 二、 重排链表 三、旋转链表 一、回文链表 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输…

安装mayavi的成功步骤

这篇文章是python 3.6版本&#xff0c;windows系统下的安装&#xff0c;其他python版本应该也可以&#xff0c;下载对应的包即可。 一定不要直接pip install mayavi&#xff0c;这个玩意儿对vtk的版本有要求。 下载whl包 搞了很久不行&#xff0c;咱也别费那个劲了&#xff0…

【2023】某python语言程序设计跟学第三周内容

目录1.数字类型与操作&#xff1a;整数&#xff1a;浮点数&#xff1a;复数数值运算操作符数字之间关系数值运算函数2.案例&#xff1a;天天向上的力量第一问&#xff1a;1‰的力量第二问&#xff1a;5‰和1%的力量第三问&#xff1a;工作日的力量第四问&#xff1a;工作日的努…

Dynamics365 本地部署整体界面

昨天已经登陆上去了然后今天开机突然又登陆不上去了 具体原因也不知道 然后我把注册插件删除又重新下载结果还是登陆不上去于是返回之前的断点就可以登陆上去了重复昨天的操作这里就不截图了6、注册新步骤右键单击&#xff08;插件&#xff09;BasicPlugin.FollowUpPlugin&…

MySQL 主备一致

MySQL 主备一致主备切换binlog 格式statementrowmixed生产格式循环复制问题主备切换 MySQL 主备切换流程 : 状态 1 : 客户端的读写都直接访问节点 A&#xff0c;而节点 B 是 A 的备库&#xff0c;只将 A 的更新都同步过来 , 并本地执行。来保持节点 B 和 A 的数据是相同当切换…

Python蓝桥杯训练:基本数据结构 [二叉树] 中

Python蓝桥杯训练&#xff1a;基本数据结构 [二叉树] 中 文章目录Python蓝桥杯训练&#xff1a;基本数据结构 [二叉树] 中一、[翻转二叉树](https://leetcode.cn/problems/invert-binary-tree/)二、[对称二叉树](https://leetcode.cn/problems/symmetric-tree/)三、[二叉树的最…

Vue3这样子写页面更快更高效

在开发管理后台过程中,一定会遇到不少了增删改查页面,而这些页面的逻辑大多都是相同的,如获取列表数据,分页,筛选功能这些基本功能。而不同的是呈现出来的数据项。还有一些操作按钮。 对于刚开始只有 1,2 个页面的时候大多数开发者可能会直接将之前的页面代码再拷贝多…

工作记录:调研monorepo和微前端

2023年1月。因工作项目需要&#xff0c;调研 monorepo 、微前端等技术。 任务 一直在做的 BI 项目&#xff0c;随着需求迭代&#xff0c;模块越来越多&#xff0c;项目越来越复杂、臃肿。 最近&#xff0c;前一阶段的开发工作基本结束。新模块还在设计阶段。借此契机&#xf…

进阶C语言——数据的存储【详解】

文章目录1. 数据类型介绍1.1 类型的基本归类2. 整形在内存中的存储2.1 原码、反码、补码2.2 大小端介绍2.3 练习3. 浮点型在内存中的存储3.1 一个例子3.2 浮点数存储的规则1. 数据类型介绍 前面我们已经学习了基本的内置类型&#xff1a; char //字符数据类型 short //短整型 …

学习ForkJoin

学习ForkJoin一、普通解决多线程方式1、案例一2、效果图二、ForkJoin一、普通解决多线程方式 1、案例一 大数据量的List问题处理&#xff0c;多线程分批处理&#xff0c;需要解决的问题&#xff1a; 下标越界。线程安全。数据丢失。 private static ThreadPoolExecutor thre…

链表OJ之 快慢指针法总结

欢迎来到 Claffic 的博客 &#x1f49e;&#x1f49e;&#x1f49e; 前言&#xff1a; 快慢指针指的是每次指针移动的步长&#xff0c;是解决链表相关的题目的一大利器&#xff0c;下面我将以例题的形式讲解快慢指针法。 目录 一. 链表的中间结点 思路&#xff1a; 代码实…

GMP调度模型总结

优秀文章 什么是GMP调度模型 Golang的一大特色就是Goroutine。Goroutine是Golang支持高并发的重要保障。Golang可以创建成千上万个Goroutine来处理任务&#xff0c;将这些Goroutine分配、负载、调度到处理器上采用的是G-M-P模型。 什么是Goroutine Goroutine Golang Coro…

云舟案例︱视频孪生技术赋能城市安全综合管理场景,提升城市数智化水平

随着城市化发展进程的加快&#xff0c;人口不断膨胀&#xff0c;社会安全隐患等问题日益突出&#xff0c;成为困扰城市建设与管理的重要难题。针对各类社会治安突出问题&#xff0c;城市管理部门积极推进城市信息化建设&#xff0c;视频监控等各类信息化采集手段为城市数字化管…

嵌入式学习笔记——使用寄存器编程实现按键输入功能

文章目录前言模块介绍原理图编程思路前言 昨天&#xff0c;通过配置通用输出模式&#xff0c;实现了LED灯的点亮、熄灭以及流水等操作&#xff0c;解决了通用输出的问题&#xff0c;今天我们再借用最常见的输入模块&#xff0c;按键来实现一个按键控制LED的功能&#xff0c;重…

SpringBoot【知识加油站】---- REST开发

SpringBoot【知识加油站】---- REST开发1. REST 简介2. REST 风格3. RESTful 入门案例1. REST 简介 REST&#xff1a;Representaional State Transfer&#xff0c;表现形式状态转换 传统风格资源描述形式 http://localhost/user/getById?id1 http://localhost/user/saveUser…

91. 解码方法 ——【Leetcode每日刷题】

91. 解码方法 一条包含字母 A-Z 的消息通过以下映射进行了 编码 &#xff1a; ‘A’ -> “1” ‘B’ -> “2” … ‘Z’ -> “26” 要 解码 已编码的消息&#xff0c;所有数字必须基于上述映射的方法&#xff0c;反向映射回字母&#xff08;可能有多种方法&#xff0…

Kubernetes13:Ingress

Kubernetes13&#xff1a;Ingress 1、把端口号对外暴露&#xff0c;通过ip端口号进行访问 使用Service里面的NodePort实现&#xff08;Cluster、LoadBanlancer、NodePort&#xff09; 2、NodePort缺陷 在每个节点上启动一个端口&#xff0c;在访问时候通过任何节点&#xf…