全面理解-友元(friend关键字)

news2025/2/11 16:28:05

在 C++ 中,friend 关键字用于授予其他类或函数访问当前类的 私有(private)和保护(protected)成员 的权限。这种机制打破了严格的封装性,但可以在特定场景下提高代码的灵活性和效率。以下是 friend 的详细说明和使用示例:


一、friend 的作用

  • 允许外部访问私有成员:类的私有成员通常只能被其自身的成员函数访问,但通过 friend 声明,可以授权特定的外部函数、其他类或成员函数访问这些私有成员。

  • 不破坏封装性的合理场景:例如操作符重载、工具函数或紧密协作的类之间共享数据。


二、friend 的用法

1. 友元函数(Friend Function)
  • 定义:将非成员函数声明为友元,使其可以访问类的私有成员。

  • 适用场景:操作符重载(如 <<>>)、工具函数等。

  • 示例

    #include <iostream>
    class MyClass {
    private:
        int secret = 42;
    
    public:
        // 声明友元函数(非成员函数)
        friend void printSecret(const MyClass& obj);
    };
    
    // 友元函数定义
    void printSecret(const MyClass& obj) {
        std::cout << "Secret: " << obj.secret << std::endl; // ✅ 允许访问私有成员
    }
    
    int main() {
        MyClass obj;
        printSecret(obj); // 输出 "Secret: 42"
        return 0;
    }

2. 友元类(Friend Class)
  • 定义:将另一个类声明为友元,使其所有成员函数可以访问当前类的私有成员。

  • 适用场景:两个类需要紧密协作(如迭代器与容器)。

  • 示例

    class Storage {
    private:
        int data = 100;
    
    public:
        // 声明友元类
        friend class Accessor;
    };
    
    class Accessor {
    public:
        static int getData(const Storage& s) {
            return s.data; // ✅ 允许访问 Storage 的私有成员
        }
    };
    
    int main() {
        Storage s;
        std::cout << Accessor::getData(s); // 输出 100
        return 0;
    }

3. 友元成员函数(Friend Member Function)
  • 定义:将另一个类的特定成员函数声明为友元。

  • 适用场景:仅允许其他类的部分函数访问私有成员。

  • 示例

    class B; // 前向声明
    
    class A {
    private:
        int secret = 200;
    
    public:
        // 声明 B 的成员函数为友元
        friend void B::accessA(const A& a);
    };
    
    class B {
    public:
        void accessA(const A& a) {
            std::cout << "A's secret: " << a.secret; // ✅ 允许访问
        }
    };
    
    int main() {
        A a;
        B b;
        b.accessA(a); // 输出 "A's secret: 200"
        return 0;
    }


三、friend 的关键规则

  1. 单向性:友元关系是单向的。若 A 是 B 的友元,B 不自动成为 A 的友元。

  2. 不传递性:友元关系不传递。若 A 是 B 的友元,B 是 C 的友元,A 不自动成为 C 的友元。

  3. 不可继承:基类的友元不自动成为派生类的友元。

  4. 声明位置无关:友元声明可放在类的 publicprotected 或 private 区域,效果相同。


四、典型应用场景

1. 操作符重载
  • 例如重载 << 和 >> 实现自定义类的输入输出:

    class MyClass {
    private:
        int value;
    
    public:
        MyClass(int v) : value(v) {}
    
        // 声明友元函数以访问私有成员
        friend std::ostream& operator<<(std::ostream& os, const MyClass& obj);
    };
    
    std::ostream& operator<<(std::ostream& os, const MyClass& obj) {
        os << "Value: " << obj.value; // ✅ 访问私有成员
        return os;
    }
    
    int main() {
        MyClass obj(42);
        std::cout << obj; // 输出 "Value: 42"
        return 0;
    }

2. 需要访问私有数据的工具函数
  • 例如实现一个序列化函数:

    class Data {
    private:
        int id;
        std::string content;
    
    public:
        Data(int i, const std::string& s) : id(i), content(s) {}
    
        friend std::string serialize(const Data& data);
    };
    
    std::string serialize(const Data& data) {
        return "ID: " + std::to_string(data.id) + ", Content: " + data.content;
    }

3. 紧密协作的类
  • 例如容器类与迭代器:

    template<typename T>
    class Vector {
    private:
        T* data;
        size_t size;
    
    public:
        friend class Iterator; // 迭代器需要访问私有成员
    
        class Iterator {
        private:
            T* ptr;
        public:
            Iterator(T* p) : ptr(p) {}
            T& operator*() { return *ptr; }
            // ...
        };
    };


