--- 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)