Skip to content

友元

类的主要特点之一是数据隐藏,即类的私有成员无法在类的外部(作用域之外)访问。但是,有时候需要在类的外部访问类的私有成员怎么办?

解决方法是使用友元函数,友元函数是一种特权函数,C++允许这个特权函数访问私有成员。这一点从现实生活中也可以很好的理解:

比如你的家有客厅、有你的卧室,那么你的客厅是Public的,所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去。但是呢,你也可以允许你的闺蜜或者好基友进去。又比如:部门内部的人员只对部门领导负责,任何问题都要请示领导后才执行。若两个部门需要协同工作,领导可能会安排相应的对接人员,对接人员直接商讨事物会提高办事效率,对接人员就相当于友元,但是假如对接人员把握不好方向,领导又没有及时干预,则可能造成一定的后果。

程序员可以把一个全局函数、某个类中的成员函数、甚至整个类声明为友元。

语法

  • friend关键字只出现在声明处
  • 其他类、类成员函数、全局函数都可声明为友元
  • 友元函数不是类的成员,不带this指针
  • 友元函数可访问对象任意成员属性,包括私有属性
示例代码01
cpp
#include <iostream>
using namespace std;

// 1、全局函数作为友元
class Room {
    friend void GoodGay(Room& room);
public:
    Room() {
        this->m_BedRoom = "卧室";
        this->m_SittingRoom = "客厅";
    }
public:
    string m_SittingRoom;
private:
    string m_BedRoom;
};

void GoodGay(Room &room) {
    cout << "好基友正在访问" << room.m_SittingRoom << endl;
    cout << "好基友正在访问" << room.m_BedRoom << endl;
}

int main(int argc, char *argv[]) {
    Room r1;
    GoodGay(r1);

    return 0;
}

程序输出:

shell
好基友正在访问客厅
好基友正在访问卧室
示例代码02
cpp
#include <iostream>
using namespace std;

// 2、类作为友元
class Room {
    friend class GoodGay;
public:
    Room() {
        this->m_BedRoom = "卧室";
        this->m_SittingRoom = "客厅";
    }
public:
    string m_SittingRoom;
private:
    string m_BedRoom;
};

class GoodGay {
public:
    GoodGay() {
        this->m_room = new Room;
    }

    void visit() {
        cout << "好基友正在访问" << this->m_room->m_SittingRoom << endl;
        cout << "好基友正在访问" << this->m_room->m_BedRoom << endl;
    }

private:
    Room* m_room;
};

int main(int argc, char *argv[]) {
    GoodGay gg;
    gg.visit();

    return 0;
}

程序输出:

shell
好基友正在访问客厅
好基友正在访问卧室
示例代码03
cpp
#include <iostream>
using namespace std;

// 3、类内成员函数作为友元
class Room; // 先声明类,在后部代码实现类

class GoodGay {
public:
    GoodGay();  // 先声明成员函数,在类外进行函数实现

    void visit();
    void visit2();
    Room* m_room;
};

class Room {
    friend void GoodGay::visit();   // 声明另一个类中的成员函数为友元

public:
    Room();

public:
    string m_SittingRoom;

private:
    string m_BedRoom;
};

Room::Room() {
    this->m_BedRoom = "卧室";
    this->m_SittingRoom = "客厅";
}

GoodGay::GoodGay() {
    this->m_room = new Room;
}
void GoodGay::visit() {
    cout << "好基友正在访问" << this->m_room->m_SittingRoom << endl;
    cout << "好基友正在访问" << this->m_room->m_BedRoom << endl;
}
void GoodGay::visit2() {
    cout << "好基友正在访问" << this->m_room->m_SittingRoom << endl;
    //cout << "好基友正在访问" << this->m_room->m_BedRoom << endl;
   // 没有被声明为友元的进行private访问会导致程序错误
}

int main(int argc, char *argv[]) {
    GoodGay gg;
    gg.visit();
    gg.visit2();

    return 0;
}

程序输出:

shell
好基友正在访问客厅
好基友正在访问卧室
好基友正在访问客厅

友元类注意

  1. 友元关系不能被继承
  2. 友元关系是单向的,类A是类B的朋友,但类B不一定是类A的朋友。
  3. 友元关系不具有传递性。类B是类A的朋友,类C是类B的朋友,但类C不一定是类A的朋友。

思考: C++是纯面向对象的吗?

如果一个类被声明为friend,意味着它不是这个类的成员函数,却可以修改这个类的私有成员,而且必须列在类的定义中,因此他是一个特权函数。C++不是完全的面向对象语言,而只是一个混合产品。增加friend关键字只是用来解决一些实际问题,这也说明这种语言是不纯的。毕竟C++设计的目的是为了实用性,而不是追求理想的抽象。

