C++之友元机制(友元函数和友元类)

一、为什么引入友元机制?
  总的来说就是为了让非成员函数即普通函数或其他类可以访问类的私有成员,这确实破坏了类的封装性和数据的隐蔽性,但为什么要这么做呢?
  (c++ primer:尽管友元被授予从外部访问类的私有部分的权限,但它并不与面向对象的编程思想相悖,相反,他们提高了公有接口的灵活性)。要理解这句话,就必须知道友元形成的过程:(任何函数,或者成员函数或者类想成为某个类的友元,这是由这个类来决定的,而不能从外部强加友情)

  我们已知道类具有封装和信息隐藏的特性。只有类的成员函数才能访问类的私有成员,程序中的其他函数是无法访问私有成员的。非成员函数可以访问类中的公有成员,但是如果将数据成员都定义为公有的,这又破坏了隐藏的特性。另外,应该看到在某些情况下,特别是在对某些成员函数多次调用时,由于参数传递,类型检查和安全性检查等都需要时间开销,而影响程序的运行效率。

  为了解决上述问题,提出一种使用友元的方案。友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,为了与该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率(即减少了类型检查和安全性检查等都需要的时间开销),但是,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员。

 
二、友元函数的形态
    友元主要分为友元函数和友元类,在电视机和遥控器的例子中,电视机和遥控器都可以控制电视(换台,调节音量,切换模式等),但电视机和遥控器不是派生继承的关系,为了复用和控制电视机,就可以把遥控器类声明为电视机的友元类,这样遥控器类中的所有函数就可以使用电视机类中的所有成员,包括复用电视机中的所有操作,但是发现除了频道这个电视机的私有成员,其他的私有成员在电视机的公共接口中都可以访问,那么,就没必要吧整个遥控器类声明为友元类,只要把直接操作频道的这个函数声明为电视机的友元函数即可。
    【友元函数】在类内部,只能申明函数原型,不能定义函数体,不是访问级别的限制。
    【友元类】友类的每个成员都可以访问另一个类中的保护成员和私有成员。
 
注意:调用私有成员需要制定对象,即:对象名.变量。对于普通私有成员变量是通过对象访问,对于私有静态变量是通过类访问

1.友元函数的简单介绍

1.1为什么要使用友元函数

在实现类之间数据共享时,减少系统开销,提高效率。如果类A中的函数要访问类B中的成员(例如:智能指针类的实现),那么类A中该函数要是类B的友元函数。具体来说:为了使其他类的成员函数直接访问该类的私有变量。即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数。

实际上具体大概有下面两种情况需要使用友元函数:(1)运算符重载的某些场合需要使用友元。(2)两个类要共享数据的时候。

1.2使用友元函数的优缺点

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

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

 

2.友元函数的使用

2.1友元函数的参数:

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

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

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

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

2.2友元函数的位置

因为友元函数是类外的函数,所以它的声明可以放在类的私有段或公有段且没有区别。

2.3友元函数的调用

可以直接调用友元函数,不需要通过对象或指针

2.4友元函数的分类:

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

普通函数友元函数

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

语法:

声明: friend + 普通函数声明

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

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

调用:类似普通函数,直接调用  

 ///
 /// @file    Point.cc
 /// @author AlexCthon(AlexCthon@163.com)
 /// @date    2018-06-12 09:40:14
 ///
#include <math.h>
#include <iostream>
using std::cout;
using std::endl;

class Point
{
public:
	Point(int ix = 0, int iy = 0)
	: _ix(ix)
	, _iy(iy)
	{
		cout << "Point(int=0, int=0)" << endl;
	}

	void print() const
	{
		cout << "(" << _ix
			 << "," << _iy
			 << ")" << endl;
	}

	int getX() const {	return _ix;	}

	int getY() const {	return _iy;	}

	//友元之普通函数
	friend float distance(const Point & lhs, const Point & rhs);

private:
	int _ix;
	int _iy;
};

