• 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.友元破坏了类的封装性,因此使用友元时必须要是是十分慎重

  • 相关阅读:
    POJ 2348 Euclid's Game【博弈】
    POJ 2484 A Funny Game【博弈】
    HDU 4193 Non-negative Partial Sums【单调队列】
    占坑补题
    Codeforces 658D Bear and Polynomials【数学】
    Codeforces 658C Bear and Forgotten Tree 3【构造】
    Codeforces 658B Bear and Displayed Friends【set】
    POJ 1704 Georgia and Bob【博弈】
    1001. A+B Format

  • 原文地址:https://www.cnblogs.com/duwenxing/p/7454801.html
Copyright © 2020-2023  润新知