今天小编给大家分享一下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 Off
Adjusted settings for 42" TV:
Tv is On
Volume setting = 5
Channel setting = 3
Mode = cable
Input = TV
42" settings after using remote:
Tv is On
Volume setting = 7
Channel setting = 10
Mode = cable
Input = TV
58" 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