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