首页 > 后端开发 > C++ > 正文

C++如何实现类的友元函数和友元类

P粉602998670
发布: 2025-09-13 12:21:01
原创
558人浏览过
友元函数和友元类通过friend关键字在类内声明,允许非成员函数或类访问私有和保护成员,是对封装性的受控放松,适用于运算符重载、迭代器实现等需紧密协作的场景。

c++如何实现类的友元函数和友元类

C++中实现类的友元函数和友元类,本质上是为了在特定场景下,允许非成员函数或非成员类访问一个类的私有(private)或保护(protected)成员。这是一种“特权”访问机制,它打破了严格的封装性,但通常是为了实现更优雅、更高效或更符合逻辑的设计。它不是随意使用的,而是经过深思熟虑后,作为设计决策的一部分。

解决方案

要实现友元函数或友元类,关键在于在需要被访问的类(我们称之为“授予者”)内部,使用

friend
登录后复制
关键字来声明这些特殊的“朋友”。

实现友元函数

友元函数可以是全局函数、其他类的成员函数,甚至是函数模板。最常见的形式是全局函数。

立即学习C++免费学习笔记(深入)”;

  1. 声明友元函数: 在授予者类的定义内部,使用

    friend
    登录后复制
    关键字声明一个函数。

    #include <iostream>
    
    class MyClass {
    private:
        int privateData;
    
    public:
        MyClass(int data) : privateData(data) {}
    
        // 声明一个全局函数为友元函数
        friend void displayPrivateData(const MyClass& obj);
        // 也可以声明一个其他类的成员函数为友元
        // friend void AnotherClass::accessMyClass(const MyClass& obj);
    };
    
    // 定义友元函数
    void displayPrivateData(const MyClass& obj) {
        // 友元函数可以直接访问MyClass的私有成员
        std::cout << "Private data from friend function: " << obj.privateData << std::endl;
    }
    
    int main() {
        MyClass obj(100);
        displayPrivateData(obj); // 调用友元函数
        return 0;
    }
    登录后复制

    在这个例子里,

    displayPrivateData
    登录后复制
    函数虽然不是
    MyClass
    登录后复制
    的成员,但因为被声明为友元,所以它可以直接访问
    MyClass
    登录后复制
    对象的
    privateData
    登录后复制

实现友元类

友元类是指一个类(我们称之为“友元类”)的所有成员函数都可以访问另一个类(“授予者类”)的私有或保护成员。

  1. 声明友元类: 在授予者类的定义内部,使用

    friend
    登录后复制
    关键字声明另一个类。

    #include <iostream>
    
    // 前向声明,因为MyClass会用到FriendClass
    class FriendClass;
    
    class MyClass {
    private:
        int secretValue;
    
    public:
        MyClass(int val) : secretValue(val) {}
    
        // 声明FriendClass为友元类
        friend class FriendClass;
    };
    
    class FriendClass {
    public:
        void accessMyClassData(const MyClass& obj) {
            // FriendClass的成员函数可以直接访问MyClass的私有成员
            std::cout << "Secret value from FriendClass: " << obj.secretValue << std::endl;
        }
    
        void modifyMyClassData(MyClass& obj, int newValue) {
            // 友元类也可以修改私有成员
            obj.secretValue = newValue;
            std::cout << "Secret value modified to: " << obj.secretValue << std::endl;
        }
    };
    
    int main() {
        MyClass myObj(50);
        FriendClass friendObj;
    
        friendObj.accessMyClassData(myObj);
        friendObj.modifyMyClassData(myObj, 75);
        // 再次访问以确认修改
        friendObj.accessMyClassData(myObj);
        return 0;
    }
    登录后复制

    这里,

    FriendClass
    登录后复制
    被声明为
    MyClass
    登录后复制
    的友元。这意味着
    FriendClass
    登录后复制
    的任何成员函数,如
    accessMyClassData
    登录后复制
    modifyMyClassData
    登录后复制
    ,都可以自由地访问
    MyClass
    登录后复制
    对象的
    secretValue
    登录后复制
    ,即使它是私有的。

友元机制对C++封装性有何影响?

友元机制无疑是对C++核心原则——封装性的一种“特殊许可”或“受控突破”。从表面上看,它似乎与封装的精神背道而驰:封装旨在隐藏类的内部实现细节,只通过公共接口对外暴露功能。而友元,顾名思义,就是允许外部实体直接窥探并操作这些私有细节。

然而,这并非意味着友元是封装的敌人。在我看来,它更像是一种“必要之恶”,或者说,是一种精心设计的妥协。它提供了一种机制,允许开发者在某些特定、且经过深思熟虑的场景下,为了实现更紧密协作、更高性能或更符合特定设计模式的代码,而有选择性地放松封装。它不是一个开放的后门,而是一个带有明确权限的VIP通道。

