C++:友元

前言:友元对于我来说一直是一个难点,最近看了一些有关友元的课程与博客,故在此将自己的学习收获做一个简单的总结

一、什么是友元

在C++的自定义类中,一个常规的成员函数声明往往意味着:

  • 该成员函数能够访问其所在类的私有部分

  • 该成员函数位于其所在类的作用域之中

  • 该成员函数必须由一个对象去激活从而被调用(通过this指针来实现)

如果将一个函数声明为另一个类的友元,则可以使该函数只具有上面的第一个特性,即可以使该函数访问类中的私有部分。

友元函数的语法:friend+普通函数声明
友元类的语法: friend+类名(不是对象名)
友元成员函数的语法:friend+成员函数的声明

[注]:如果将类A声明为类B的友元类,则类A中的所有成员函数都可以访问类B的私有部分,即类A中的成员函数都是类B的友元函数。

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;
 4 
 5 class Student;
 6 void showStudent &);//一个函数或类在类中被声明为友元时,其在类外必须要有声明!
 7 class Student{
 8 public:
 9     friend void showStudent &);//将函数show声明为类Student的友元函数 
10     Student)=default;
11     Studentstring name,int age):Namename),Ageage){}
12 private:
13     string Name;
14     int Age;
15 }; 
16 
17 void showStudent &stu){
18     cout<<"Name:"<<stu.Name<<endl;
19     cout<<"Age:"<<stu.Age<<endl;
20 } 
21 
22 int main){
23     Student stu"Tomwenxing",23);
24     showstu);
25     return 0;
26 }

特别注意:

1.友元函数不是类的成员函数。和一般函数相比友元函数可以访问类中的所有成员,而一般函数只能访问类中的非公有成员

2.友元函数不受类中的访问权限关键字的限制,因此可以把友元函数或友元类的声明放在类的任意位置(不管该位置是公有、私有还是受保护),其结果是相同的(建议将友元函数或友元类的声明放在类定义最开始的地方)

3.友元函数的作用域并非其声明所在类的作用域。如果友元函数是另一个类的成员函数,则其作用域和另一个类的作用域;否则该友元函数的作用域和一般函数的作用域相同

二、为什么使用友元

1.使用友元可以实现类之间的数据共享,减少系统的开销,提高效率

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;
 4 
 5 class Age;//声明类
 6 class Name{
 7 public:
 8     friend Age;//将类Age声明为类Name的友元类 
 9     Name)=default;
10     Namestring name){
11         this->name=name;
12     }
13     void show1const Age&);
14 private:
15     string name;
16 };
17 
18 class Age{
19 public:
20     friend Name;//将类Name声明为类Age的友元类 
21     Age)=default;
22     Ageint age){
23         this->age=age;
24     }
25     void show2const Name&);
26 private:
27     int age;
28 };
29 void Name::show1const Age& age){
30     cout<<"调用类Name中的成员函数show1"<<endl;
31     cout<<"Name:"<<name<<endl;
32     cout<<"Age:"<<age.age<<endl; 
33 }
34 void Age::show2const Name& name){
35     cout<<"调用类Age中的成员函数show2"<<endl;
36     cout<<"Name:"<<name.name<<endl;
37     cout<<"Age:"<<age<<endl;
38 }
39 
40 int main){
41     Name name"Tomwenxing");
42     Age age23);
43     name.show1age);
44     cout<<"------------分界线----------------"<<endl;
45     age.show2name);
46     return 0; 
47 }

上例中Name类和Age类互为友元类,从而实现了类Age和类Name之间的数据共享。

2.运算符重载的某些场合需要使用友元

Example 1:重载+号时利用友元实现“加法的交换律”

先来看一个有关复数加法的例子:

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;
 4 
 5 class Complex;//对类Comple进行声明 
 6 class Complex{
 7 public:
 8     Complex):real0),img0){} //默认构造函数
 9     Complexint r,int i):realr),imgi){} //带参数的构造函数
10     void show){ //打印复数 
11         cout<<""<<real<<","<<img<<")"<<endl; 
12     } 
13     Complex operator+const Complex &c){ //对+进行重载
14         return Complexreal+c.real,img+c.img);
15     } 
16     Complex operator+const int &value){
17         return Complexreal+value,img);
18     }
19 private:
20     int real; //复数实部
21     int img; //复数虚部 
22 };
23 
24 int main){
25     Complex c1100,20);
26     Complex c2100,30);
27     Complex sum1=c1+c2;
28     sum1.show);
29     Complex sum2=c1+10;
30     sum2.show);
31     return 0;
32 }

上例中Comple对象和Complex对象或int型整数的加法本质上是调用类中的成员函数,即语句sum1=c1+c2等价于sum1=c1.operator+c2),语句sum2=c1+10等价于sum2=c1.operator+10) ,因此这两条语句可以在系统中可以顺利执行。但如果main函数中出现如下语句时,编译器会报错:

