有些情况下,允许特定的非成员函数访问一个类的私有成员,同时仍阻止一般的访问,这是很方便做到的。例如被重载的操作符,如输入或输出操作符,经常需要访问类的私有数据成员。
友元(friend)机制允许一个类将对其非公有成员的访问权授予指定的函数或者类,友元的声明以friend开始,它只能出现在类定义的内部,友元声明可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员,所以它们不受其声明出现部分的访问控制影响。通常,将友元声明成组地放在类定义的开始或结尾是个好主意。
1、友元函数
定义:友元函数是指某些虽然不是类成员函数却能够访问类的所有成员(成员变量、成员函数)的函数。类授予它的友元特别的访问权,这样该友元函数就能访问到类中的所有成员。(private、protected、public)
#include <iostream>
using namespace std;
class A
{
public:
friend void set_show(int x, A &a); //该函数是友元函数的声明 放到类里面任何位置都可以,
//一般建议放到类的开始或是结束
private:
int data;
};
void set_show(int x, A &a) //友元函数定义,为了访问类A中的成员
{
a.data = x;
cout << a.data << endl;
}
int main(void)
{
class A a;
set_show(1, a);
return 0;
}
2、友元类
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。
(如果你是我的友元类,那么你就可以在你的成员函数中访问我的所有成员(包括私有成员变量和函数))
关于友元类的注意事项:
- 友元关系不能被继承。
- 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
- 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明。
#include <iostream>
using namespace std;
class A
{
public:
friend class C; //这是友元类的声明 声明C为A的友元类
private:
int data;
};
class C //友元类定义,为了访问类A中的成员
{
public:
void set_show(int x, A &a) { a.data = x; cout<<a.data<<endl;}
};
int main(void)
{
class A a;
class C c;
c.set_show(1, a);
return 0;
}
3、友元成员函数
作用:只在某个成员函数中可以访问 另一个类的所有成员变量和成员函数。
使类B中的成员函数成为类A的友元函数,这样类B的该成员函数就可以访问类A的所有成员了。
当用到友元成员函数时,需注意友元声明和友元定义之间的相互依赖,在该例子中,类B必须先定义,否则类A就不能将一个B的函数指定为友元。然而,只有在定义了类A之后,才能定义类B的该成员函数。更一般的讲,必须先定义包含成员函数的类,才能将成员函数设为友元。另一方面,不必预先声明类和非成员函数来将它们设为友元。
#include <iostream>
using namespace std;
class A; //当用到友元成员函数时,需注意友元声明与友元定义之间的互相依赖。这是类A的声明
class B
{
public:
void set_show(int x, A &a); //该函数是类A的友元函数
};
class A
{
public:
friend void B::set_show(int x, A &a); //该函数是友元成员函数的声明
private:
int data;
void show() { cout << data << endl; }
};
void B::set_show(int x, A &a) //只有在定义类A后才能定义该函数,毕竟,它被设为友元是为了访问类A的成员
{
a.data = x;
cout << a.data << endl;
}
int main(void)
{
class A a;
class B b;
b.set_show(1, a);
return 0;
}
友元小结:
在需要允许某些特定的非成员函数访问一个类的私有成员(及受保护成员),而同时仍阻止一般的访问的情况下,友元是可用的。
优点:
- 可以灵活地实现需要访问若干类的私有或受保护的成员才能完成的任务;
- 便于与其他不支持类概念的语言(如C语言、汇编等)进行混合编程;
- 通过使用友元函数重载可以更自然地使用C++语言的IO流库。
缺点:
- 一个类将对其非公有成员的访问权限授予其他函数或者类,会破坏该类的封装性,降低该类的可靠性和可维护性。
4. 友元函数与运算符重载
1、运算符声明成类成员还是声明独立友元函数建议准则:
- C++规定,赋值运算符=、数组下标运算符[]、函数调用运算符、成员访问运算符->在重载时必须声明为类的成员函数
- 流运算符<<、>>、类型转换运算符不能定义类的成员函数,只能是友元函数
- 一元运算符和和赋值运算符重载时,一般声明类的成员函数
- 二元运算符在运算符重载时,一般声明为友元函数
2、问题引出
Test test(10), test1(5);
test1 = test + 10; //正常执行
test1 = 10 + test; //编译错误
所以使用友元函数可以解决此问题
#include "stdafx.h"
#include <iostream>
#include <algorithm>
using namespace std;
class Test
{
public:
Test();
Test(int value) :m_value(value) {}
~Test();
//重载 + 运算符 - * / %运算符类似
const Test& operator + (const Test &test) const;
//重载赋值运算符
const Test& operator = (const Test &test);
//二元运算符建议用友元函数的方式重载
friend Test operator + (int intValue, const Test &test);
//流运算符一般只能用友元的方式重载
//今后就可以重载流运算符,以便封装某个对象的打印格式
friend ostream & operator << (ostream &out,const Test &test);
//重载输入运算符
friend istream & operator >> (iostream &cin, Test &test);
int GetValue() { return m_value; }
private:
int m_value;
};
Test::Test():m_value(0)
{
cout << "调用无参构造函数" << endl;
}
Test::~Test()
{
}
//重载 + 运算符
const Test& Test::operator + (const Test & test) const
{
Test T(this->m_value + test.m_value);
return T;
}
const Test& Test::operator = (const Test &test)
{
if (this == &test) return *this; //判断是否是给自己赋值
this->m_value = test.m_value;
return *this; //返回当前对象的引用
}
//友元函数不需要域运算符了
Test operator + (int intValue, const Test &test)
{
return Test(intValue + test.m_value);
}
//流运算符一般只能用友元的方式重载
ostream & operator << (ostream &out, const Test &test)
{
out << test.m_value;
return out;
}
istream & operator >> (iostream &cin, Test &test)
{
cin >> test.m_value;
return cin;
}
void main()
{
Test test(10), test1(5);
Test test2 = 10 + test + 10;
cout << test2.GetValue() << endl;
cout << test << endl;
system("pause");
}
友元函数破坏了类的封装性,一般不建议使用
运算符重载可参考博客:C++运算符重载