友元类
友元类的使用
友元不仅仅适合于友元函数,还可以将类作为友元,在这种情况下,友元类的所有方法都可以访问原始类的私有方法和保护成员,什么时候去使用友元类呢?
两个类之间不存在包含和所属关系,但是我们某一个类中的方法想要调用另一个类中的私有成员和保护成员,这就需要使用到友元类
我们可以通过一个例子来看看友元类的用法,这是例子是电视机和遥控器的例子,遥控器并不是电视机的一部分,它们也不是包含关系,但是遥控器却可以改变电视机的状态,因此我们可以将遥控器类作为电视机类的一个友元
我们可以先编写一个电视机的类作为原始类,其中包含电视机开关状态,频道,音量以及信号源,然后再编写一个遥控器类作为电视机类的友元类,这样遥控器类的所有方法都可以访问电视机类的私有成员和保护成员,这就是友元类的方法
我们可以编写一个测试程序,看看友元类能否修改我们原始类中的私有成员和保护成员
前向声明及限定友元
在我们上面的程序中,我们的友元类中只有void set_channel(Tv &t , int m){t.channel = m;}这个成员函数是去访问了原始类的私有成员,而其他的成员函数都是通过访问原始类的公有接口去访问原始类的私有成员,因此我们可以只将遥控器类中的void set_channel函数定义为友元,而其他的不需要,因为其他类都是通过公有接口去访问的私有数据
即我们只需要友元类中的set_channel函数定义为友元函数,而其他的函数不需要,因此,我们可以进行如下的修改
改写之后的头文件与之前的头文件相比,改写后的Remote类中只有一个函数是Tv类的友元,而之前的头文件中, Remote类中的所有成员函数都是Tv类的友元
互为友元的关系
假设我们技术的进步,不光能通过遥控器控制电视机,电视机也是反馈信息给我们的遥控器,让它们成为一种交互式的器件,这样我们就可以通过C++的编程,也就是可以通过让类彼此成为对方的友元来实现这个功能,即不光Remote是Tv类的友元,Tv类也是Remote类的友元,对于这样的情况,我们需要使用Remote对象的Tv方法,就需要将Tv类的原型放在Remote类的声明之前,并且将Tv类的定义放在Remote类声明之后,让编译器能够有足够的信息去编译这个方法,即如下所示
class Tv;
{
friend class Remote;
public:
void buzz(Remote & rm);
....
};
class Remote
{
friend class Tv;
public:
void bool volup(Tv & t){t.volup();}
....
};
inline void Tv::buzz(Remote & rm)
{
......
}
由于Remote类的声明在Tv类声明的后面,所以可以在Remote类声明中定义
void bool volup(Tv & t){t.volup();,但是Tv中的buzz方法的定义必须在Remote类的后面
嵌套类
在C++中,我们可以将一个类的声明放在另一个类中,在另一个类中声明的类就被称为嵌套类,它通过提供提供新的类型类作用域来避免名称的混乱,包含类的成员函数可以创建和使用被嵌套类的对象,仅仅当类的声明位于公有部分时,才能在包含类的外面使用嵌套类,并且必须使用作用域解析运算符,对类进行嵌套与对类的包含不同,包含意味着将类对象作为另一个类的成员,而对类进行嵌套不创建类成员,而仅仅是定义了一种类型,该类型仅仅在包含嵌套类声明中的类中有效
我们可以使用一个队列的例子来看看嵌套类的使用方法
// 模板类定义,用于实现队列结构
template <class Item>
class Queue
{
private:
// 设置默认队列大小为10
enum{Q_SIZE = 10};
// 内部类,定义队列节点结构
class Node // 定义节点类
{
public:
// 节点存储的数据
Item item;
// 指向下一个节点的指针
Node *next;
// 构造函数,用传入的参数初始化节点
Node(const Item &t) : item(t), next(NULL){}
};
// 队列头节点
Node *front;
// 队列尾节点
Node *rear;
// 当前队列中的元素数量
int items;
// 队列的最大容量
const int qsize;
public:
// 构造函数,接受队列大小参数,默认大小为Q_SIZE
Queue(int qs = Q_SIZE);
// 析构函数,用于清理队列中的所有节点
~Queue();
// 检查队列是否为空
bool isempty() const;
// 检查队列是否已满
bool isfull() const;
// 返回当前队列中的元素数量
int queuecount() const;
// 将一个元素添加到队列尾部
bool enqueue(const Item &item);
// 从队列头部移除一个元素
bool dequeue(Item &item);
};
在这串代码中,我们使用了嵌套类的方法,使用一个类作为队列的节点,嵌套类的优点在于它们可以封装在外部类的作用域内,同时又能够直接访问外部类的私有成员,在这个例子中,Node
类被用来表示队列中的节点,它与 Queue
类紧密相关,因此适合作为其内部类