①友元
1.介绍
前边的章节中,我们将友元函数用作类的扩展接口中,我们也可以将类作为友元。在这种情况下,友元类的所有方法都可以访问原始类的私有成员和保护成员。也可以做更严格的定义,只让特定的成员函数指定为另一个类的友元。哪些函数、成员函数或类为友元是由类定义的。
2.友元类
什么时候希望一个类成为另一个类的友元呢?当二者可以相互影响或单方面影响的时候。例如,电视机和遥控(既不是Is-a,也不是has-a),而是遥控器可以影响电视(换台,调音等)。我们来实现这个例子。
#ifndef TV_H_
#define TV_H_
class Tv
{
private:
int state; //on or off
int volume;
int maxchannel;
int channel; //current channel seeting
int mode; //broadcast or cable
int input; //TV or DVD
public:
enum { Off, On };
enum { Minval, Maxval = 20 };
enum { Antenna, Cable };
enum { TV, DvD };
Tv(int s = Off, int mc = 125) :state(s), maxchannel(mc), volume(5), channel(2), mode(Cable), input(TV) {}
void onoff() { state = (state == Off) ? On : Off; } //切换状态
bool ison() { 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;
friend class Remote;
};
class Remote
{
private:
int mode;
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 channup(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, int c) { t.set_mode(); }
void set_input(Tv& t, int c) { t.set_input(); }
};
#endif
友元类声明:
friend class Remote;
#include<iostream>
#include"TV.h"
bool Tv::volup()
{
if (volume < Maxval)
{
volume++;
return true;
}
else
return false;
}
bool Tv::voldown()
{
if (volume < Minval)
return false;
volume--;
return true;
}
void Tv::chanup()
{
if (channel < maxchannel)
{
channel++;
}
else
channel = 1;
}
void Tv::chandown()
{
if (channel > 1)
{
channel--;
}
else
channel = maxchannel;
}
void Tv::settings() const
{
std::cout << "TV is " << (state == Off ? "Off" : "On") << std::endl;
if (state == On)
{
std::cout << "Volume setting = " << volume << std::endl;
std::cout << "Channel setting = " << channel << std::endl;
std::cout << "Mode = " << (mode == Antenna ? "Antenna" : "cabel") << std::endl;
std::cout << "input = " << (input == TV ? "TV" : "DvD") << std::endl;
}
}
#include<iostream>
#include"TV.h"
int main()
{
using std::cout;
Tv s42;
cout << "Initial settings for 42\"TV\"\n";
s42.settings();
s42.onoff();
s42.chanup();
cout<<"\nAdjusted settings for 42\"TV\"\n";
s42.settings();
Remote grey;
grey.set_chan(s42, 10);
grey.volup(s42);
grey.volup(s42);
cout << "\n Settings after using remote:\n";
s42.settings();
Tv s58(Tv::On);
s58.set_mode();
grey.set_chan(s58, 28);
cout << "s58 setting:\n";
s58.settings();
return 0;
}
说明:练习的主要目的表明,友元类是一种自然语言,用于表示一些关系。如果不这样,则必须将Tv类私有部分设置为公有,或建立包含两个声明的大型类,着都无法反应这样一个事实:同一个遥控器可用于多台电视机。
3.友元成员函数
上一个例子中,大多数Remote方法都是通过Tv类的公有接口实现的。唯一直接访问Tv成员的Remote方法是set::chan();因此我们可以将这个类的成员函数作为Tv类的友元函数。这样做的麻烦在于声明和定义的顺序。
1.在Tv类中将该成员函数声明为友元。编译器要处理,必须知道Remote定义,Remote在Tv前,但是Remote方法的参数又有Tv类,这意味着Tv类在前。解决该问题的方法是:使用前向说明,并且先声明,最后定义。
class Tv;
class Remote{...}
class Tv{...}
我们看到 remote()函数定义出现了调用Tv方法,Tv还没定义 ,所以解决办法就是将remote定义放在Tv后。(友元类不需要前向声明,因为 友元语句本身指出它就是个类)
#ifndef TV_H_
#define TV_H_
class Tv;
class Remote
{
private:
int mode;
public:
Remote(int m = Tv::TV) :mode(m) {}
bool volup(Tv& t);
bool voldown(Tv& t);
void onoff(Tv& t);
void channup(Tv& t);
void chandown(Tv& t);
void set_chan(Tv& t, int c);
void set_mode(Tv& t, int c);
void set_input(Tv& t, int c);
};
class Tv
{
private:
int state; //on or off
int volume;
int maxchannel;
int channel; //current channel seeting
int mode; //broadcast or cable
int input; //TV or DVD
public:
enum { Off, On };
enum { Minval, Maxval = 20 };
enum { Antenna, Cable };
enum { TV, DvD };
Tv(int s = Off, int mc = 125) :state(s), maxchannel(mc), volume(5), channel(2), mode(Cable), input(TV) {}
void onoff() { state = (state == Off) ? On : Off; } //切换状态
bool ison() { 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;
friend void Remote::set_chan(Tv& t, int c); ;
};
bool Remote::volup(Tv& t) { return t.volup(); }
bool Remote::voldown(Tv& t) { return t.voldown(); }
void Remote::onoff(Tv& t) { t.onoff(); }
void Remote::channup(Tv& t) { t.chanup(); }
void Remote::chandown(Tv& t) { t.chandown(); }
void Remote::set_chan(Tv& t, int c) { t.channel = c; }
void Remote::set_mode(Tv& t, int c) { t.set_mode(); }
void Remote::set_input(Tv& t, int c) { t.set_input(); }
#endif
4.其他友元关系
上边只介绍了遥控器影响电视状态,单方面影响,如果两个类互相影响,则互为对方的友元函数。在具体调用方法和顺序前,一定要记得先声明,后定义。
5.共同的友元
函数需要访问两个类的私有数据。从逻辑上看,这样的函数应该是每个类的成员函数,但这是不可能的。它可以是一个类的成员,同时是另一个类的友元,但有时将函数作为两个类的友元更合理。一个函数分别在一个类中声明两次。
class Analyzer;
class Probe
{
friend void sync(Analyzer &a, const Probe &p); // sync a to p
friend void sync(Probe &p, cosnt Analyzer &a); // sync p to a
}
class Analyzer
{
friend void sync(Analyzer &a, const Probe &p); // sync a to p
friend void sync(Probe &p, cosnt Analyzer &a); // sync p to a
}
inline void sync(Analyzer &a, const Probe &p)
{
...
}
inline void sync(Probe &p, cosnt Analyzer &a)
{
}
②嵌套类
1.嵌套类
在C++中,可以将类声明放在另一个类中,在另一个类中被声明的类称为嵌套类。包含类的成员函数可以创建和使用被嵌套类的对象,仅当声明位于公有部分,才能在包含类的外边使用嵌套类,而且必须使用作用域解析运算符。
对类进行嵌套与包含不同,包含是将类对象作为另一个类的成员,而对类进行嵌套不创建成员,而是定义了一种类型,为了更好的实现包含类。
对于队列的实现来说,我们包含了结构体Node。在队列中,只有enqueue()方法创建了Node对象,我们可以将创建新对象方法交给类的构造函数来完成。
class Queue
{
private:
// 这里Node是一个嵌套的结构体定义
// Item是一个别名, 详见之前的笔记
struct Node {Item item; struct Node * next;}
...
};
bool Queue::enqueue(const Item & item)
{
if(isfull())
return false;
Node * add = new Node;
add->item = item;
add->next = NULL:
...
}
采用嵌套类的构造函数来完成:
class Queue
{
// 嵌套类
class Node
{
public: //全是公有
Item item;
Node * next;
// 构造函数, 将item赋值为i, next指针设置为0, 也就是空值指针
Node(const Item & i): item(i), next(0) {}
};
...
};
bool Queue::enqueue(const Item & item)
{
if(isfull())
return false;
Node * add = new Node(item);
...
}
这个例子中是在类声明中定义了构造函数, 假设想在方法文件中定义构造函数, 则定义必须指出Node类是在Queue类中定义的, 我们可以通过两次作用域解析运算符来完成:
Queue::Node::Node(const Item & i): item(i), next(0) {}
2.嵌套类和访问权限
嵌套类的声明位置决定了嵌套类的作用域, 即它决定了程序的那些部分可以创建这种类的对象.
其次.嵌套类的公部分, 保护部分和私有部分控制了对类成员的访问。(哪些部分可以访问这些数据)
1.作用域
如果嵌套类是在另一个类的私有部分声明的, 则只有包含类知道它,派生类和外部均不知道嵌套类的存在,更别说使用。( 类的默认访问权限是private)
如果嵌套类是在另一个类的保护部分声明的, 则它对于后者和基于后者的派生类可见(可直接创建对象), 但是对于外部世界是不可见的。
如果嵌套类是在另一个类的共有部分声明的, 则允许后者, 后者的派生类以及外部世界使用它,然而, 由于嵌套类的作用域为包含它的类, 因此在外部世界使用它时, 必须使用类限定符.
2.访问控制
对嵌套类的访问控制规则与常规类相同. (私有不可见,只有公有部分可见,保护部分派生可见,外部不可见。这也是为什么Node均声明为公有的)
类声明的位置决定了类的作用域或可见性, 类可见后, 访问控制规则(共有, 保护, 私有, 友元)将决定程序对嵌套类成员的访问权限.
3.模板中嵌套类
// queuetp.h
#ifndef QUEUETP_H_
#define QUEUETP_H_
// 模板类
template <class T>
class QueueTP
{
private:
enum { Q_SIZE = 10 };
// 定义一个嵌套类
class Node
{
public:
T item; //用到了模板
Node* next;
Node(const T& 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);
~QueueTP();
bool isempty() const
{
return items == 0;
}
bool isfull() const
{
return items == qsize;
}
bool queuecount() const
{
return items;
}
bool enqueue(const T& item);
bool dequeue(T& item);
};
template<class T>
QueueTP<T>::QueueTP(int qs) :qsize(qs)
{
// 置为空值指针
front = rear = 0;
items = 0;
}
template<class T>
QueueTP<T>::~QueueTP()
{
Node* temp;
while (front != 0)
{
temp = front;
front = front->next;
delete temp;
}
}
template <class T>
bool QueueTP<T>::enqueue(const T& item)
{
if (isfull())
return false;
Node* add =