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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM