今天小编给大家分享一下C++中友元类和嵌套类如何使用的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。
前言
友元这个词,在学习类的时候肯定接触过,但是当时我们只用了很多友元函数。
友元有三种:
友元函数
友元类
友元类方法
类并非只能拥有友元函数,也可以将类作为友元。在这种情况下,友元类的所以方法都能访问原始类的私有成员和保护成员。另外,也可以做更严格的限制,只将特定的成员函数指定为另一个类的友元。
1. 友元类
假如我们有两个类:
Tv
电视机类,Remote
遥控器类。那么这两个类是什么关系呢?既不是has-a关系,也不是 is-a关系,但是我们知道遥控器可以控制电视机,那么遥控器必须能够访问电视机的私有或保护数据。所以,遥控器就是电视机的友元。类似于友元函数的声明,友元类的声明:
friend class Remote;
友元声明可以位于公有、私有或保护部分,其所在的位置无关紧要。
下面是代码实现:
//友元类1.h
#ifndef TV_H_
#define TV_H_
class Tv
{
private:
int state;//On or Off
int volume;//音量
int maxchannel;//频道数
int channel;//频道
int mode;//有线还是天线,Antenna or Cable
int input;//TV or DVD
public:
friend class Remote;
enum{Off,On};
enum{MinVal,MaxVal=20};
enum{Antenna,Cable};
enum{TV,DVD};
Tv(int s=Off,int mc=125):state(s),volume(5),maxchannel(mc),
channel(2),mode(Cable),input(TV){}
void onoff(){state=(state==On)?Off:On;}
bool ison() const{return state==On;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_mode(){mode=(mode==Antenna)?Cable:Antenna;}
void set_input(){input=(input==TV)?DVD:TV;}
void settings() const;//display all settings
};
class Remote
{
private:
int mode;//控制TV or DVD
public:
Remote(int m=Tv::TV):mode(m){};
bool volup(Tv & t){return t.volup();}
bool voldown(Tv & t){return t.voldown();}
void onoff(Tv &t){t.onoff();}
void chanup(Tv &t){t.chanup();}
void chandown(Tv &t){t.chandown();}
void set_chan(Tv &t,int c){t.channel=c;}
void set_mode(Tv &t){t.set_mode();}
void set_input(Tv &t){t.set_input();}
};
#endif
//友元类1.cpp
#include"友元类1.h"
#include<iostream>
bool Tv::volup()
{
if(volume<MaxVal)
{
volume++;
return true;
}
else
return false;
}
bool Tv::voldown()
{
if(volume>MinVal)
{
volume--;
return true;
}
else
return false;
}
void Tv::chanup()
{
if(channel<maxchannel)
channel++;
else
channel=1;
}
void Tv::chandown()
{
if(channel>1)
channel--;
else
channel=maxchannel;
}
void Tv::settings() const
{
using std::cout;
using std::endl;
cout<<"Tv is "<<(state==Off? "Off":"On")<<endl;
if(state==On)
{
cout<<"Volume setting = "<<volume<<endl;
cout<<"Channel setting = "<<channel<<endl;
cout<<"Mode = "
<<(mode==Antenna?"antenna":"cable")<<endl;
cout<<"Input = "
<<(input==TV?"TV":"DVD")<<endl;
}
}
//友元类1main.cpp
#include<iostream>
#include"友元类1.h"
int main()
{
using std::cout;
Tv s42;
cout<<"Initial setting for 42" TV:
";
s42.settings();
s42.onoff();
s42.chanup();
cout<<"
Adjusted settings for 42" TV:
";
s42.settings();
Remote grey;
grey.set_chan(s42,10);
grey.volup(s42);
grey.volup(s42);
cout<<"
42" settings after using remote:
";
s42.settings();
Tv s58(Tv::On);
s58.set_mode();
grey.set_chan(s58,28);
cout<<"
58" settings:
";
s58.settings();
return 0;
}
PS D:studyc++path_to_c++> g++ -I .include -o 友元类1 .友元类1.cpp .友元类1main.cpp
PS D:studyc++path_to_c++> .友元类1.exe
Initial setting for 42" TV:
Tv is OffAdjusted settings for 42" TV:
Tv is On
Volume setting = 5
Channel setting = 3
Mode = cable
Input = TV42" settings after using remote:
Tv is On
Volume setting = 7
Channel setting = 10
Mode = cable
Input = TV58" settings:
Tv is On
Volume setting = 5
Channel setting = 28
Mode = antenna
Input = TV
总之,友元类和友元函数很类似,不需要过多说明了。
2. 友元成员函数
在上面那个例子中,我们知道大部分
Remote
方法都是用Tv
类的公有接口实现的。这意味着这些方法不是真正需要作为友元。事实上,只有一个直接访问Tv
的私有数据的Remote
方法即Remote::chan()
,因此它才是唯一作为友元的方法。我们可以选择仅让特定的类成员成为另一个类的友元,而不必让整个类成为友元,但这样做会有一些麻烦。让
Remote::chan()
成为Tv
类的友元的方法是,在Tv
类声明中将其声明为友元:class Tv
{
friend void Remote::set_chan(Tv & t,int c);
...
}
但是,编译器能处理这条语句,它必须知道
Remote
的定义。否则,它就不知道Remote::set_chan
是什么东西。所以我们必须把Remote
的声明放到Tv
声明的前面。但是Remote
声明中同样提到了TV
类,那么我们必须把TV
声明放到Remote
声明的前面。这就发生了循环依赖。我们得使用 前向声明(forward declaration) 来解决这一问题。class Tv;
class Remote{...};
class Tv{...};
但是,还有一点麻烦需要解决:
Remote
的类声明中不能直接给出成员函数的定义了,因为这些函数会访问Tv
类成员,而Tv
类的成员的声明是Remote
类的后面的。那么我们必须在Remote
的类声明外给出方法定义。代码实现:
//友元成员函数1.h
#ifndef TVFM_H_
#define TVFM_H_
class Tv;
class Remote
{
public:
enum{Off,On};
enum{MinVal,MaxVal=20};
enum{Antenna,Cable};
enum{TV,DVD};
private:
int mode;//控制TV or DVD
public:
Remote(int m=TV):mode(m){};
bool volup(Tv & t);
bool voldown(Tv & t);
void onoff(Tv &t);
void chanup(Tv &t);
void chandown(Tv &t);
void set_chan(Tv &t,int c);
void set_mode(Tv &t);
void set_input(Tv &t);
};
class Tv
{
private:
int state;//On or Off
int volume;//音量
int maxchannel;//频道数
int channel;//频道
int mode;//有线还是天线,Antenna or Cable
int input;//TV or DVD
public:
friend void Remote::set_chan(Tv &t,int c);
enum{Off,On};
enum{MinVal,MaxVal=20};
enum{Antenna,Cable};
enum{TV,DVD};
Tv(int s=Off,int mc=125):state(s),volume(5),maxchannel(mc),
channel(2),mode(Cable),input(TV){}
void onoff(){state=(state==On)?Off:On;}
bool ison() const{return state==On;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_mode(){mode=(mode==Antenna)?Cable:Antenna;}
void set_input(){input=(input==TV)?DVD:TV;}
void settings() const;//display all settings
};
inline bool Remote::volup(Tv & t){return t.volup();}
inline bool Remote::voldown(Tv & t){return t.voldown();}
inline void Remote::onoff(Tv &t){t.onoff();}
inline void Remote::chanup(Tv &t){t.chanup();}
inline void Remote::chandown(Tv &t){t.chandown();}
inline void Remote::set_chan(Tv &t,int c){t.channel=c;}
inline void Remote::set_mode(Tv &t){t.set_mode();}
inline void Remote::set_input(Tv &t){t.set_input();}
#endif
之前我们说过,内联函数的链接性是内部的,这就意味著函数定义必须在使用函数的文件中。在上面的代码中,内联函数的定义位于头文件中。当然你也可以将定义放在实现文件中,但必须删除关键字
inline
,这样函数的链接性将是外部的。还有就是,我们直接让整个
Remote
类成为友元并不需要前向声明,因为友元语句已经指出Remote
是一个类:
friend class Remote;
。总之,不推荐使用友元成员函数,使用友元类完全可以达到相同的目的。
3. 其他友元关系
3.1 成为彼此的友元类
还是电视机和遥控器的例子,我们知道遥控器能控制电视机,但是我告诉你,现代电视机也是可以控制遥控器的。例如,我们现在可以在电视上玩角色扮演游戏,当你控制的角色从高处落入水中时,遥控器(手柄)会发出振动模拟落水感。那么,遥控器是电视机的友元,电视机也是遥控器的友元,那么它们互为友元。
class Tv
{
friend class Remote;
public:
void buzz(Remote & r);
...
};
class Remote
{
friend class Tv;
public:
void bool volup(Tv &t){t.volup();}
...
};
inline void Tv::buzz(Remote &r)
{
...
}
这里
buzz
函数的定义必须放到Remote
类声明的后面,因为buzz
的定义中会使用到Remote
的成员。3.2 共同的友元
使用友元的另一种情况是,函数需要访问两个类的私有数据,那么必须这样做:函数既是一个类的友元也是另一个类的友元.
例如,有两个类
Analyzer
和Probe
,我们需要同步它们的时间成员:class Analyzer;
class Probe
{
friend void sync(Analyzer & a,const Probe &p);
friend void sync(Probe &p,const Analyzer &a);
};
class Probe
{
friend void sync(Analyzer & a,const Probe &p);
friend void sync(Probe &p,const Analyzer &a);
};
inline void sync(Analyzer & a,const Probe &p)
{
...
}
inline void sync(Probe &p,const Analyzer &a)
{
...
}
4. 嵌套类
在C++中我们可以将类声明放在另一个类中。在另一个类中声明的类被称为嵌套类。
实际上,嵌套类很简单,它的原理和类中声明结构体、常量、枚举、
typedef
、名称空间是一样的,这些技术我们一直都在使用。对类进行嵌套和包含是不一样的。包含意味著将类对象作为另一个类的成员,而对类进行嵌套不创建类成员,而是定义了一种类型,该类型仅在包含嵌套类的类中有效。
一般来说我们使用嵌套类是为了帮助实现另一个类,并避免名称冲突
嵌套类的作用域和访问控制
作用域
如果嵌套类是在另一个类的私有部分声明的,那么只能在后者的类作用域中使用它,派生类以及外部世界无法使用它。
如果嵌套类是在另一个类的保护部分声明的,那么只能在后者、后者的派生类的类作用域中使用该嵌套类,外部世界无法使用它。
如果嵌套类是在另一个类的公有部分声明的,那么能在后者、后者的派生类和外部世界中使用它。
class Team
{
public:
class Coach{...}
...
};
上面的
Coach
就是一个公有部分的嵌套类,那么我们可以这样:Team::Coach forhire;
总之,嵌套类的作用域和类中声明结构体、常量、枚举、
typedef
、名称空间是一样。但是对于枚举量来说,我们一般把它放在类的公有部分,例如ios_base
类中的各种格式常量:ios_base::showpoint
等。访问控制
嵌套类的访问控制和常规类是一模一样的,嵌套类也有
public
,private
,protected
,只有公有部分对外部世界开放。例如:
class A
{
class B
{
private:
int num;
public
void foo();
};
};
则在A的类作用域中,可以创建B对象,并使用
B.foo()
方法。看看一个类模板中使用嵌套类的例子:
#ifndef QUEUETP_H_
#define QUEUETP_H_
template<typename Item>
class QueueTP
{
private:
enum{Q_SIZE=10};
class Node
{
public:
Item item;
Node *next;
Node(const Item & i):item(i),next(0){}
};
Node *front;
Node *rear;
int items;
const int qsize;
QueueTP(const QueueTP &q):qsize(0){}//抢占定义,赋值构造函数
QueueTP & operator=(const QueueTP &q){return *this;}//抢占定义
public:
QueueTP(int qs=Q_SIZE):qsize(qs)
{
front = rear =0;
items=0;
}
~QueueTP()
{
Node* temp;
while (front !=0)
{
temp=front;
front=front->next;
delete temp;
}
}
bool isempty() const
{
return items==0;
}
bool isfull() const
{
return items==qsize;
}
int queuecount() const
{
return items;
}
bool enqueue(const Item & item)
{
if(isfull())
return false;
Node * add = new Node(item);
items++;
if(front==0)
front=add;
else
rear->next=add;
rear=add;
return true;
}
bool dequeue(Item &item)
{
if(front==0)
return 0;
item=front->item;
items--;
Node * temp=front;
front=front->next;
delete temp;
if(items==0)
rear=0;
return true;
}
};
#endif