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 show(Student &);//一个函数或类在类中被声明为友元时,其在类外必须要有声明! 7 class Student{ 8 public: 9 friend void show(Student &);//将函数show声明为类Student的友元函数 10 Student()=default; 11 Student(string name,int age):Name(name),Age(age){} 12 private: 13 string Name; 14 int Age; 15 }; 16 17 void show(Student &stu){ 18 cout<<"Name:"<<stu.Name<<endl; 19 cout<<"Age:"<<stu.Age<<endl; 20 } 21 22 int main(){ 23 Student stu("Tomwenxing",23); 24 show(stu); 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 Name(string name){ 11 this->name=name; 12 } 13 void show1(const Age&); 14 private: 15 string name; 16 }; 17 18 class Age{ 19 public: 20 friend Name;//将类Name声明为类Age的友元类 21 Age()=default; 22 Age(int age){ 23 this->age=age; 24 } 25 void show2(const Name&); 26 private: 27 int age; 28 }; 29 void Name::show1(const Age& age){ 30 cout<<"调用类Name中的成员函数show1"<<endl; 31 cout<<"Name:"<<name<<endl; 32 cout<<"Age:"<<age.age<<endl; 33 } 34 void Age::show2(const 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 age(23); 43 name.show1(age); 44 cout<<"------------分界线----------------"<<endl; 45 age.show2(name); 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():real(0),img(0){} //默认构造函数 9 Complex(int r,int i):real(r),img(i){} //带参数的构造函数 10 void show(){ //打印复数 11 cout<<"("<<real<<","<<img<<")"<<endl; 12 } 13 Complex operator+(const Complex &c){ //对+进行重载 14 return Complex(real+c.real,img+c.img); 15 } 16 Complex operator+(const int &value){ 17 return Complex(real+value,img); 18 } 19 private: 20 int real; //复数实部 21 int img; //复数虚部 22 }; 23 24 int main(){ 25 Complex c1(100,20); 26 Complex c2(100,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():real(0),img(0){} //默认构造函数 11 Complex(int r,int i):real(r),img(i){} //带参数的构造函数 12 void show(){ //打印复数 13 cout<<"("<<real<<","<<img<<")"<<endl; 14 } 15 Complex operator+(const Complex &c){ //对+进行重载 16 return Complex(real+c.real,img+c.img); 17 } 18 Complex operator+(const int &value){ 19 return Complex(real+value,img); 20 } 21 private: 22 int real; //复数实部 23 int img; //复数虚部 24 }; 25 26 Complex operator+(const int &value,const Complex &c){ 27 return Complex(value+c.real,c.img); 28 } 29 int main(){ 30 Complex c(100,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():real(0),img(0){} //默认构造函数 8 Complex(int r,int i):real(r),img(i){} //带参数的构造函数 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():real(0),img(0){} //默认构造函数 13 Complex(int r,int i):real(r),img(i){} //带参数的构造函数 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.友元破坏了类的封装性,因此使用友元时必须要是是十分慎重