c++中的友元

通过之前所学内容,我们知道,在c++中,由于类具有的封装性和信息隐藏的特性,所以我们访问私有成员时通常以类的成员函数作为接口,因为程序中的其他函数是无法访问类中的私有成员的;那么如果就是想要不采用这种方式,还有什么办法可以访问到类的私有成员呢?这就引出了本篇博客所讲的重点:友元

(一)友元函数

首先来看下面一段代码:

#include<iostream>
using namespace std;
#include<stdlib.h>
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date()" << endl;
	}

private:
	int _year;//年
	int _month;//月
	int _day;//日

};
//友元函数
//友元类
void Print(const Date&d)
{
	cout << d._year << "-" << d._month << "-" << d._day << endl;
}

int main()
{
	Date d1;
	Print(d1);
	system("pause");
	return 0;
}
此段代码中,我们在类外定义了Print()函数,试图通过它来访问类中的私有成员,由之前知识所学,显然这是编译不通过的。


这里就不得不提到“友元”

要点一友元的概念--->

                 友元是一种说明在类体内的非成员函数,为了与该类的成员函数加以区别,在说明时前面加上关键字friend。

要点二:

                1 .   友元函数不是类的成员函数。
                2 .   友元函数可以通过对象访问所有成员,私有和保护成员也一样。

要点三:友元的两大分类--->

                1.友元函数

                2.友元类

综上,这里要想编译通过,我们可以在类中声明Print()是友元函数,即

friend void Print(const Date&d);
(二)友元类

首先来看下面一段代码:

class Time
{
	//friend class Date;
private:
	int _hour;
};
class Date
{
	friend void Print(const Date&d);
public:
	Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date()" << endl;
	}
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
		cout << _t._hour << endl;
	}
private:
	int _year;//年
	int _month;//月
	int _day;//日
	Time _t;
};
//友元函数
//友元类
void Print(const Date&d)
{
	cout << d._year << "-" << d._month << "-" << d._day << endl;
}

int main()
{
	Date d1;
	Print(d1);
	return 0;
}

在此段代码中,我们定义了两个类:分别为Time类和Date类,并且在Date类中的Display函数试图去访问Time类中的私有成员_hour;显然是不成功的

同样,要想在Date类中访问Time类中的私有成员,必须在Time类中声明Date类是其的友元类,即:

class Time
{
	friend class Date;
private:
	int _hour;
};

----------------------------------------------------------------------------------------------------------------------------------------------------

下来我们再来看一个问题输入,输出运算符的重载(还是以上面的代码为例,只不过在main函数中稍作改动,试图输出对象d1中的所有成员变量:_year、_month、_day、_hour)事实证明这样是不可行的

int main()
{
	Date d1(2016,12,31);
	d1.Display();
	cout << d1<<endl;;//d1.operator(cout);
	return 0;
}


这里我们就必须对“<<”运算符进行重载,即:

public:
	void operator<<(const ostream &out)
	{
		out << _year << "-" << _month << "-" << _day << endl;
	}

int main()
{
	Date d1(2016,12,31);
	d1.Display();
	cout<<d1;
	return 0;
}

但是这里依然有一个问题,编译不通过


实际上cout<<d1;这句应该反着写,即改为d1<<cout;我们知道运算符的重载,在实际调用“运算符重载函数”时,都会转换为  "对象.operator"的形式,此处传递的第一个参数为对象d1的地址,第二个参数为cout;然后调用operator函数时,&d1传递给了this指针,cout传递给了out(这里out就是cout的别名)具体如下图所示:


但是这里还是有错误,应该吧const去掉,即改为:

void operator<<( ostream &out)
{
	out << _year << "-" << _month << "-" << _day << endl;
}
这样,程序就完全正确了;附上最终修改正确的代码:

class Time
{
	friend class Date;
public:
	Time(int hour=0)
		:_hour(hour)
	{}
private:
	int _hour;
};
class Date
{
	friend void Print(const Date&d);
public:
	Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date()" << endl;
	}
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
		cout << _t._hour << endl;
	}

	void operator<<( ostream &out)
	{
		out << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;//年
	int _month;//月
	int _day;//日
	Time _t;
};

void Print(const Date&d)
{
	cout << d._year << "-" << d._month << "-" << d._day << endl;
}

int main()
{
	Date d1(2016,12,31);
	d1.Display();
	d1 << cout;
	return 0;
}