五、优缺点与最佳实践

优点
  • 灵活性:支持操作符重载和特殊函数访问私有数据。

  • 性能优化:避免通过公有接口间接访问数据的开销。

缺点
  • 破坏封装性:过度使用会导致代码维护困难。

  • 耦合性增加:友元类/函数与当前类紧密绑定。

最佳实践
  • 慎用友元:优先通过公有接口访问数据,仅在必要时使用。

  • 最小化友元范围:尽量声明友元函数而非友元类,或仅开放必要的成员函数。

  • 文档说明:明确标注友元关系的设计意图。


总结

特性说明
核心作用授权外部函数或类访问私有/保护成员
常见用法友元函数、友元类、友元成员函数
典型场景操作符重载、工具函数、紧密协作的类
注意事项单向性、不传递性、不可继承
替代方案优先使用公有接口,避免过度依赖友元

合理使用 friend 关键字可以在不严重破坏封装性的前提下,解决特定场景下的代码设计问题。

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

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

相关文章

【Java】多线程和高并发编程(三):锁(下)深入ReentrantReadWriteLock

文章目录 4、深入ReentrantReadWriteLock4.1 为什么要出现读写锁4.2 读写锁的实现原理4.3 写锁分析4.3.1 写锁加锁流程概述4.3.2 写锁加锁源码分析4.3.3 写锁释放锁流程概述&释放锁源码 4.4 读锁分析4.4.1 读锁加锁流程概述4.4.1.1 基础读锁流程4.4.1.2 读锁重入流程4.4.1.…

macbook2015升级最新MacOS 白苹果变黑苹果

原帖&#xff1a;https://www.bilibili.com/video/BV13V411c7xz/MAC OS系统发布了最新的Sonoma&#xff0c;超酷的动效锁屏壁纸&#xff0c;多样性的桌面小组件&#xff0c;但是也阉割了很多老款机型的升级权利&#xff0c;所以我们可以逆向操作&#xff0c;依旧把老款MAC设备强…

如何使用C++将处理后的信号保存为PNG和TIFF格式

在信号处理领域&#xff0c;我们常常需要将处理结果以图像的形式保存下来&#xff0c;方便后续分析和展示。C提供了多种库来处理图像数据&#xff0c;本文将介绍如何使用stb_image_write库保存为PNG格式图像以及使用OpenCV库保存为TIFF格式图像。 1. PNG格式保存 使用stb_ima…

探索从传统检索增强生成(RAG)到缓存增强生成(CAG)的转变

在人工智能快速发展的当下&#xff0c;大型语言模型&#xff08;LLMs&#xff09;已成为众多应用的核心技术。检索增强生成&#xff08;RAG&#xff09;&#xff08;RAG 系统从 POC 到生产应用&#xff1a;全面解析与实践指南&#xff09;和缓存增强生成&#xff08;CAG&#x…

尝试一下,交互式的三维计算python库,py3d

py3d是一个我开发的三维计算python库&#xff0c;目前不定期在PYPI上发版&#xff0c;可以通过pip直接安装 pip install py3d 开发这个库主要可视化是想把自己在工作中常用的三维方法汇总积累下来&#xff0c;不必每次重新造轮子。其实现成的python库也有很多&#xff0c;例如…

[创业之路-289]:《产品开发管理-方法.流程.工具 》-15- 需求管理 - 第1步:原始需求收集

概述&#xff1a; 需求收集是需求管理的第一步&#xff0c;也是产品开发、项目管理或软件设计中的关键步骤。原始需求收集主要是指从各种来源获取关于产品或服务的初步需求和期望。 以下是对需求管理中的原始需求收集的详细分析&#xff1a; 1、原始需求收集的目的 原始需求…

蓝桥杯---数青蛙(leetcode第1419题)

文章目录 1.题目重述2.例子分析3.思路分析4.思路总结5.代码解释 1.题目重述 这个题目算是模拟这个专题里面的一类比较难的题目了&#xff0c;他主要是使用crock这个单词作为一个整体&#xff0c;让我们确定&#xff1a;给你一个字符串&#xff0c;至少需要多少个青蛙进行完成鸣…

单片机之基本元器件的工作原理

一、二极管 二极管的工作原理 二极管是一种由P型半导体和N型半导体结合形成的PN结器件&#xff0c;具有单向导电性。 1. PN结形成 P型半导体&#xff1a;掺入三价元素&#xff0c;形成空穴作为多数载流子。N型半导体&#xff1a;掺入五价元素&#xff0c;形成自由电子作为多…

OpenEuler学习笔记(二十三):在OpenEuler上部署开源MES系统

