友元函数的简单介绍

为什么要使用友元函数

在实现类之间数据共享时,减少系统开销,提高效率。如果类A中的函数要访问类B中的成员(例如:智能指针类的实现),那么类A中该函数要是类B的友元函数。

友元允许外面的类或函数去访问类的私有变量和保护变量,从而共享同一函数。

实际上具体大概有下面两种情况需要使用友元函数:

(1)运算符重载的某些场合需要使用友元。

(2)两个类要共享数据的时候。

使用友元函数的优缺点

优点:能够提高效率,表达简单、清晰。

缺点:友元函数破环了封装机制,尽量不使用成员函数,除非不得已的情况下才使用友元函数。

友元函数的使用

友元函数的参数

因为友元函数没有this指针,则参数要有三种情况:

要访问非static成员时,需要对象做参数;

要访问static成员或全局变量时,则不需要对象做参数;

如果做参数的对象是全局对象,则不需要对象做参数;

友元函数的位置

友元声明可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员,所以它们不受声明出现部分的访问控制影响。

友元函数的调用

可以直接调用友元函数,不需要通过对象或指针,而且不需要“::”类作用域符

友元函数的分类

根据这个函数的来源不同,可以分为三种方法:

将非成员函数声明为友元函数

目的:使普通函数能够访问类的友元

声明: friend + 普通函数声明

注意:

类内声明友元函数只是指定了访问的权限,保证该普通函数可以访问类的所有成员,而不是一个实际意义上的函数声明。

如果我们希望类的用户能够调用某个友元函数,那么我们必须在类外再次按照普通函数那样声明函数(或者在类调用函数之前定义)

实现位置:可以在类外或类中

实现代码:与普通函数相同

调用:类似普通函数,直接调用,不需要friend和“::”

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;
class Student{
    public:
        Student(char *name, int age, float score);
    public:
        friend void show(Student *pstu);  //将show()声明为友元函数,需要类的指针作为参数
    private:
        char *m_name;
        int m_age;
        float m_score;
};
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
//非成员函数
void show(Student *pstu){
    cout<<pstu->m_name<<"的年龄是 "<<pstu->m_age<<",成绩是 "<<pstu->m_score<<endl;
}
int main(){
    Student stu("小明", 15, 90.6);
    show(&stu);  //调用友元函数
    Student *pstu = new Student("李磊", 16, 80.5);
    show(pstu);  //调用友元函数
    return 0;
}

运行结果:

小明的年龄是 15,成绩是 90.6

李磊的年龄是 16,成绩是 80.5

show() 是一个全局范围内的非成员函数,它不属于任何类,它的作用是输出学生的信息。m_name、m_age、m_score 是 Student 类的 private 成员,原则上不能通过对象访问,但在 show() 函数中又必须使用这些 private 成员,所以将 show() 声明为 Student 类的友元函数。读者可以亲自测试一下,将上面程序中的第 8 行删去,观察编译器的报错信息。

注意,友元函数不同于类的成员函数,在友元函数中不能直接访问类的成员,必须要借助对象。下面的写法是错误的:

1
2
3
void show(){
    cout<<m_name<<"的年龄是 "<<m_age<<",成绩是 "<<m_score<<endl;
}

成员函数在调用时会隐式地增加 this 指针,指向调用它的对象,从而使用该对象的成员;而 show() 是非成员函数,没有 this 指针,编译器不知道使用哪个对象的成员,要想明确这一点,就必须通过参数传递对象(可以直接传递对象,也可以传递对象指针或对象引用),并在访问成员时指明对象。

将其他类的成员函数声明为友元函数

如果一个类的成员函数是另一个类的友元函数,则称这个成员函数为友元成员。通过友元成员,不仅可以访问自己所在的类对象中的所有其他成员,还可以访问由关键字friend声明语句所在的类的对象中的所有成员。这种机制可以让两个类相互访问,从而共同完成某些特定任务。

当用到友元成员函数时,需注意友元声明和友元定义之间的相互依赖,如果类B中的函数为类A的友元,类B必须先定义,否则类A就不能将一个B的函数指定为友元。然而,只有在定义了类A之后,才能定义类B的该成员函数。