--- Thinking in C++

课堂练习

请编写电视机类,电视机有开机和关机状态、有音量、有频道,提供音量操作的方法、频道操作的方法。由于电视机只能逐一调整频道、不能指定频道,增加遥控类,遥控类除了拥有电视机已有的功能,再增加根据输入调台功能。

提示:遥控器可作为电视机类的友元类。

点击查看答案
cpp
#include <iostream>
using namespace std;

class Remote;
class Television {
    friend class Remote;
public:
    enum { On, Off }; //电视状态
    enum { minVol, maxVol = 100 }; //音量从0到100
    enum { minChannel = 1, maxChannel = 255 }; //频道从1到255

    Television() {
        mState = Off;
        mVolume = minVol;
        mChannel = minChannel;
    }

    //打开电视机
    void OnOrOff() {
        this->mState = (this->mState == On ? Off : On);
    }

    //调高音量
    void VolumeUp() {
        if (this->mVolume >= maxVol) {
            return;
        }
        this->mVolume++;
    }

    //调低音量
    void VolumeDown() {
        if (this->mVolume <= minVol) {
            return;
        }
        this->mVolume--;
    }

    //更换电视频道
    void ChannelUp() {
        if (this->mChannel >= maxChannel) {
            return;
        }
        this->mChannel++;
    }

    void ChannelDown() {
        if (this->mChannel <= minChannel) {
            return;
        }
        this->mChannel--;
    }

    //展示当前电视状态信息
    void ShowTeleState() {
        cout << "开机状态:" << (mState == On ? "已开机" : "已关机") << endl;
        if (mState == On) {
            cout << "当前音量:" << mVolume << endl;
            cout << "当前频道:" << mChannel << endl;
        }
        cout << "-------------" << endl;
    }

private:
    int mState; //电视状态,开机,还是关机
    int mVolume; //电视机音量
    int mChannel; //电视频道
};

//电视机调台只能一个一个的调,遥控可以指定频道
//电视遥控器
class Remote {
public:
    Remote(Television* television) {
        pTelevision = television;
    }

public:
    void OnOrOff() {
        pTelevision->OnOrOff();
    }

    //调高音量
    void VolumeUp() {
        pTelevision->VolumeUp();
    }

    //调低音量
    void VolumeDown() {
        pTelevision->VolumeDown();
    }

    //更换电视频道
    void ChannelUp() {
        pTelevision->ChannelUp();
    }
    void ChannelDown() {
        pTelevision->ChannelDown();
    }

    //设置频道 遥控新增功能
    void SetChannel(int channel) {
        if (channel < Television::minChannel || channel > Television::maxChannel) {
            return;
        }
        pTelevision->mChannel = channel;
    }

    //显示电视当前信息
    void ShowTeleState() {
        pTelevision->ShowTeleState();
    }
private:
    Television* pTelevision;
};

//直接操作电视
void test01() {
    Television television;
    television.ShowTeleState();
    television.OnOrOff(); //开机
    television.VolumeUp(); //增加音量+1
    television.VolumeUp(); //增加音量+1
    television.VolumeUp(); //增加音量+1
    television.VolumeUp(); //增加音量+1
    television.ChannelUp(); //频道+1
    television.ChannelUp(); //频道+1
    television.ShowTeleState();
}

//通过遥控操作电视
void test02() {
    //创建电视
    Television television;
    //创建遥控
    Remote remote(&television);
    remote.OnOrOff();
    remote.ChannelUp();//频道+1
    remote.ChannelUp();//频道+1
    remote.ChannelUp();//频道+1
    remote.VolumeUp();//音量+1
    remote.VolumeUp();//音量+1
    remote.VolumeUp();//音量+1
    remote.VolumeUp();//音量+1
    remote.ShowTeleState();
}

int main(int argc, char *argv[]) {
    test01();
    test02();

    return 0;
}

程序输出:

shell
开机状态:已关机
-------------
开机状态:已开机
当前音量:4
当前频道:3
-------------
开机状态:已开机
当前音量:4
当前频道:4
-------------

强化训练

数组类封装

cpp
#ifndef MYARRAY_H
#define MYARRAY_H

class MyArray {
public:
    //无参构造函数,用户没有指定容量,则初始化为100
    MyArray();
  
    //有参构造函数,用户指定容量初始化
    explicit MyArray(int capacity);
  
    //用户操作接口
    //根据位置添加元素
    void SetData(int pos, int val);
  
    //获得指定位置数据
    int GetData(int pos);
  
    //尾插法
    void PushBack(int val);
  
    //获得长度
    int GetLength();
  
    //析构函数,释放数组空间
    ~MyArray();
private:
    int mCapacity; //数组一共可容纳多少个元素
    int mSize; //当前有多少个元素
    int* pAdress; //指向存储数据的空间
};