堆友
堆友

Alibaba Design打造的设计师全成长周期服务平台,旨在成为设计师的好朋友

堆友 306
查看详情 堆友

使用友元,你明确地告诉编译器和阅读代码的人:“这个函数或类,虽然不是我的直接成员,但它与我关系非常紧密,我信任它,允许它访问我的私有部分。”这种信任是单向的,被声明为友元的一方并不会自动将其私有成员暴露给授予者。

所以,友元机制对封装性的影响,更确切地说,是一种有条件、有目的的封装放松。它要求开发者权衡便利性、性能与代码的长期可维护性、模块独立性。滥用友元无疑会破坏封装,使代码变得脆弱、难以理解和维护,因为私有成员的修改可能会影响到很多外部的友元函数或友元类。但若能谨慎使用,它能解决一些纯粹依赖公共接口难以优雅解决的问题。

C++友元函数与成员函数的区别与应用场景?

友元函数和成员函数虽然都能操作类的内部数据,但它们在C++的世界里扮演着截然不同的角色,有着本质的区别和各自最擅长的应用场景。

核心区别:

  1. 所有权与绑定:
    • 成员函数: 它是类的一部分,与类的实例紧密绑定。调用时通常需要通过一个对象(
      obj.memberFunction()
      登录后复制
      ),并且隐式地接收一个指向该对象的
      this
      登录后复制
      指针。它直接操作调用它的那个对象的成员。
    • 友元函数: 它不是类的一部分,可以是全局函数,也可以是另一个类的成员函数。它不绑定到任何特定的对象,也没有
      this
      登录后复制
      指针。要操作一个对象的成员,该对象必须作为参数显式地传递给友元函数。
  2. 访问权限:
    • 成员函数: 默认拥有对本类所有成员(私有、保护、公共)的访问权限。
    • 友元函数: 默认没有任何特殊权限,但一旦被类声明为
      friend
      登录后复制
      ,它就能访问该类的私有和保护成员。
  3. 命名空间与作用域
    • 成员函数: 处于类的作用域内,可以通过类名或对象名访问。
    • 友元函数: 如果是全局友元,则处于全局作用域;如果是另一个类的成员友元,则处于那个类的作用域。

应用场景:

  • 成员函数:

    • 绝大多数类操作: 任何直接操作对象自身状态或行为的功能,都应该优先设计为成员函数。例如,
      Student
      登录后复制
      类的
      enrollCourse()
      登录后复制
      BankAccount
      登录后复制
      类的
      deposit()
      登录后复制
    • 访问和修改私有数据: 通过公有的成员函数(如getter/setter)来间接访问和修改私有数据,是封装的常规手段。
    • 对象生命周期管理: 构造函数、析构函数、拷贝构造函数、赋值运算符等。
  • 友元函数:

    • 重载二元运算符: 这是友元函数最经典的应用场景之一,特别是当运算符的左操作数不是类类型时。例如,
      std::ostream
      登录后复制
      operator<<
      登录后复制
      (输出流操作符)。如果你想让
      std::cout << myObject;
      登录后复制
      这样的代码工作,而
      std::cout
      登录后复制
      std::ostream
      登录后复制
      类型,那么
      operator<<
      登录后复制
      就不能是
      MyObject
      登录后复制
      的成员函数(因为
      std::ostream
      登录后复制
      是左操作数)。此时,将其声明为友元函数是理想选择,它可以访问
      MyObject
      登录后复制
      的私有数据来格式化输出
      // 示例:重载输出流运算符
      class Point {
          int x, y;
      public:
          Point(int _x, int _y) : x(_x), y(_y) {}
          friend std::ostream& operator<<(std::ostream& os, const Point& p);
      };
      std::ostream& operator<<(std::ostream& os, const Point& p) {
          os << "(" << p.x << ", " << p.y << ")"; // 访问私有成员x, y
          return os;
      }
      登录后复制
    • 需要同时操作两个或多个类私有成员的函数: 假设有一个
      Swap
      登录后复制
      函数,需要交换两个不同类型对象(或者相同类型但不能通过公共接口直接交换)的私有成员。如果这两个类都将
      Swap
      登录后复制
      函数声明为友元,那么
      Swap
      登录后复制
      函数就能完成任务。这种场景相对少见,且通常可以通过其他设计模式(如访问者模式)来规避,但它确实是友元的一种潜在用途。
    • 全局工具函数: 当一个函数从逻辑上不属于任何一个类,但又需要访问某个类的私有数据来完成特定任务时,可以考虑友元函数。不过,这种设计需要特别谨慎,因为它可能暗示着类的职责划分不够清晰。

