c++中的友元

    xiaoxiao2021-03-26  15

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

    转载请注明原文地址: https://ju.6miu.com/read-659654.html

    最新回复(0)