我們已知道類具有封裝和信息隱藏的特性。只有類的成員函數才能訪問類的私有成員,程序中的其他函數是無法訪問私有成員的。非成員函數可以訪問類中的公有成員,但是如果將數據成員都定義為公有的,這又破壞了隱藏的特性。另外,應該看到在某些情況下,特別是在對某些成員函數多次調用時,由於參數傳遞,類型檢查和安全性檢查等都需要時間開銷,而影響程序的運行效率。
為了解決上述問題,提出一種使用友元的方案。友元是一種定義在類外部的普通函數,但它需要在類體內進行說明,為了與該類的成員函數加以區別,在說明時前面加以關鍵字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
