通过之前所学内容,我们知道,在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)为了让可读性变得更好,还有一种方法:写在类外面,作为全局的。但是这样又会出现一个问题:不能用对象直接去访问类中的成员变量;解决办法就是:在类中声明为友元就可以了