1 Comple sum3=10+c1; //错误!
2 sum3.show);

这是由于10是int型整数而非Complex类的对象,因而无法调用类中的成员函数operator+)来完成Complex对象和int型整数的加法,换句话说就是如果仅仅在类中对+进行重载是无法使对象在和int型整数进行加法时满足加法的交换律。这时候如果想解决这个问题,就需要借助友元的力量了,如下:

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;
 4 
 5 class Complex;//对类Comple进行声明 
 6 Complex operator+const int&,const Complex&); //对函数进行声明 
 7 class Complex{
 8 public:
 9     friend Complex operator+const int&,const Complex&);
10     Complex):real0),img0){} //默认构造函数
11     Complexint r,int i):realr),imgi){} //带参数的构造函数
12     void show){ //打印复数 
13         cout<<""<<real<<","<<img<<")"<<endl; 
14     } 
15     Complex operator+const Complex &c){ //对+进行重载
16         return Complexreal+c.real,img+c.img);
17     } 
18     Complex operator+const int &value){
19         return Complexreal+value,img);
20     }
21 private:
22     int real; //复数实部
23     int img; //复数虚部 
24 };
25 
26 Complex operator+const int &value,const Complex &c){
27     return Complexvalue+c.real,c.img);
28 } 
29 int main){
30     Complex c100,20);
31     Complex sum1=c+10;
32     sum1.show);
33     cout<<"----------分界线-------------"<<endl;
34     Complex sum2=10+c;
35     sum2.show);
36     return 0;
37 }

此时语句sum2=10+c相当于sum2=operator+10,c),从而实现了加法的交换律

Example 2:对>>和<<的重载

我们希望对>>和<<进行重载,从而使Comple对象可以直接使用cout和cin。那么只在类中对运算符>>和<<进行重载是否可以?我们可以先来试一下:

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;
 4 
 5 class Complex{
 6 public:
 7     Complex):real0),img0){} //默认构造函数
 8     Complexint r,int i):realr),imgi){} //带参数的构造函数
 9     void show){ //打印复数 
10         cout<<""<<real<<","<<img<<")"<<endl; 
11     } 
12     ostream& operator<<ostream &out) const{
13         out<<""<<real<<","<<img<<")";
14         return out; 
15     }
16     istream& operator>>istream &in){
17         in>>real>>img;
18         return in;
19     }
20 private:
21     int real; //复数实部
22     int img; //复数虚部 
23 };
24 
25 int main){
26     Complex c;
27     cout<<"请输入复数:";
28     c>>cin; //输入复数
29     c<<cout;//输出对象 
30     return 0;
31 }

由上面的例子可以看出其实是可以的,但由于对运算符>>和<<的使用本质上是对类中成员函数的调用,因此完成对复数进行输入操作的语句是c>>cin(相当于c.operator>>cin)),而完成对复数进行输出操作的语句时c<<out(相当于c.operator<<cout)),但这和我们平时的操作习惯有很大不同,并且可读性也很差。为了解决这个问题,我们需要借助友元的力量:

 1 #include<iostream>
 2 #include<string>
 3 using namespace std;
 4 
 5 class Complex;//声明类
 6 ostream& operator<<ostream&,const Complex&);//声明函数
 7 istream& operator>>istream&,Complex&);//声明函数 
 8 class Complex{
 9 public:
10     friend ostream& operator<<ostream &out,const Complex &c);//声明为友元函数 
11     friend istream& operator>>istream &in,Complex &c); 
12     Complex):real0),img0){} //默认构造函数
13     Complexint r,int i):realr),imgi){} //带参数的构造函数
14     void show){ //打印复数 
15         cout<<""<<real<<","<<img<<")"<<endl; 
16     } 
17 private:
18     int real; //复数实部
19     int img; //复数虚部 
20 };
21 ostream& operator<<ostream &out,const Complex &c){
22     out<<""<<c.real<<","<<c.img<<")";
23     return out;
24 }
25 istream& operator>>istream &in,Complex &c){
26     in>>c.real>>c.img;
27     return in;
28 }
29 int main){
30     Complex c;
31     cout<<"请输入复数:";
32     cin>>c;//输入复数
33     cout<<c;//输出对象 
34     return 0;
35 }

此时语句cin>>c相当于operator>>cin,c),而语句cout<<c相当于operator<<cout,c),从而完成所期望的功能

 

三、友元的特别注意事项

1.切记友元函数不是类的成员函数,故编译器不会在友元函数中隐式地插入this指针

2.友元是不能被继承的,原因很简单: “父亲的朋友不一定也是儿子的朋友”

3.友元破坏了类的封装性,因此使用友元时必须要是是十分慎重

Published by

风君子

独自遨游何稽首 揭天掀地慰生平

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注