#if 0
float distance(const Point & lhs, const Point & rhs)
{
	return sqrt((lhs.getX() - rhs.getX()) * (lhs.getX() - rhs.getX()) + 
				(lhs.getY() - rhs.getY()) * (lhs.getY() - rhs.getY()));
}
#endif
 
float distance(const Point & lhs, const Point & rhs)
{
	return sqrt((lhs._ix - rhs._ix) * (lhs._ix - rhs._ix) + (lhs._iy - rhs._iy) * (lhs._iy - rhs._iy));
}
int main(void)
{
	Point pt1(1, 2);
	Point pt2(3, 4);
	cout << "pt1和pt2之间的距离: " << distance(pt1, pt2) << endl;
	return 0;
}

 

Y的一个成员函数为类X的友元函数

目的:使类Y的一个成员函数成为类X的友元,具体而言:在类Y的这个成员函数中,借助参数X,可以直接以X的私有变量

语法:

声明位置:声明在公有中 (本身为函数)

声明:friend + 成员函数的声明

调用:先定义Y的对象y---使用y调用自己的成员函数---自己的成员函数中使用了友元机制

代码: 

#include <math.h>
#include <iostream>
using std::cout;
using std::endl;

class Point;//类的前向声明

class Line
{
public:
	float distance(const Point & lhs, const Point & rhs);
};

class Point
{
public:
	Point(int ix = 0, int iy = 0)
	: _ix(ix)
	, _iy(iy)
	{
		cout << "Point(int=0, int=0)" << endl;
	}

	void print() const
	{
		cout << "(" << _ix
			 << "," << _iy
			 << ")" << endl;
	}
	//友元之成员函数
	friend float Line::distance(const Point & lhs, const Point & rhs);

private:
	int _ix;
	int _iy;
};


#if 0
float distance(const Point & lhs, const Point & rhs)
{
	return sqrt((lhs.getX() - rhs.getX()) * (lhs.getX() - rhs.getX()) + 
				(lhs.getY() - rhs.getY()) * (lhs.getY() - rhs.getY()));
}
#endif
 
float Line::distance(const Point & lhs, const Point & rhs)
{
	return sqrt((lhs._ix - rhs._ix) * (lhs._ix - rhs._ix) + 
				(lhs._iy - rhs._iy) * (lhs._iy - rhs._iy));
}

int main(void)
{
	Point pt1(1, 2);
	Point pt2(3, 4);

	Line line;
	cout << "pt1和pt2之间的距离: " << line.distance(pt1, pt2) << endl;

	return 0;
}

  

Y的所有成员函数都为类X友元函数友元类

目的:使用单个声明使Y类的所有函数成为类X的友元,它提供一种类之间合作的一种方式,使类Y的对象可以具有类X和类Y的功能。

语法:

声明位置:公有私有均可,常写为私有(把类看成一个变量)

声明: friend + 类名(不是对象)

补充: 当用到友元成员函数时,需注意友元声明与友元定义之间的互相依赖。类的前置声明。

#include <math.h>
#include <iostream>
using std::cout;
using std::endl;

class Point;//类的前向声明

class Line
{
public:
	float distance(const Point & lhs, const Point & rhs);

	void setPoint(int ix, int iy, Point & pt);

private:
	int _iz;
};

class Point
{
public:
	Point(int ix = 0, int iy = 0)
	: _ix(ix)
	, _iy(iy)
	{
		cout << "Point(int=0, int=0)" << endl;
	}

	void print() const
	{
		cout << "(" << _ix
			 << "," << _iy
			 << ")" << endl;
	}
	//友元之友元类
	//friend class Line;
	friend Line;// 一定是破坏了类的封装性
				//友元是单向的, 不具备传递性, 不能继承
				//
				//A -> B, B -> C  ==> A -> C

	void setZ(Line & line, int iz)
	{//Point不能访问Line的私有成员
		line._iz = iz;
	}

private:
	int _ix;
	int _iy;
};
 
