第十五章之(一)友元

MFC遇见问题了,不知道如何在界面中创造一个文本框,用于让用户输入文字。而且搜了好久都找不到教程(如果谁会,请联系我,或者加我QQ:20004604帮助我)


因此,只能回头过来更新这个了。回头看了一下十四章的类模板,已经涉及到类模板的继承了,感觉好复杂,似乎也用不到,所以跳过来更新十五章内容了。


十五章计划只更新15.1的友元和15.2的嵌套类。15.3的异常到时候看情况,可能会跳过,然后直接更新十六章内容。


————————————————————————————————

让一个类成为另一个类的友元:

当面临两个类对象,这两个类首先是互相独立且没有共同点的。

因此,不能使用继承(无论是公有还是私有)。

但这两个类之间有联系。例如第一个类的对象,可以操纵并影响第二个类的对象的某些功能(书上的例子是电视机和电视机的遥控器)。

 

就以书上提供的电视机和遥控器为例,先自行定义类定义:

电视机的(被操控一方):

#include<iostream>

//类定义
class TV
{
	bool ison;	//开关
	int channelmax;		//最大频道数
	int channel;	//当前频道
	int volume;		//当前音量(最大100,最小0)
	bool model;	//模式,有线1或者无线0
	bool AVTV;	//AV/TV模式,AV 1,TV 0
public:
	TV();			//构造函数
	void OnOff();	//开关机按钮
	void AddChannel();	//当前频道加1
	void MinusChannel();	//频道减1
	void AddVolume();	//音量加1
	void MinusVolume();		//音量减1
	void ChangeModel();		//更改有线无线模式
	void ChangeAVTV();		//更改AVTV模式
};

//类方法
TV::TV()
{
	ison = false;	//开关
	channelmax = 10;		//刚开始只有10个频道
	channel = 1;	//初始频道为1
	volume = 1;	//音量为1
	model = 1;	//有线
	AVTV = 0;	//TV模式
}
void TV::OnOff()	//自定义的时候是分开的,后来想到应该是一个按钮,故合并
{
	if (ison == true)ison = false;
	else ison = true;
}

void TV::AddChannel()
{
	if (ison == false)return;		//如果关机则没反应
	channel++;
	if (channel > channelmax)channel = 1;	//如果比最大频道大,则跳回初始频道
}
void TV::MinusChannel()
{
	if (ison == false)return;		//如果关机则没反应
	channel--;
	if (channel > channelmax)channel = channelmax;	//如果比最大频道大,则跳回最后频道
}
void TV::AddVolume()
{
	if (ison == false)return;		//如果关机则没反应
	if (volume < 100)volume++;		//达到100则没不能再加了
		//理论上这里应该还有一个显示音量的画面
}
void TV::MinusVolume()
{
	if (ison == false)return;		//如果关机则没反应
	if (volume > 0)volume--;
	//理论上这里应该还有一个显示音量的画面
}
void TV::ChangeModel()
{
	if (ison == false)return;		//如果关机则没反应
	if (model == 1)model = 0;
	else model = 1;
}
void TV::ChangeAVTV()
{
	if (ison == false)return;		//如果关机则没反应
	if (AVTV == 1)AVTV = 0;
	else AVTV = 1;
}

然后,因为遥控器的类要求能操控电视机,假设遥控器的类名为Control,因此,需要在TV的public部分加入代码:


public:
	friend class Control;	//使Control成为友元类

于是TV类的完成了,下来制作遥控器类:

class Control		//遥控器类
{
public:
	void OnOff(TV&t) { return t.OnOff(); }	//开关机按钮
	void AddChannel(TV&t) { return t.AddChannel(); }	//当前频道加1
	void MinusChannel(TV&t) { return t.MinusVolume(); }	//频道减1
	void AddVolume(TV&t) { return t.AddVolume(); }	//音量加1
	void MinusVolume(TV&t) { return t.MinusVolume(); }		//音量减1
	void ChangeModel(TV&t) { return t.ChangeModel(); }		//更改有线无线模式
	void ChangeAVTV(TV&t) { return t.ChangeAVTV(); }		//更改AVTV模式
	void SetChannel(int c, TV&t);	//选择某个频道,友元类在这个函数里起作用了(可以直接调用私有成员)
};
void Control::SetChannel(int c, TV&t)	//比最大大则自动为最大,比1小则自动为1
{
	if (c < 1)c = 1;
	else if (c>t.channelmax)c = t.channelmax;
	t.channel = c;
}

若在类中声明另一个类为其友元,则另一个类可以使用当前类的私有成员(如在TV中声明Control类为TV类的友元,于是在Control类中的SetChannel()方法中,就可以使用TV类的私有成员channel)。

 

 

为了方便展示,故在TV类中再添加一类方法show(),用于输出当前状态(开关,频道,音量,最大频道等:


void TV::show()
{
	using std::cout;
	using std::endl;
	cout << "开关:";
	if (ison == true)cout << "开";
	else cout << "关";
	cout << ",最大频道数:" << channelmax << ",当前频道:" << channel << ",当前音量:" << volume << ",有线无线模式:";
	if (model == true)cout << "有线";
	else cout << "无线";
	cout << ",AV/TV:";
	if (AVTV == true)cout << "AV";
	else cout << "TV";
	cout << endl;
}

附测试程序


int main()
{
	using namespace std;
	TV m, n;	//两个TV
	Control one;	//遥控器
	cout << "输出2个电视(m和n)目前情况" << endl;
	m.show();
	n.show();
	cout << "首先开启m和n";
	m.OnOff();
	n.OnOff();
	cout << "设置m的当前频道:";
	int p;
	cin >> p;
	one.SetChannel(p, m);
	cout << "设置n的当前频道:";
	cin >> p;
	one.SetChannel(p, n);
	cout << "m增加5次音量" << endl;
	for (int i = 0; i < 5; i++)
		m.AddVolume();
	cout << "m增加5次音量,减少1次音量" << endl;
	for (int i = 0; i < 5; i++)
		n.AddVolume();
	n.MinusVolume();
	cout << "m增加3次频道" << endl;
	for (int i = 0; i < 3; i++)
		m.AddChannel();
	cout << "n减少3次频道" << endl;
	for (int i = 0; i < 3; i++)
		n.MinusChannel();
	cout << "m切换有线无线模式" << endl;
	m.ChangeModel();
	cout << "n切换AVTV模式" << endl;
	n.ChangeAVTV();
	cout << "重新查看m和n的情况" << endl;
	m.show();
	n.show();
	system("pause");
	return 0;
}

显示:

输出2个电视(m和n)目前情况
开关:关,最大频道数:10,当前频道:1,当前音量:1,有线无线模式:有线,AV/TV:TV
开关:关,最大频道数:10,当前频道:1,当前音量:1,有线无线模式:有线,AV/TV:TV
首先开启m和n设置m的当前频道:10
设置n的当前频道:5
m增加5次音量
m增加5次音量,减少1次音量
m增加3次频道
n减少3次频道
m切换有线无线模式
n切换AVTV模式
重新查看m和n的情况
开关:开,最大频道数:10,当前频道:3,当前音量:6,有线无线模式:无线,AV/TV:TV
开关:开,最大频道数:10,当前频道:2,当前音量:5,有线无线模式:有线,AV/TV:AV
请按任意键继续. . .

让一个类的某个类方法,成为另一个类的友元函数:

让一个类的某个类方法,成为另一个类的友元,和让一个类成为另一个类的友元很相似。

 

在另一个类中,使用friend 另一个类的函数头

例如:

friend voidControl::SetChannel(int c, TV&t);  //使Control成为友元类

 

但这样带来一个问题,假如Control类在TV类之前,那么在其类方法中不能使用内联函数(例如voidOnOff(TV&t){ return t.OnOff();}  //开关机按钮这样),而且也有一个问题是,Control类中使用了TV类作为参数

 

如果TV类在Control类之前,那么TV类不知道Control类是什么,自然也没办法把他的某个类方法作为内联函数了。

 

①为了解决后一个问题,那么应该将Control类放在TV类前面;

②为了让TV类成员(引用)能够作为Control的参数,因此,应该使用前向声明(forward declaraton,即在Control类之前加入语句:class TV;

 

③但又存在Control类中不能使用内联函数的问题,因此,应该在Control类定义中,不要使用内联函数(但可以在后面类方法定义时,使用inline关键字使其内联),把其函数定义,放于TV类定义之后。

 

于是,整个结构应该是这样的:

class TV; (类的前向声明)

class Control{类定义};

class TV{ 类定义};

TV类和Control类的类方法定义

 

这样就可以顺利运行了。

 

另外,如果相反的话(Control,TV,Control)是不行的。

 

原因在于,在TV类里面声明Control的某个方法为其友元方法。

因此,在编译的时候,编译器要知道这个方法是什么(即之前声明过),而若只声明某个类,那是不行的(因为编译器只知道有这么一个类,但不知道这个类里面有什么类方法)。

另外,又因为类方法的声明只能在类定义中(类方法的定义可以放其他地方),因此也不能单独把其拿出来,放在类TV的定义之前。

 

 

于是,若友元的是类方法,其顺序只能是:

①被友元类的前置声明——》

②友元(包含友元函数的方法所在的)类的声明(不能有内联函数)——》

③被友元类的声明——》

④两个类的类定义

 

而若友元的是类,顺序则无要求。

代码如下:

 

//类定义
class TV;		//前置,以使用TV类作为参数
class Control		//遥控器类
{
public:
	void OnOff(TV&t);	//开关机按钮
	void AddChannel(TV&t);	//当前频道加1
	void MinusChannel(TV&t);	//频道减1
	void AddVolume(TV&t);	//音量加1
	void MinusVolume(TV&t);		//音量减1
	void ChangeModel(TV&t);		//更改有线无线模式
	void ChangeAVTV(TV&t);		//更改AVTV模式
	void SetChannel(int c, TV&t);	//选择某个频道,友元类在这个函数里起作用了(可以直接调用私有成员)
};

class TV
{
	bool ison;	//开关
	int channelmax;		//最大频道数
	int channel;	//当前频道
	int volume;		//当前音量(最大100,最小0)
	bool model;	//模式,有线1或者无线0
	bool AVTV;	//AV/TV模式,AV 1,TV 0
public:
	friend void Control::SetChannel(int c, TV&t);	//使Control的类方法成为友元
	TV();			//构造函数
	void OnOff();	//开关机按钮
	void AddChannel();	//当前频道加1
	void MinusChannel();	//频道减1
	void AddVolume();	//音量加1
	void MinusVolume();		//音量减1
	void ChangeModel();		//更改有线无线模式
	void ChangeAVTV();		//更改AVTV模式
	void show();	//输出当前状态
};

//类方法
略。。。

两个类互为友元的情况:

假如Control类中,需要使用TV类的数据成员,我们是已知方法的。

那么在这个的基础上,TV类也要使用Control类的数据成员,则需要做更多。

 

首先,在两个类的类定义中,添加 friend class 友元类名 这行代码,并且,添加的位置,要位于该类使用友元类对象作为参数的类方法之前。

 

其次,参数应该是友元类(不过是否能用友元类的某个类方法(使用函数指针)作为参数,不确定。我倾向于是不行的,因为之前没有声明该方法的原型,另外也感觉怪怪的,好像哪里不对)。

 

最后,类方法要放于两个类定义之后,且类定义中不能使用友元的方法在内联函数中(但是在类定义后的类方法中,可以加关键字inline使用内联函数作为替代)。

 

如代码:

#include<iostream>

//类定义
class M
{
	int a;
public:
	friend class N;	//这样要在使用其类的类方法之前
	M() :a(0) {}
	void add(N&n);	//N是友元,故能直接引用N作为参数
	void show();
};
class N
{
	int b;
public:
	friend class M;//这样要在使用其类的类方法之前
	N():b(5){}	//默认构造函数,初始值不同
	void add(M&m);	//M是友元,故能直接引用N作为参数
	void show();
};
//类方法定义
void M::add(N&n)
{
	a++;
	n.b++;
}
void M::show()	//显示其值,也可以将N类对象作为参数传递,用于显示N类对象的数据成员的值
{
	std::cout << "a:" << a << std::endl;
}
void N::add(M&m)	//两个类的方法,对数值操纵不同,以区分
{
	m.a += 10;
	b++;
}
void N::show()
{
	std::cout << "b:" << b << std::endl;
}

//测试代码
int main()
{
	using namespace std;
	M one;
	N two;
	one.show();
	two.show();
	cout << "M对象add方法" << endl;
	one.add(two);
	one.show();
	two.show();
	cout << "N对象add方法" << endl;
	two.add(one);
	one.show();
	two.show();
	system("pause");
	return 0;
}

运行结果是:

a:0
b:5
M对象add方法
a:1
b:6
N对象add方法
a:11
b:7
请按任意键继续. . .

 

 

共同的友元:

假如在一个函数中,需要同时使用两个类的私有成员。

 

使用一个类的私有成员,有两个办法:

①是该类的类方法(继承是不能直接调用其私有成员的);

②是该类的友元函数。

 

而这个函数若要使用两个类的私有成员,

首先,①+①的方法是不可能的,因为不可能同时是两个类的类方法。

其次,①+②或者②+①的方法组合,是可以的,上面已经说明了。

最后,②+②的方法,也是可行的。即一个函数,既是第一个类的友元函数,也是第二个类的友元函数。

 

②+②的方法很简单,两个类的类对象都是其参数,然后在其函数内部使用friend声明其是友元函数(就像使用friend ostream&operator<<(ostream&, 类名& m)这个运算符重载函数一样)。

但由于要在第一个类中,声明友元函数,且友元函数的参数使用第二个类对象(该函数的参数包括第一个类的类对象和第二个类的类对象)。因此,第二个类应该前向声明其类声明。

但在第二个类中就无需这么做了(因为第一个类已经进行定义过了)

 

如代码:

#include<iostream>

class N;
class M
{
	int a = 1;
public:
	friend void show(M&m, N&n);
};
class N
{
	int b = 2;
public:
	friend void show(M&m, N&n);
};
void show(M&m, N&n)
{
	std::cout << "M:" << m.a << ", N:" << n.b << std::endl;
}

int main()
{
	M a;
	N b;
	show(a, b);
	system("pause");
	return 0;
}

输出内容如下:

M:1, N:2
请按任意键继续. . .



你可能感兴趣的:(友元类)