前言
hello。大家好,我们来继续分享关于C++的知识点,今天我们来分享一些关于友元的知识。也欢迎大家订阅我的免费专栏——以分号结尾的诗:C++,在这个专栏里,我会持续介绍关于C++的知识。闲言少叙,让我们开始吧。
1.友元的概念
1.1友元的基本概念
友元分为:友元函数和友元类和友元成员函数 友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
1.2友元的形式
1.友元函数
friend 函数名();
2.友元类
class 类名1;
class 类名2;
{
friend class 类名1;
……
}
3.友元成员函数
class class1;
class class2
{
void f();
};
class class1
{
friend void class2::f();
};
1.3为什么要有友元的存在
友元的存在真的必要吗?我们来可以下下面这个例子: 我们写了一个日期类的函数,将<<运算符重载,用以输出日期,但是重载没有采用友元(细心的朋友可能会发现在我们之前介绍运算符重载的文章中,这里的重载采用的是友元)。代码如下:
#include<iostream>
using namespace std;
class Date
{
int _year;
int _month;
int _day;
public:
Date(int year=0,int month=1,int day=1)
{
_year = year;
_month = month;
_day = day;
}
ostream&operator<<(ostream &_cout)
{
_cout << _year << "-" << _month << "-" << _day;
return _cout;
}
};
int main()
{
Date d1(2021, 8, 9);
cout << d1<<endl;
return 0;
}
我们来看一下输出结果 好家伙,很迷惑,竟然报错了,这是为什么呢? 我们将程序做一个小小的修改,注意只是小小的修改哦,修改后代码如下;
#include<iostream>
using namespace std;
class Date
{
int _year;
int _month;
int _day;
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
ostream&operator<<(ostream &_cout)
{
_cout << _year << "-" << _month << "-" << _day;
return _cout;
}
};
int main()
{
Date d1(2021, 8, 9);
d1 << cout << endl;
return 0;
}
我们只是将d1与cout换了一个位置,我们再来看输出结果: 咦?惊不惊喜意不意外? 为什么会这样呢?我们来分析一下: 我们尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。 我们将其展开如下图,很明显,我们不可以改变操作数的顺序啊。 那么我们又该如何解决这个问题呢?我们可以使cout重新变回左操作数吗? 但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。 我们来看一下:
#include<iostream>
using namespace std;
class Date
{
int _year;
int _month;
int _day;
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
friend ostream&operator<<(ostream &_cout, const Date d);
};
ostream&operator<<(ostream &_cout,const Date d)
{
_cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
int main()
{
Date d1(2021, 8, 9);
cout<<d1 << endl;
return 0;
}
至此,我们已经解决了这个问题。 通过这个问题我们可以发现,在使用类的过程中,有的时候我们不得不打破类的限制,突破类的边界,来使问题得到更好的解决。 友元的存在便可以打破边界突破限制。就像一个人,人为什么需要朋友呢?一个人心中藏着自己的故事,自己的思想,自己的秘密。这些东西独属于他自己。但人又是不甘于寂寞的,所以,人需要一些真正的知心朋友,将自己的一些故事,一些思想,一些秘密分享给朋友。一起分享、一起承担。这样人才不会那么孤独,这样人生活地才会更加快乐。我们与朋友之间彼此保留着自己的隐私,同时又分享彼此的忧喜,这是不是和友元很像呢?
2.友元函数
2.1友元函数的概念
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
我们在上面的那个日期类中就用到了友元函数,现在我们来实现cout和cin的重载,代码如下:
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout<<d._year<<"-"<<d._month<<"-"<<d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
_cin>>d._year;
_cin>>d._month;
_cin>>d._day;
return _cin;
}
2.2注意
1.友元函数可访问类的私有和保护成员,但不是类的成员函数 2.友元函数不能用const修饰 3.友元函数可以在类定义的任何地方声明,不受类访问限定符限制 4.一个函数可以是多个类的友元函数 5.友元函数的调用与普通函数的调用和原理相同 这几点注意事项都很好理解,我们就不过多解释啦。
3.友元类
3.1友元类的概念
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
怎么来理解这句话呢?我们来看一个友元类的例子:
class Date;
class Time
{
friend class Date;
public:
Time(int hour, int minute, int second)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
_t._hour = hour;
_t._minute = minute;
_t.second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
在上面这段代码中,日期类为时间类的友元,日期类可以直接访问时间类中所有的非公有成员。 友元类相对于友元函数将可访问的范围扩大了,友元函数是从类外可以访问类内的一个函数,而友元类是可以在另一个类中可以访问这个类的所有成员函数。
2.2注意
1.友元关系是单向的,不具有交换性。但是两个类可以互为友元。 比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。 2.友元关系不能传递。如果B是A的友元,C是B的友元,则不能说明C时A的友元。 这两条也很好理解,我们可以继续沿用上面的例子。对于1,我们可以联想到一个很事实,就是你愿意分享给你的朋友的东西,你的朋友可能并不愿意分享给你。或者更残忍一点,你的朋友,并不会也把你当作朋友。对于2,一个很简单的道理,朋友的朋友不一定是你的朋友哦。还记得小时候你的那复杂的三角朋友关系吗?A不要你和B玩,B不要你和A玩,你却要和A、B玩,A与B却不相往来,哈哈哈,小朋友间的三角友谊很有趣的哦。
4.友元成员函数
通过声明友元成员函数,这样,就只有这个函数能访问另一个类的private成员。 使类B中的成员函数成为类A的友元函数,这样类B的该成员函数就可以访问类A的所有成员了。
我们来看一个例子。
#include<iostream>
using namespace std;
class Date;
class Time
{
public:
Time(int h, int m, int s)
{
hour = h;
minute = m;
sec = s;
}
void display(Date &);
private:
int hour;
int sec;
int minute;
};
class Date
{
public:
Date(int m, int d, int y)
{
mouth = m;
day = d;
year = y;
}
friend void Time::display(Date &);
private:
int mouth;
int day;
int year;
};
void Time::display(Date &d)
{
cout << d.mouth << "/" << d.day << "/" << d.year << endl;
cout << hour << ":" << minute << ":" << sec << endl;
}
int main(void)
{
Time t1(10, 13, 56);
Date d1(4, 15, 2019);
t1.display(d1);
return 0;
}
1.在函数名display的前面要加上display所在的对象名如(t1) 2.display成员函数的实参是Date类对象d1,f否则就不能访问对象d1的私有数据 3.在Time::display函数中引用Date类私有数据时必须加上对象名,如d.mouth.
5.破坏者?
我们已经介绍了关于友元的知识,我们知道他可以打破类的限制,但是这种打破,却不应该是随意的。如果我们使用友元,就会将类的封装性破坏,不恰当地使用友元,对程序而言十分危险。所以使用友元一定要慎重。就像我们再生活中,交友一定要谨慎。高山流水,伯牙子期是难得的,万一你和张三是好朋友,他会拉着你一块去做狂徒的,谨慎啊。
后记
好的,今天我们关于友元的分享就到这里了。希望对大家有所帮助。关于友元,我们知道他可以打破封装,但是这在打破的同时也是一种破坏。 最近时常能刷到一句话,“就算是friend中也藏着一个end”,我觉得这个end不应该理解为友谊会走到终点,而应该理解为友谊的边界,即使是最好的朋友,也有一个能抵达的end,这是彼此之间的边界。保留着彼此的独立人格,同时又可以共同分享,共同承担。在我们的友元里也是如此,打破封装,但要谨慎打破,不随意打破。 最后,我们分享一下胡适先生写下的中国第一首白话诗《两只蝴蝶》(原题朋友)
两只黄蝴蝶,双双飞上天;
不知为什么,一个忽飞还。
剩下那一只,孤单怪可怜;
也无心上天,天上太孤单。
|