---
GA: G-RZYLL0RZGV
---
###### tags: `大一程設-下` `東華大學` `東華大學資管系` `基本程式概念` `資管經驗分享`
Friend Functions - 朋友函式
===
[TOC]
## 前言
在前面的幾份筆記中,大家應該對於「封裝」的概念應該有一定程度的熟悉了,相信對於要把 Member functions & Member variable (屬性與方法) 的權限設定為哪種已經非常了解。
而針對 private 的屬性與方法我們必須要透過 getter & setter 來取得與設值,這件事大家應該也非常了解。
但同時相信大家一定覺得這個過程實在是非常麻煩,我只是想要取得一個 private 屬性,卻要額外加上那麼多東西,心裡總是覺得有點說不過去。
對,所以 C++ 提供了一個叫做 Friend Functions 的工具,來讓我們存取 private member。
## 認識 Friend Functions
在真的看 Friend Functions 之前,我們先設想一個情境,再慢慢帶入到為何要 Friend Functions。
我們都知道一支手機門號都會有他隸屬的電信公司,比方說中華電信,台灣大哥大,遠傳等,而通常電信公司針對手機的方案都會提供網內互打不用錢。
在上面這個情境之下,我們必須確認到兩支手機是否隸屬於同一個電信公司,所以我們可能可以設計出下面這樣的類別。
> 下面這個例子不考慮繼承、多型,也不考慮各大門市獨立成類別,以最簡單只建立一個 Phone 類別就好。
```cpp=
class Phone{
public:
Phone(){ // default constructor
this->type = "";
this->phone_number = "";
}
Phone(string t, string pn){
this->phone_number = pn;
this->type = t;
}
string get_number(){
return this->phone_number;
}
string get_type(){
return this->type;
}
void set_number(string pn){
this->phone_number = pn;
}
void set_type(string t){
this->type = t;
}
bool equal(Phone& p1, Phone& p2){
if(p1.type == p2.type) return true;
else return false;
}
private:
string type; // 隸屬公司
string phone_number;
};
int main(){
Phone p1("中華電信", "0912345678");
Phone p2("台灣大哥大", "0987654321");
p1.equal(p1, p2);
p2.equal(p1, p2);
return 0;
}
```
相信這個類別非常簡單,最主要是在 equal 這個函式內可以確定傳入的兩個物件的所屬公司是否相同。但相信 main 的運作讓人覺得有點奇怪,如果今天有一台 p3,`p1.equal(p2,p3);` 這個寫法絕對令人感到納悶,這樣完全沒有確認到 p1。
而一個 member function 必須要實體化物件才能呼叫,這樣這個例子非常糟糕。
所以可以得知,這個寫法絕對是沒效率的,而且有邏輯上的缺陷,equal 這個 function 不能夠定義成 member function,所以我們可能可以改良一下。
```cpp=
class Phone{
public:
/*
* 這邊省略,與上面相同,但不包含 equal
*/
private:
string type; // 隸屬公司
string phone_number;
};
bool equal(Phone& p1, Phone& p2){
if(p1.get_type() == p2.get_type()) return true;
else return false;
}
int main(){
Phone p1("中華電信", "0912345678");
Phone p2("台灣大哥大", "0987654321");
equal(p1, p2);
return 0;
}
```
這邊我們把 equal 修改成一般的 function,且隸屬於這個 `main.cpp`,因為我們有在上面定義 Phone 這個類別,所以在同一個檔案下可以直接宣告一個函式,定義兩個 Phone 當作參數。
這邊你應該有感覺,邏輯來看比較正確了。
但這樣還是有一個缺點,這個 equal 函示要由我們自己去定義,要自己定義參數為何,內部實作也要我們自己寫,你應該可以看到上面第 12 行,我們不是寫 `p1.type` 而是寫 `p1.get_type()`,如果前面的封裝你有好好學,你一定知道為甚麼。
因為 equal 函式不是類別的 member,所以他沒辦法存取 private 屬性與方法,那你會說,就算把 equal 獨立出來,還是沒辦法做到直接存取 private member,還是很麻煩阿。
> 這就是需要 Friend Functions 的原因。
### Friend Functions 朋友函式
認識 Friend Functions 之前,有一些前提先講清楚。
* Friend Functions 一定是定義在 Class 內
* Friend Functions 不是 Class 的 Member Function
* Friend Functions 不會在 Class 內實作,在引用類別時我們自己實作
* Friend Functions 能夠透過傳入類別的參數存取 private member
我們來修改上面的範例,再好好說明。
```cpp=
class Phone{
public:
/*
* 這邊省略,與上面相同,但不包含 equal
*/
friend bool equal(Phone& p1, Phone& p2); // 先定義,不實作
private:
string type; // 隸屬公司
string phone_number;
};
// 我們自己實作 equal
bool equal(Phone& p1, Phone& p2){
if(p1.type == p2.type) return true;
else return false;
}
int main(){
Phone p1("中華電信", "0912345678");
Phone p2("台灣大哥大", "0987654321");
equal(p1, p2);
return 0;
}
```
應該可以發現上例我們在類別內加上了一個 Friend Functions,然後自己實作他,也能發現第 14 行我們能夠去存取 private 的 member。這樣我們的目的就達成了。
那你可能會問,為甚麼這樣就能做到,`friend` 到底做到了甚麼?
今天一個 function 若宣告為 `friend` 是告訴來引用此類別的人說,我們提供一個公開的方法(規格),且能夠讓你去存取 private member。但要做什麼,需要你自己來實作。
所以為什麼會叫 `Friend Functions`?
今天有一個類別叫做 Phone,他跟大家說,只要引用我的人,你們都是我的朋友,所以我提供一個公開的介面 (friend) 給你讓你來存取我的 private 屬性與方法,只要你符合我說的規定(EX : 回傳型別、參數型態)。
* 這邊的我代表的是 Phone 類別
* 你就是正在看這篇筆記的你
希望到這邊你已經理解 Friend Functions 的運作與由來了!
> 第 12 章會教各位把類別內容獨立到不同的檔案去,並把他們 include 進來。
## 為何要 call-by reference?
```cpp=
bool equal(Phone& p1, Phone& p2){
if(p1.type == p2.type) return true;
else return false;
}
```
相信參數的定義大家都看到我們使用 call-by reference,為甚麼呢?為什麼要與傳入的參數使用同樣的一塊記憶體?
相信大家學到這邊都已經了解,我們能夠定義類別來達成非常多事情,那透過上面這個 Phone 類別的範例,我們在 main 內實體化兩個 Phone 物件,所以想當然耳他會占用兩塊記憶體。
那大家想想看,一個 Phone 物件會占用多少記憶體呢?
int 佔 4 bytes,double 佔 8 bytes,可是 Phone 物件又不是基本型態,我怎麼知道他佔用多少記憶體?
答案就是你在類別內宣告的那些函式、變數、指標、陣列等,整個都會算在內,假設一個類別內有 5 個 int 變數、5 個 double 變數,這樣就有 20 + 40 = 60 bytes,所以我如果實體化一個物件就要在記憶體內佔 60 bytes,今天如果把物件當作參數傳進函式內,使用 call-by value 的話大家覺得會怎麼辦?
我們會把參數的物件建立一個 copy,所以在函式結束前等於多開出 60 bytes,那如果今天參數是 3 個同類別的物件,還用 call-by value,在函式結束期間就需要 180 bytes 的空間,而我們本來實體化的 3 個物件也還在,等於要耗費 360 bytes。
總結來說,當類別定義內容越大,一個物件被實體化所需要的記憶體就越大,如果使用 call-by value 會非常浪費記憶體,所以基本上針對類別物件作為參數這件事情,我們都會使用 call-by reference。
## [補充 - 自行閱讀] const 的用法
* [c++ const介紹](https://dotblogs.com.tw/Ace_Dream/2016/06/02/const)
* [C++ 筆記 - const 位置,用法總結](https://lionrex.pixnet.net/blog/post/120554734-c%2B%2B-%E7%AD%86%E8%A8%98---const-%E4%BD%8D%E7%BD%AE%EF%BC%8C%E7%94%A8%E6%B3%95%E7%B8%BD%E7%B5%90)
## Reference
* [C++ Friend 和 This 教學](https://vinesmsuic.github.io/2020/01/12/c++-friend/)
* [friend 函式、friend 類別](https://openhome.cc/Gossip/CppGossip/friendFunctionClass.html)