#endif
#include"MyArray.h"

MyArray::MyArray() {
    this->mCapacity = 100;
    this->mSize = 0;
    //在堆开辟空间
    this->pAdress = new int[this->mCapacity];
}

//有参构造函数,用户指定容量初始化
MyArray::MyArray(int capacity) {
    this->mCapacity = capacity;
    this->mSize = 0;
    //在堆开辟空间
    this->pAdress = new int[capacity];
}

//根据位置添加元素
void MyArray::SetData(int pos, int val) {
    if (pos < 0 || pos > mCapacity - 1) {
        return;
    }
    pAdress[pos] = val;
}

//获得指定位置数据
int MyArray::GetData(int pos) {
    return pAdress[pos];
}

//尾插法
void MyArray::PushBack(int val) {
    if (mSize >= mCapacity) {
        return;
    }
    this->pAdress[mSize] = val;
    this->mSize++;
}

//获得长度
int MyArray::GetLength() {
    return this->mSize;
}

//析构函数,释放数组空间
MyArray::~MyArray() {
    if (this->pAdress != nullptr) {
        delete[] this->pAdress;
    }
}
#include <iostream>
using namespace std;

#include"MyArray.h"

void test() {
    //创建数组
    MyArray myarray(50);

    //数组中插入元素
    for (int i = 0; i < 50; i++) {
        //尾插法
        myarray.PushBack(i);
        //myarray.SetData(i, i);
    }

    //打印数组中元素
    for (int i = 0; i < myarray.GetLength(); i++) {
        cout << myarray.GetData(i) << " ";
    }
    cout << endl;
}

int main(int argc, char *argv[]) {
    test();

    return 0;
}

程序输出:

面试题

内部类访问权限问题

以下几个问题的前提是:B是A的内部类

Q1:B能否访问A的private成员?

默认情况下,B不能直接访问A的private成员。C++中内部类(嵌套类)与外部类的关系是逻辑包含,但不自动拥有友元权限。若需访问,需显式声明友元关系

Q2:A能否访问B的private成员?

A默认无法访问B的private成员。B的私有成员仅对自身可见,除非B将A声明为友元(friend class A;)或提供公有访问接口。

Q3:有什么办法能让B访问A的私有成员?

方法1:友元声明
在A类中声明B为友元类:

cpp
class A {
private:
    int privateData;
    friend class B; // B成为A的友元
public:
    class B {
        void accessA(A& a) {
            a.privateData = 42; // 可直接访问A的私有成员
        }
    };
};

代价:友元会破坏封装性,允许B直接操作A的私有成员。

方法2:间接访问(推荐,不破坏封装)
A通过公有或受保护的接口暴露私有成员,B通过接口访问:

cpp
class A {
private:
    int privateData;
public:
    int getData() const { return privateData; } // 公有接口
    class B {
        void accessA(A& a) {
            int data = a.getData(); // 通过接口访问
        }
    };
};

此方式通过设计模式(如代理模式)维护封装性,符合面向对象原则。

Q4:友元破坏了封装性,那么不破坏封装性的情况下怎么做?

接口隔离原则:A提供有限的公有/受保护接口,仅暴露必要的功能,避免直接暴露私有数据。

中间层设计:通过中间类或接口类封装A的私有操作,B通过中间层间接调用。

依赖注入:B持有A的引用,通过A的公有方法操作数据,而非直接访问私有成员。

Q5:如何在B对象内调用A的成员函数?

方式1:传递A的实例
在B中保存A的引用或指针,通过实例调用:

cpp
class A {
public:
    class B {
        A& parent; // 保存A的引用
    public:
        B(A& a) : parent(a) {}
        void callA() {
            parent.aPublicFunc(); // 调用A的公有函数
        }
    };
};

方式2:友元关系
若B需要调用A的私有函数,需将B声明为A的友元:

cpp
class A {
private:
    void privateFunc() {}
    friend class B; // B可调用A的私有函数
public:
    class B {
        void callA(A& a) {
            a.privateFunc(); // 直接调用A的私有函数
        }
    };
};

方式3:静态成员函数
若A的函数为静态,B可通过类名直接调用:

cpp
class A {
public:
    static void staticFunc() {}
    class B {
        void callA() {
            A::staticFunc(); // 调用静态函数
        }
    };
};

总结

  • 权限控制:内部类与外部类默认无特殊访问权限,需通过友元或接口设计实现交互。
  • 封装性维护:优先通过公有接口和设计模式实现交互,避免滥用友元。
  • 代码示例:结合实例引用、友元声明和接口设计,灵活应对不同场景。

此回答覆盖了权限机制、设计原则和代码实践,兼顾理论深度与工程实用性,符合C++后端开发的高分标准。