• [杂记]关于C++中友元的一些理解


    友元旨在让函数或类访问另一个类中的成员, 下面根据友元的类型简单做一下整理.

    1. 普通函数作友元

    如果一个函数想要访问一个类中的(私有)成员, 则必须将它声明为该类的友元函数. 这种是最简单的情形, 例如下面的代码:

    class BaseClass{
    private:
        int value; 
    public: 
        BaseClass(int v) : value(v) {}
        int countOnes() const ;
        friend std::ostream& operator<<(std::ostream& os, BaseClass& bc);
    };
    
    int BaseClass::countOnes() const {
        int v = this->value;
        int num = 0;
        while (v){
            ++num; 
            v &= (v - 1);
        }
        return num;
       
    }
    
    std::ostream& operator<<(std::ostream& os, BaseClass& bc){
        os << bc.value << "   " << bc.countOnes() << "\n";
        return os;
    }
    
    
    int main(){
        BaseClass bc (5);
        std::cout << bc;
        system("pause");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    输出:

    5   2
    
    • 1

    以上代码中重载了<<运算符, 并输出成员value的值和二进制的value中包含多少个1. 这样就可以在类外调cout << .

    2. 一个类作为另一个类的友元

    如果两个类之间不是继承关系, 也不是一个包含另一个的关系, 而是只是共享某些特征, 可以考虑将一个类作为另一个类的友元. 例如, 我们希望B类能访问A类的成员, 那么B把A当朋友. 当然这种不是双向的(比如大多数感情一样), A并不能访问B的成员. 这种情形, 要在A类的声明中加入friend class B(声明的位置无关紧要), 说明B是A的友元.

    例如下面的代码:

    class FriendA {
    private:
        int money;
        friend class FriendB;
    public:
        FriendA(int m) : money(m) {}
    
    };
    
    class FriendB{
    private:
        int money;
    public:
        FriendB(int m) : money(m) {}
        bool AmIRich(FriendA& fa) {
            return this->money > fa.money;
        }
    };
    
    int main(){
        
        FriendA a (100);
        FriendB b (30);
    
        std::cout << b.AmIRich(a);
    
        system("pause");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    输出:

    0
    
    • 1

    这种情况下, FriendB能访问FriendA的所有成员. 但有时候我们不希望这样, 而是规定B的某些函数才能访问A的成员, 这样就有了成员函数作友元.

    3. 成员函数作友元

    这种情况略微复杂一些. 复杂在我们必须要弄明白类声明的顺序. 例如, 如果FriendB类的一个函数想要获取到FriendA类的一个成员, 如果这么写:

    class FriendA {
    private:
        int money;
        std::string address;
        friend std::string FriendB::arriveA(FriendA&);
    public:
        FriendA(int m, std::string add) : money(m), address(add) {}
    
    };
    
    class FriendB{
    private:
        int money;
    public:
        FriendB(int m) : money(m) {}
        std::string arriveA(FriendA& fa) {
            return fa.address;
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    就会报错:
    在这里插入图片描述
    为什么呢? 因为编译器看到FriendB::arriveA()函数的时候, 它不认识, 它不知道FriendB类里面有没有这个函数. 那我们如果把FriendB类放到FriendA类前面呢? 也不行, 因为FriendB类里函数定义用到了FriendA. 打破这种僵局的方法是,

    1. B的函数定义有A的参数, 则A的声明必须在B前面, 也即在B之前加上class A; 声明
    2. A中声明B的函数为A的友元, 则B的完整定义必须在A之前
    3. 在A的友元函数的定义中, 编译器必须知道该函数已经是A的友元, 因此要在类定义之后定义. 如果放在B中, 这时候编译器还不知道它是A的友元函数.

    总之, 声明定义的原则是: 编译器到这里的时候知道不知道.

    综上, 代码修改如下:

    class FriendA;  // 首先声明 B中的函数知道有A这么个东西
    
    class FriendB{
    private:
        int money;
    public:
        FriendB(int m) : money(m) {}
        std::string arriveA(FriendA& fa);  // 不能在此定义函数, 因为编译器不知道fa是可访问的
    };
    
    class FriendA {
    private:
        int money;
        std::string address;
        friend std::string FriendB::arriveA(FriendA&);  // 声明友元关系, 这时候编译器知道arriveA是B的一个成员了
    public:
        FriendA(int m, std::string add) : money(m), address(add) {}
    
    };
    
    std::string FriendB::arriveA(FriendA& fa){  // 在类外定义函数才行
        return fa.address;
    }
    
    int main(){
        std::string str = "Sdasdjw";
        FriendA fa (2, str);
        FriendB fb (2);
        std::cout << fb.arriveA(fa);
    
        system("pause");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    输出:

    Sdasdjw
    
    • 1

    4. 两个类互为友元类或者共有友元函数

    感情当然可以是双向的. 如果A是B的友元类, B也是A的友元类, 这种情况直接声明即可.但需要注意的是, 若先声明A后声明B, 则需要将A的涉及到B的函数放在B后面类外定义. 这样编译器才能知道B中的信息. 例如:

    class B;
    
    class A{
    private:
        int num;
        friend class B;
    public: 
        A(int n) : num(n) {}
        void showBName(B& b);
    };
    
    class B{
    private:
        char* name;
        friend class A;
    public: 
        B(char* n) {
            this->name = new char[strlen(n)];
            strcpy(this->name, n);
    
        }
        virtual ~B() { delete[] this->name; }
        void showANum(A& a){
            std::cout << a.num;
        }
        
    };
    
    void A::showBName(B& b){
        std::cout << b.name;
    }
    
    int main(){
        A a (233);
        
        B b ("sdajw");
    
        a.showBName(b);
        b.showANum(a);
    
        system("pause");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    共有友元函数情形相同, 最好也是在两个类后再定义.

    5. 模板类的友元函数

    模板类的友元函数大概分三类, 第一类是非模板友元函数. 也即当作最普通的函数看待, 我们需要对每个可能的模板类型都重载一个友元函数.

    第二类是约束模板友元函数, 也就是模板类的类型要与类的类型保持一致.

    第三类是非约束模板友元函数, 也即友元函数比较独立, 友元函数的类型与类的类型是独立(不同)的.

    5.1 非约束模板友元函数

    这样的友元在声明时, 需要额外再写一个template, 因为和类的template是不同的.

    template<typename T>
    class TestClass{
    private:
        T name;
    public:
        TestClass(T t) {this->name = t;}
        template<typename T1> friend void showName(T1&);
    };
    template<typename T1>
    void showName(T1& t){
        std::cout << t.name << "\n";
    }   
    
     
    int main(){
        TestClass<std::string> tc ("asdwqdw");
        showName(tc);
        system("pause");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    输出:

    asdwqdw
    
    • 1

    5.2 约束模板友元函数

    约束, 即友元函数的类型与模板的类型相同, 这时要在类中将模板具体化, 而且要提前声明, 如下:

    template<typename T> void showName(T&);  // 提前声明
    
    template<typename T>
    class TestClass{
    private:
        T name;
    public:
        TestClass(T t) {this->name = t;}
        friend void showName<>(TestClass<T>&); // <>是模板具体化, 将函数参数类型与模板绑定
    };
    
    template<typename T>
    void showName(T& cls){  // 正常声明即可 不要声明成TestClass&
        std::cout << cls.name << "\n";
    }
    
     
    int main(){
        TestClass<std::string> tc ("asdwqdw");
        showName(tc);
        system("pause");
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    输出:

    asdwqdw
    
    • 1
  • 相关阅读:
    openmmlab教程3-MMSeg 使用
    【微服务容器化】第三章-Docker容器的数据卷是什么
    【Linux】:常见指令理解(3)
    Linux 可执行文件瘦身指令 strip 使用示例
    【笔记】离线Ubuntu20.04+mysql 5.7.36 + xtrabackup定时增量备份脚本
    LeetCode994. 腐烂的橘子(C++中等题)
    在哪里可以下载大连大学2023考研真题大纲?学校论坛可以吗
    Linux系统离线安装Python
    coppercam入门手册[6]
    面试题目总结(1) https中间人攻击,ConcurrentHashMap的原理 ,serialVersionUID常量,redis单线程,
  • 原文地址:https://blog.csdn.net/wjpwjpwjp0831/article/details/126884737