但是,仔细看我们发现main()函数中的d1<<cout很别扭,按平常的书写习惯都是cout<<d1;这样来的,而由上边所讲此处cout<<d1;是编译不通过的。那么要想既保持这样的书写格式,又想要编译通过该怎么办呢?

在上面的代码中,我们把“输出运算符的重载”写在了类中,相当于是类的成员函数,可以访问私有成员;在main函数中调用时,传参时都会传递一个隐含的this指针;这里我们还有一种方法可以访问私有成员:把“输出运算符的重载”不定义在类中,而是定义在类外,写成全局的;然后再在类中声明为友元函数。即如下:

//Date类中声明"<<的重载"为友元函数
class Date
{
	friend void operator<<(ostream &out, const Date&d);
};
//类外定义"输出运算符<<"的重载
void operator<<( ostream &out,const Date&d)
{
   out << d._year << "-" << d._month << "-" << d._day << endl;
}
//注意main函数部分对“<<”的调用
int main()
{
	Date d1(2016,12,31);
	d1.Display();
	operator<<(cout, d1);
	return 0;
}
---------------------------------------------------------------------------------------------------------------------------------------------------------

依旧贴上完整版修改过的源代码:

class Time
{
	friend class Date;
public:
	Time(int hour=0)
		:_hour(hour)
	{}
private:
	int _hour;
};
//Date类中声明"<<的重载"为友元函数
class Date
{
	friend void Print(const Date&d);
	friend void operator<<(ostream &out, const Date&d);
public:
	Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
		cout << "Date()" << endl;
	}
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
		cout << _t._hour << endl;
	}

private:
	int _year;//年
	int _month;//月
	int _day;//日
	Time _t;
};
//类外定义"输出运算符<<"的重载
void operator<<( ostream &out,const Date&d)
{
   out << d._year << "-" << d._month << "-" << d._day << endl;
}
void Print(const Date&d)
{
	cout << d._year << "-" << d._month << "-" << d._day << endl;
}
//注意main函数部分对“<<”的调用
int main()
{
	Date d1(2016,12,31);
	d1.Display();
	operator<<(cout, d1);
	return 0;
}
这里operator<<(cout,d1)也可以写成cout<<d1;看起来更符合平常的编程书写习惯。

而有时我们需要连续输出(cout<<d1<<d2;)的时候怎么办呢?

       这里就要注意了,当写成cout<<d1<<d2;时编译是不会通过的,为什么?这里就牵扯到了我们之前讲“运算符重载的返回值 为什么不是void”谈到到的一个问题--->链式赋值。这里其实道理是一样的:当输出一个的时候,没有返回值不存在任何问题,但是当连续输出的时候前一个的返回值必须为cout,也就是编译器调用operator函数后只有返回的是cout,才能进行后边的cout<<d2;

所以代码部分也应该做相应的修改:

//Date类中友元函数的声明
class Date
{
	//friend void operator<<(ostream &out, const Date&d);
	friend ostream& operator<<(ostream &out, const Date&d);
};
//类外定义"输出运算符<<"的重载,注意这里的返回值不再是void
ostream& operator<<( ostream &out,const Date&d)
{
   out << d._year << "-" << d._month << "-" << d._day << endl;
   return out;
}
//注意main函数部分对“<<”的调用,连续输出
int main()
{
	Date d1(2016,12,31);
	Date d2;
	d1.Display();
	cout << d1<<d2;
	return 0;
}
同理:“输入运算符>>的重载”也是把它定义在类外,然后再在类中声明为友元函数就可以。注意:第二个参数不能用const修饰,因为这样输入的东西就不能被改了

//在类中声明为友元函数
class Date
{
	friend istream& operator>>(istream &in, Date&d);

};

istream& operator>>(istream &in, Date&d)
{
	cout << "请依次输入年月日:" ;
	in >> d._year >> d._month >> d._day;
	return in;
}
int main()
{
	Date d1(2016, 12, 31);
	Date d2;
	d1.Display();
	cin >> d1 >> d2;
	cout << d1 << d2;
	system("pause");
	return 0;
}
运行结果:




------------------------------------------------------------------------------------------------------------------------------------------------------

综上,总结:

(1)对于运算符的重载,我们可以写在类里面。但是这样很不方便,因为调用时对象就得写在前面,如:d1<<cout;看起来很别扭,不符合我们的使用习惯

(2)为了让可读性变得更好,还有一种方法:写在类外面,作为全局的。但是这样又会出现一个问题:不能用对象直接去访问类中的成员变量;解决办法就是:在类中声明为友元就可以了











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值