float Line::distance(const Point & lhs, const Point & rhs)
{
	return sqrt((lhs._ix - rhs._ix) * (lhs._ix - rhs._ix) + 
				(lhs._iy - rhs._iy) * (lhs._iy - rhs._iy));
}

void Line::setPoint(int ix, int iy, Point & pt)
{
	pt._ix = ix;
	pt._iy = iy;
}

int main(void)
{
	Point pt1(1, 2);
	Point pt2(3, 4);

	Line line;
	cout << "pt1和pt2之间的距离: " << line.distance(pt1, pt2) << endl;

	line.setPoint(5, 6, pt1);
	cout << "pt1 = ";
	pt1.print();

	return 0;
}

    

小结:其实一些操作符的重载实现也是要在类外实现的,那么通常这样的话,声明为类的友元是必须滴。

 

4.友元函数和类的成员函数的区别

    友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。

  成员函数有this指针,而友元函数没有this指针。这点其实和静态成员函数一样,静态成员函数也是没有this指针的,所以它只能访问静态成员变量或者通过对象访问非静态成员变量。

   友元类是单向的,不可传递,不能被继承

 

class Rect  
{  
public:  
	Rect()      // 构造函数,计数器加1  
	{  
		count++;  
	}  
	//Rect(const Rect& r)
	//{
	//	width = r.width;
	//	height = r.height;
	//	count++;
	//}
	~Rect()     // 析构函数,计数器减1  
	{  
		count--;  
	}  
	static int getCount()       // 返回计数器的值  
	{  
		return count;  
	} 
	friend int get();
private:  
	int width;  
	int height;  
	static int count;       // 一静态成员做为计数器  
};  

int Rect::count = 0;        // 初始化计数器 ,静态成员变量必须要在类外部初始化 
int get()
{
	return Rect::count;//友元函数通过类访问私有静态成员变量
}
int main()  
{  
	Rect rect1;  
	cout<<"The count of Rect: "<<Rect::getCount()<<endl;//通过类访问公有静态成员函数,输出1  

	Rect rect2(rect1);   // 使用rect1复制rect2,此时应该有两个对象  
	cout<<"The count of Rect: "<<Rect::getCount()<<endl; //输出1
	cout << get() << endl;//输出1
	//cout << Rect::count << endl;//不能编译通过,不能访问私有成员。this只能访问类中的非静态成员变量或成员函数
	system("pause");
	return 0;  
}  

注意:怎么理解友元类是单向的,不可传递,不能被继承?

1、友元类不能被继承。这个不多说,很容易理解。

2、友元类是单向的。A->B,B->A?这是错误的。

3、友元类是不可传递的。A->B,B->C,   A=>C?这是错误的。

补充:当类的成员变量很多时,需要提供许多的get/set方法来实现成员变量的存取,在这种情况下,不妨用友元的方法。

 

三、运算符重载(使用友元函数)
 ///
 /// @file    Complex.cc
 /// @author  AlexCthon(AlexCthon@163.com)
 /// @date    2018-06-12 10:10:03
 ///
 
#include <iostream>
using std::cout;
using std::endl;

class Complex
{
public:
	Complex(double dreal = 0, double dimag = 0)
	: _dreal(dreal)
	, _dimag(dimag)
	{}

	void display() {
		cout << _dreal << " + " << _dimag << "i" << endl;
	}

	//成员函数的形式
	Complex operator+(const Complex & rhs)
	{
		return Complex(_dreal + rhs._dreal, _dimag + rhs._dimag);
	}

private:
	double _dreal;
	double _dimag;
};

 
int main(void)
{
	Complex c1(1, 2);
	Complex c2(3, 4);

	Complex c3 = c1 + c2;
	c3.display();

	Complex c4 = c1 + 5;// c1.operator+(5);
	c4.display();

	Complex c5 = 5 + c1;//operator+(5, c1);
	c5.display();

	return 0;
}

  

有关运算符重载更多细节,参考这篇博客:https://www.cnblogs.com/cthon/p/9181404.html

 
posted @ 2018-06-14 08:37  CTHON  阅读(3400)  评论(0编辑  收藏  举报