在OpenEuler上部署小企业开源MES&#xff08;制造执行系统&#xff0c;Manufacturing Execution System&#xff09;是一个非常有价值的项目&#xff0c;可以帮助企业实现生产过程的数字化管理。以下是基于开源MES系统&#xff08;如 Odoo MES 或 OpenMES&#xff09;的部署步骤…

ubuntu中如何在vscode的终端目录后显示(当前的git分支名) 实测有用

效果展示 配置过程&#xff1a; 在 Ubuntu 中&#xff0c;如果你想在 VS Code 的终端提示符后显示当前的 Git 分支名&#xff0c;可以通过修改 Shell 配置文件&#xff08;如 ~/.bashrc 或 ~/.zshrc&#xff09;来实现。以下是具体步骤&#xff1a; 1. 确定使用的 Shell 首…

从二叉树遍历深入理解BFS和DFS

1. 介绍 1.1 基础 BFS&#xff08;Breadth-First Search&#xff0c;广度优先搜索&#xff09;和 DFS&#xff08;Depth-First Search&#xff0c;深度优先搜索&#xff09;是两种常见的图和树的遍历算法。 BFS&#xff1a;从根节点&#xff08;或起始节点&#xff09;开始&am…

Kotlin协程详解——协程上下文

目录 一、上下文结构 get()获取元素 minusKey()删除元素 fold()元素遍历 plus()添加元素 CombinedContext Key 二、协程名称CoroutineName 三、上下文组合 四、协程作用域CoroutineScope 五、典型用例 协程的上下文&#xff0c;它包含用户定义的一些数据集合&#x…

手写一个C++ Android Binder服务及源码分析

手写一个C Android Binder服务及源码分析 前言一、 基于C语言编写Android Binder跨进程通信Demo总结及改进二、C语言编写自己的Binder服务Demo1. binder服务demo功能介绍2. binder服务demo代码结构图3. binder服务demo代码实现3.1 IHelloService.h代码实现3.2 BnHelloService.c…

Deep Dive into LLMs like ChatGPT - by Andrej Karpathy

https://www.youtube.com/watch?v7xTGNNLPyMIhttps://www.youtube.com/watch?v7xTGNNLPyMIDeep Dive into LLMs like ChatGPT - by Andrej Karpathy_哔哩哔哩_bilibilihttps://www.youtube.com/watch?v7xTGNNLPyMI转载自Andrej Karpathy Youtube ChannelThis is a general a…

react实例与总结(一)

目录 一、简单认识 1.1、特点 1.2、JSX语法规则 1.3、函数组件和类式组件 1.4、类组件三大属性state、props、refs 1.4.1、state 1.4.2、props 1.4.3、refs 1.5、事件处理 1.6、收集表单数据—非受控组件和受控组件 1.7、高阶函数—函数柯里化 1.8、生命周期—新旧…

51单片机(国信长天)矩阵键盘的基本操作

在CT107D单片机综合训练平台上&#xff0c;首先将J5处的跳帽接到1~2引脚&#xff0c;使按键S4~S19按键组成4X4的矩阵键盘。在扫描按键的过程中&#xff0c;发现有按键触发信号后(不做去抖动)&#xff0c;待按键松开后&#xff0c;在数码管的第一位显示相应的数字:从左至右&…

STM32 RTC亚秒

rtc时钟功能实现&#xff1a;rtc模块在stm32内部&#xff0c;由电池或者主电源供电。如下图&#xff0c;需注意实现时仅需设置一次初始化。 1、stm32cubemx 代码生成界面设置&#xff0c;仅需开启时钟源和激活日历功能。 2、生成的代码,需要对时钟进行初始化&#xff0c;仅需…

【Linux】深入理解linux权限

&#x1f31f;&#x1f31f;作者主页&#xff1a;ephemerals__ &#x1f31f;&#x1f31f;所属专栏&#xff1a;Linux 目录 前言 一、权限是什么 二、用户和身份角色 三、文件属性 1. 文件属性表示 2. 文件类型 3. 文件的权限属性 四、修改文件的权限属性和角色 1. …

json格式,curl命令,及轻量化处理工具

一. JSON格式 JSON&#xff08;JavaScript Object Notation&#xff09; 是一种轻量级的数据交换格式。它基于一个子集的JavaScript编程语言&#xff0c;使用人类易于阅读的文本格式来存储和表示数据。尽管名字中有“JavaScript”&#xff0c;但JSON是语言无关的&#xff0c;几…

web直播弹幕抓取分析 signature

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 前言 最近遇到太多难点了卡了很久&am…