总的来说,成员函数是“内部人”,负责管理和操作自己的数据;友元函数是“被信任的外部人”,在特定任务中被授予特权。选择哪种,取决于函数与类之间关系的紧密程度和设计上的合理性。

友元类在实际项目中的常见应用模式是什么?

友元类在实际项目中的应用相对友元函数要少一些,因为它意味着一个类将自己的所有私有成员完全暴露给另一个类,这在封装性上是一个更大的让步。然而,在一些特定的设计模式和场景下,友元类能够提供非常简洁且高效的解决方案。

  1. 构建器(Builder)或工厂(Factory)模式的变体: 有时,一个类的构造过程非常复杂,或者需要根据不同的参数生成具有特定内部状态的对象,而这些状态又希望保持私有,不被外部直接修改。一个专门的构建器或工厂类可以被声明为目标类的友元,从而可以直接访问和设置目标类的私有成员,完成对象的精细化构造。这样,外部调用者只需与构建器/工厂交互,而无需了解目标类的内部结构,同时目标类的封装性在普通情况下依然得以保持。

  2. 迭代器(Iterator)模式的实现: 在实现自定义容器(如链表、树)时,迭代器类通常需要深入到容器的内部结构(例如,访问链表的

    Node
    登录后复制
    结构体,或者树的
    TreeNode
    登录后复制
    指针)来遍历元素。如果
    Node
    登录后复制
    TreeNode
    登录后复制
    是容器类的私有嵌套结构,那么迭代器类作为容器类的友元,就可以直接访问这些私有结构,从而高效地实现
    operator++
    登录后复制
    operator*
    登录后复制
    等迭代器操作,而无需容器提供大量的公共接口来暴露内部细节。

    // 简化示例:容器和迭代器
    template <typename T>
    class MyList {
    private:
        struct Node {
            T data;
            Node* next;
            Node(T d) : data(d), next(nullptr) {}
        };
        Node* head;
    
    public:
        // 前向声明迭代器
        class Iterator;
        // 声明Iterator为友元类
        friend class Iterator;
    
        MyList() : head(nullptr) {}
        void push_back(T val) {
            if (!head) {
                head = new Node(val);
            } else {
                Node* current = head;
                while (current->next) current = current->next;
                current->next = new Node(val);
            }
        }
        // ... 其他MyList成员
    
        class Iterator {
        private:
            Node* current_node;
        public:
            Iterator(Node* node) : current_node(node) {}
    
            T& operator*() { return current_node->data; }
            Iterator& operator++() {
                if (current_node) current_node = current_node->next;
                return *this;
            }
            bool operator!=(const Iterator& other) const {
                return current_node != other.current_node;
            }
        };
    
        Iterator begin() { return Iterator(head); }
        Iterator end() { return Iterator(nullptr); } // 结束标志
    };
    登录后复制

    在这个例子中,

    MyList::Iterator
    登录后复制
    作为
    MyList
    登录后复制
    的友元,可以直接访问
    MyList::Node
    登录后复制
    结构体,包括其
    data
    登录后复制
    next
    登录后复制
    成员,这对于实现高效的迭代器至关重要。

  3. 桥接模式(Bridge Pattern)或 PIMPL(Pointer to IMPLementation)惯用法: 在某些情况下,为了实现接口与实现的分离,或者为了减少编译依赖,我们会使用PIMPL。实现类(Impl类)通常是接口类(Public类)的私有成员或私有指针,但有时,Impl类可能需要反过来访问Public类的一些私有状态或调用其私有方法。在这种比较少见但确实存在的场景下,将Impl类声明为Public类的友元,可以简化这种双向的私有访问。

  4. 单元测试(Unit Testing)框架: 虽然不推荐,但有时在编写单元测试时,为了彻底测试一个类的所有功能,包括其私有方法的行为和私有成员的状态,一些测试框架或测试夹具(test fixture)可能会被声明为被测类的友元。这样,测试代码就可以直接访问私有部分,进行更深入的验证。然而,这通常被视为一种“测试污染”,更推荐的做法是通过公共接口间接测试私有行为,或者设计更细粒度的类,使得私有部分在另一个更小的类中成为公共部分进行测试。

这些应用模式共同的特点是,友元类与授予者类之间存在着非常紧密、不可分割的协作关系,这种关系超出了普通公共接口所能提供的范畴,且为了设计上的简洁、效率或特定模式的实现,这种封装的“放宽”是经过权衡和控制的。它不是为了方便而方便,而是为了解决特定的设计挑战。

以上就是C++如何实现类的友元函数和友元类的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号