0%

C++ 迷你系列(一)友元函数与友元类

引言

封装是对象的一大特征,它可以选择性的对外暴露属性和方法。但是有没有一种方式可以把自己的内部属性暴露给指定的函数、方法或者类呢?答案是肯定的,那就是友元。网络上大部分文章介绍的都是在同一个文件中使用友元,而本文章将示例分在不同文件中进行介绍。

友元函数

parent.hpp 头文件中声明了类 Parent,它有两个属性,分别是公开的 username 及私有的 age。同时声明了带参构造函数及打印基础信息的成员方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef CPPDEMO_PARENT_H
#define CPPDEMO_PARENT_H

#include <string>

class Parent
{
public:
std::string username;
private :
int age;

public:
Parent(std::string username, int age);
void print_info() const;
};

#endif //CPPDEMO_PARENT_H

以下 parent.cpp 是具体的实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

#include <iostream>
#include <utility>
#include "parent.hpp"

using namespace std;

Parent::Parent(std::string username, int age)
{
this->username = std::move(username);
this->age = age;
}

void Parent::print_info() const
{
cout << "username is : " << this->username << " age is : " << this->age << endl;
}

以下的 main.cpp 是我们的测试类:

1
2
3
4
5
6
7
8
#include "parent.hpp"

int main()
{
Parent p("father", 38);
p.print_info();
return 0;
}

控制台输出本对象的基础信息:

1
# username is : father age is : 38

我们知道,ageParent 比较私密的属性,毕竟谁也不想把自己的年龄告诉其他人,除非把自己的信息通过某种特殊的途径对外告知,比如:资料卡。

我们在类 Parent 中添加传递信息的资料卡函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef CPPDEMO_PARENT_H
#define CPPDEMO_PARENT_H

#include <string>

class Parent
{
// 将 card 函数声明为友元函数
friend void card(Parent &);

public:
std::string username;
private :
int age;

public:
Parent(std::string username, int age);

void print_info() const;
};

#endif //CPPDEMO_PARENT_H

我们在 main.cpp 中添加对 void card(Parent &) 函数的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include "parent.hpp"

using namespace std;

void card(Parent &parent)
{
cout << "others get the username: " << parent.username << " and age is : " << parent.age << endl;
}

int main()
{
Parent p("father", 38);
card(p);
return 0;
}

控制台得到以下输出:

1
# others get the username: father and age is : 38

外部通过 card 方法拿到了 Parent 类中的私有字段 age

友元类

作为父母,肯定愿意把自己的“一切”告诉孩子!所以我们改造类 Parent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef CPPDEMO_PARENT_H
#define CPPDEMO_PARENT_H

#include <string>

class Parent
{
// 将 child 类声明为友元类
friend class Child;

public:
std::string username;
private :
int age;

public:
Parent(std::string username, int age);

void print_info() const;
};

#endif //CPPDEMO_PARENT_H

接着我们定义 Child 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef CPPDEMO_CHILD_H
#define CPPDEMO_CHILD_H

// 提前告诉编译器,Parent 是类,所以 Child 类定义中,别着急报错
class Parent;

class Child
{
private:
Parent *parent;

public:
// 由于是单参数构造器,需要显示的声明,避免隐式转换
explicit Child(Parent *parent);

void parent_info();
};

#endif //CPPDEMO_CHILD_H

Child 类的实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
#include "child.hpp"
#include "parent.hpp"

using namespace std;

// 初始化列表方式
Child::Child(Parent *parent) : parent(parent)
{
}

// 访问 Parent 的私有属性
void Child::parent_info()
{
std::cout << "child get the parent info username is : " <<
this->parent->username << " age is : " << this->parent->age << endl;
}

main.cpp 进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "parent.hpp"
#include "child.hpp"

using namespace std;


int main()
{
Parent p("father", 38);
Child child(&p);
child.parent_info();
return 0;
}

输出:

1
# child get the parent info username is : father age is : 38

成员友元函数

如果把类变成友元类,那么友元类中的所有方法都能访问私有属性了。那么如何限制指定的方法访问呢?此时就需要成员友元函数了。

改造 Parent 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#ifndef CPPDEMO_PARENT_H
#define CPPDEMO_PARENT_H

#include <string>
#include "child.hpp"

class Parent
{
// 改为成员友元函数
friend void Child::parent_info();

public:
std::string username;
private :
int age;

public:
Parent(std::string username, int age);

void print_info() const;
};

#endif //CPPDEMO_PARENT_H

Child 类新增 all_parent_info 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include "child.hpp"
#include "parent.hpp"

using namespace std;

Child::Child(Parent *parent) : parent(parent)
{
}

void Child::parent_info()
{
std::cout << "child get the parent info username is : " <<
this->parent->username << " age is : " << this->parent->age << endl;
}

void Child::all_parent_info()
{
// 由于 all_parent_info 并不是友元方法,所以这里会提示:'age' is a private member of 'Parent' declared private here
std::cout << "child get the parent info username is : " <<
this->parent->username << " age is : " << this->parent->age << endl;
}

我们运行一下看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include "parent.hpp"
#include "child.hpp"

using namespace std;

int main()
{
Parent p("father", 38);
Child child(&p);
child.parent_info();
child.all_parent_info();
return 0;
}

得到以下输出:

1
2
3
4
5
# /Users/tubetrue01/CLionProjects/cppDemo/child.cpp:24:71: error: 'age' is a private member of 'Parent'
this->parent->username << " age is : " << this->parent->age << endl;
^
# /Users/tubetrue01/CLionProjects/cppDemo/parent.hpp:19:9: note: declared private here
int age;

由此说明 all_parent_info() 方法并不具备访问私有属性的权限。