所以先声明类A(仅声明类),再声明类B,再重新声明类A(包括所有成员),再实现类A和类B。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <iostream>
using namespace std;
class Address;  //前向声明Address类,因为Student类中有Address参数
//声明Student类
class Student{
    public:
        Student(char *name, int age, float score);
public:
        void show(Address *addr);
private:
        char *m_name;
        int m_age;
        float m_score;
};
//声明Address类
class Address{
private:
        char *m_province;  //省份
        char *m_city;  //城市
        char *m_district;  //区(市区)
public:
    Address(char *province, char *city, char *district);
    //将Student类中的成员函数show()声明为友元函数
    friend void Student::show(Address *addr);
};
//实现Student类
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(Address *addr){//必须放在Address类后实现
    cout<<m_name<<"的年龄是 "<<m_age<<",成绩是 "<<m_score<<endl;
    cout<<"家庭住址:"<<addr->m_province<<"省"<<addr->m_city<<"市"<<addr->m_district<<"区"<<endl;
}
//实现Address类
Address::Address(char *province, char *city, char *district){
    m_province = province;
    m_city = city;
    m_district = district;
}
int main(){
    Student stu("小明", 16, 95.5f);
    Address addr("陕西", "西安", "雁塔");
    stu.show(&addr);

    Student *pstu = new Student("李磊", 16, 80.5);
    Address *paddr = new Address("河北", "衡水", "桃城");
    pstu -> show(paddr);
    return 0;
}

运行结果:

小明的年龄是 16,成绩是 95.5

家庭住址:陕西省西安市雁塔区

李磊的年龄是 16,成绩是 80.5

家庭住址:河北省衡水市桃城区

本例定义了两个类 Student 和 Address,程序第 27 行将 Student 类的成员函数 show() 声明为 Address 类的友元函数,由此,show() 就可以访问 Address 类的 private 成员变量了。

注意:

  1. 在声明友元函数时,要加上成员函数所在类的类名和作用域运算符::。

  2. 程序第 4 行对 Address 类进行了前向声明,是因为在 Address 类定义之前、在 Student 类中使用到了它,如果不前向声明,编译器会报错,提示’Address’ has not been declared。类的 和函数的前向是一个道理。

  3. 程序将 Student 类的声明和实现分开了,而将 Address 类的声明放在了中间,这是因为编译器从上到下编译代码,show() 函数体中用到了 Address 的成员 province、city、district,如果提前不知道 Address 的具体声明内容,就不能确定 Address 是否拥有该成员(类的声明中指明了类有哪些成员)。

友元类

如果一个类作为另一个类的友元,称这个类为友元类。当一个类成为另一个类的友元类时,这个类的所有成员函数都成为另一个类的友元函数。友元类中的所有成员函数都可以通过对象名直接访问另一个类的所有成员,从而实现不同类之间的数据共享。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <iostream>
using namespace std;
class Address;  //前向声明Address类
//声明Student类
class Student{
    public:
        Student(char *name, int age, float score);
    public:
        void show(Address *addr);
    private:
        char *m_name;
        int m_age;
        float m_score;
};
//声明Address类
class Address{
    public:
        Address(char *province, char *city, char *district);
    public:
    //将Student类声明为Address类的友元类
        friend class Student;
    private:
        char *m_province;  //省份
        char *m_city;  //城市
        char *m_district;  //区(市区)
};
//实现Student类
Student::Student(char *name, int age, float score): m_name(name), m_age(age), m_score(score){ }
void Student::show(Address *addr){
    cout<<m_name<<"的年龄是 "<<m_age<<",成绩是 "<<m_score<<endl;
    cout<<"家庭住址:"<<addr->m_province<<"省"<<addr->m_city<<"市"<<addr->m_district<<"区"<<endl;
}
//实现Address类
Address::Address(char *province, char *city, char *district){
    m_province = province;
    m_city = city;
    m_district = district;
}
int main(){
    Student stu("小明", 16, 95.5f);
    Address addr("陕西", "西安", "雁塔");
    stu.show(&addr);

    Student *pstu = new Student("李磊", 16, 80.5);
    Address *paddr = new Address("河北", "衡水", "桃城");
    pstu -> show(paddr);
    return 0;
}

第 24 行代码将 Student 类声明为 Address 类的友元类,声明语句为:

friend class Student;

有的编译器也可以不写 class 关键字,不过为了增强兼容性还是建议写上。

使用友元类时注意:

  1. 友元关系不能被继承。

  2. 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。

  3. 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明

除非有必要,一般不建议把整个类声明为友元类,而只将某些成员函数声明为友元函数,这样更安全一些。