owned this note
owned this note
Published
Linked with GitHub
---
title: 'Class 運用 - operator、friend、explicit'
disqus: kyleAlien
---
Class 運用 - operator、friend、explicit
===
## OverView of Content
如有引用參考請詳註出處,感謝 :smile:
[TOC]
## Refernce & 副本
* 返回使用 reference & 副本的差別在,副本是函式內創建靜態變數 (無連結),**在 ==Func 結束前會產生其副本== 讓呼叫的函式取得**,最後函式結束後釋放靜態區域變數
:::warning
**使用哪個要依照需求而定,不能一味地看效率**
:::
```cpp=
/**
* 宣告
*/
class Score {
private:
int score;
public:
Score(int score = 0); // 預設引數
const Score& Sum_Ref(Score& s) const; // Refernce 函數
const Score Sum_Cpy(const Score& s) const; // 副本
int getScore() {
return score;
};
};
// --------------------------------------------------------------------
/**
* 實做
*/
#include "TestClass.h"
Score::Score(int score) {
this->score = score;
}
const Score& Score::Sum_Ref(Score& s) const { // "1. "
s.score += this->score;
return s;
}
const Score Score::Sum_Cpy(const Score& s) const { // "2. "
Score c; // default construct
c.score = this->score + s.score;
return c; // 返回前會呼叫 Score 複製函數
}
// --------------------------------------------------------------------
/**
* 客戶端使用
*/
#include "TestClass.h"
int main() {
Score s1(30), s2(30), s3(30), s4(30);
Score result_1 = s1.Sum_Ref(s2); //"3. "
std::cout << "s1 score: " << s1.getScore()
<< ", s2 score: " << s2.getScore()
<< ", After Ref sum score: " << result_1.getScore() << std::endl;
Score result_2 = s3.Sum_Cpy(s4);
std::cout << "s3 score: " << s3.getScore()
<< ", s4 score: " << s4.getScore()
<< ", After Ref sum score: " << result_2.getScore() << std::endl;
return 0;
}
```
1. `Sum_Ref` 宣告為**返回一個 Reference 物件**,並且引數為可改變的物件,==效率較高==
2. `Sum_Cpy` 宣告為返回一個物件,該物件是 **==新的物件==,其++生命週期在 Function 開始到結束++**,在**釋放記憶體前 return 物件,複製該物件內容到呼叫函數取得**,==效率較低==
3. 改變 s2 的內容,因為是傳 reference 進入不會新建物件
**--實作--**
> 
## operator 運算子多載
* operator 運算子多載可以讓使用起來更好看 (儘管 function name 就是 opeator 很奇怪) ;運算子函數的規格,**operator ==++op++==(argument...)**,**op 是運算符號,但++它還是一個函數++**
:::warning
**不能多載 C++ 沒有的運算子**
:::
C++ 運算子可以有多種意義 (如同函數多載),**C++ 會依據==引數的數量== & ==型態==來決定使用哪個運算子**
```cpp=
/**
* 宣告
*/
class Score {
private:
int score;
public:
Score(int score = 0);
const Score& Sum_Ref(Score& s) const;
const Score Sum_Cpy(const Score& s) const;
const Score operator+(const Score& s) const; // 新增
int getScore() {
return score;
};
};
/**
* 實作 新增 operator+,有無空白都可以 operator + 也可
*/
const Score Score::operator+(const Score& s) const { //"1. "
Score c; // default construct
c.score = this->score + s.score;
return c;
}
/**
* 客戶端使用
*/
#include "TestClass.h"
int main() {
Score s1(30), s2(50);
Score result_1 = s1.Sum_Cpy(s2);
std::cout << "After function sum score: " << result_1.getScore() << std::endl;
Score result_2 = s1 + s2; //"1. "
std::cout << "After + sum score: " << result_2.getScore() << std::endl;
Score result_3 = s1.operator +(s2); //"2. "
std::cout << "After operator+ sum score: " << result_3.getScore() << std::endl;
return 0;
}
```
1. 其內部實現如同 `SumCpy`,創建一個新物件
2. 可以用 func 全名,**`operator+` 就是 func 名**,功能如同**使用 + 號**,\+ 號是 operator 的簡寫
**--實作--**
> 
### operator 函數回傳值 - 重複使用
* 這要考慮到,**符號的==結合性==**,而 **加號的結合性為 ==左到右==**;如果上面使用 `s1 + s2 + s3` 會轉換成
1. **s1.opertor**(s2 + s3);
> 由最左邊的 s1 開始轉換 operator()
2. s1.opertor(**s2.opertor**(s3)); 以此類推
> 再轉換到 s2 operator()
```cpp=
#include "TestClass.h"
int main() {
Score s1(30), s2(50), s3(70);
// 直接呼叫副本 Function
Score result_1 = s1.Sum_Cpy(s2).Sum_Cpy(s3);
std::cout << "After function sum score: " << result_1.getScore() << std::endl;
// 使用運算符重載
Score result_2 = s1 + s2 + s3;
std::cout << "After + sum score: " << result_2.getScore() << std::endl;
// 呼叫運算符重載 function
Score result_3 = s1.operator +(s2).operator +(s3);
std::cout << "After operator+ sum score: " << result_3.getScore() << std::endl;
return 0;
}
```
**--實作--**
> 新增 s3,並連續加法,三種方法都可以得到相同結果
>
> 
### 多載限制 - operator 限制
1. **不能違反原始運算子的 ++語法規則++**
```cpp=
int x;
Score s;
% x; // 原始操作 不合法
% s; // 使用 operator 複寫,但操作 不合法
```
2. **不能產生新的符號**
> Ex: 想產生一個沒有的 `__` 符號 operator__(),這會導致編譯失敗
3. **引數至少要有一個使用者自定義型態**,這樣才不會與原始符號衝突 (也就是要有一個參數)
> Ex: operator+(const Score& s)
* 還有以下的符號 **不能重載 !**
| 運算子 | 描述 |
| ---------------- | ------------------------------ |
| sizeof | 計算該類別佔多少 byte 記憶體 |
| . | 成員運算子,struct、class 使用 |
| .* | 成員指標 運算子 |
| :: | ==範疇== 運算子 |
| ?: | 三元條件 運算子 |
| typeid | RTTI 運算子 |
| const_cast | 轉換 const 型態 |
| dynamic_cast | 轉換 dynamic 型態 |
| reinterpret_cast | 轉換 reinterpret 型態 |
| static_cast | 轉換 static 型態 |
* 以下運算子 **++不能透過非成員函數++ 加載 (也就是 friend 函數不可加載)**,會破壞順序
| 運算子 | 描述 |
| -------- | -------- |
| = | 指定 運算子 |
| () | 函數呼叫 運算子 |
| [] | ==標註== 運算子 |
| -> | 指標間接抓取 運算子 |
## friend 夥伴函數
之前有提到,**存取 private 成員資料的唯一方式是透過函數**,但還有一個方法可以提取到私有變量,就是 **使用 friend 宣告的函數,==friend 函數可取得 private 變量==**
:::info
記憶:因為是好朋友,所以可以使用自身的私有物品
:::
* friend 有三種變形
1. 夥伴 **函數**
2. 夥伴 **類別**
3. 夥伴 **成員函數**
* 需要 friend 的原因在於,類別多載二元運算時 (兩個引數稱二元) 產生的例外狀況
```cpp=
// 以上面的 Score 為例, 新增一個普通變數
// 原型
const Score operator+(const int i) const;
// 實作
const Score Score::operator +(int i) const {
Score c; // default construct
c.score = this->score + i;
return c;
}
// 客戶端
int main() {
Score s1(30);
Score result_1 = s1 + 10; // 一般的正常用法,但如果反過來加呢 ? 10 + s1
std::cout << "result: " << result_1.getScore() << std::endl;
return 0;
}
```
該呼叫是透過 s1 的方法 `operator+()`,但是如果反向過來變成 `10 + s1`,以語意來說是正確的,但是 **10 並不是類型,它沒有 operator+()** !!
### friend 宣告
* **==friend 是非成員函數==,定義時++不使用 :: (範疇運算)++**
> 非成員函數 : 不是用物件呼叫 (Call Function),它所擁有的引數都是顯示 (包括沒有 this)
* 在**類別內宣告 friend 函數,該函數就可以使用 private 的成員**
```cpp=
/**
* 新增 10 + obj 的宣告
*/
class Score {
private:
int score;
public:
Score(int score = 0);
const Score& Sum_Ref(Score& s) const;
const Score Sum_Cpy(const Score& s) const;
const Score operator+(const Score& s) const;
const Score operator+(const int i) const;
// "1. " 新增 friend 非成員函數
friend const Score operator+(const int i, const Score & s); // const;
int getScore() {
return score;
};
};
// --------------------------------------------------------------------
/**
* 實現
*/
// 這裡不能出現 friend 關鍵字
const Score operator +(const int i, const Score & s) {
//return s.operator +(i); "Same as s + i"
return s + i; //"2. " 反過來使用 Score 的 operator= 函數
}
// --------------------------------------------------------------------
/**
* 客戶端使用
*/
#include "TestClass.h"
int main() {
Score s1(30);
Score result_1 = s1 + 10;
std::cout << "result: " << result_1.getScore() << std::endl;
Score result_2 = 30 + s1; //"3. "
std::cout << "result: " << result_2.getScore() << std::endl;
Score result_3 = operator+(30, s1);
std::cout << "result: " << result_3.getScore() << std::endl;
return 0;
}
```
**--實作--**
> 
1. **宣告時需要 friend,定義時不用 firend**,**並且 ==非成員函數不可使用 [const 修飾 func](https://hackmd.io/32uk2K2ZSUKoCKYlidy-Dg?both#const-成員函數)==,因為沒有可以修飾的對象**
```cpp=
// 不能用 const,因為 Score 非自身的物件
friend const Score operator+(const int i, const Score & s) const;
```
:::warning
* **friend 這個關鍵字只能用 (出現) 在 class 中**,用在定義就會錯誤 (如下圖)
> 
:::
2. 既然 `friend` 可以使用該對象的資源,代表也可以使用對象的 function,那就可以直接反向後使用
3. **`30 + s1` 等價於` operator*(30, s1)`**
### friend & OOP 關係
* ++乍看之下 friend 似乎破壞了 OOP 的資料隱藏規則++,但其實 **它應該看做類別的延伸介面**,因為 **如果沒有了 `friend` 宣告,使用者也不能使用,==`friend` 宣告即可看成介面的延伸==**
* Class 宣告哪些函數可以存取 private 資料,而 `friend 函數非成員函數`、`類別函數`,**兩者是表示類別介面的兩種不同機制**
```cpp=
// 類別函數、成員函數
const Score operator+(const int i) const;
// 非成員函數
friend const Score operator+(const int i, const Score & s);
```
### 多載輸出 `<<` 運算子
* `<<` 除了在 C 中的基礎位移 (向左位移);C++ 中的 `<<` 運算子還可以使用在多種基本型態,這是因為 **`<<` 運算子多載了許多基礎類型**
```cpp=
// 在 Score 中多載 << 運算子,讓它自動輸出需要的資料
/*
* 宣告
*/
class Score {
private:
int score;
public:
Score(int score = 0);
const Score& Sum_Ref(Score& s) const;
const Score Sum_Cpy(const Score& s) const;
const Score operator+(const Score& s) const;
const Score operator+(const int i) const;
// why use friend ?
friend const Score operator+(const int i, const Score & s);
// Write friend of new <<
friend std::ostream& operator<<(std::ostream &os, const Score & s);
int getScore() {
return score;
};
};
/*
* 定義
*/
std::ostream& operator<<(std::ostream &os, const Score &s) {
os << "score: " << s.score << " ";
return os;
}
/*
* 客戶端使用
*/
#include "TestClass.h"
int main() {
Score s1(30);
Score result_1 = s1 + 10;
Score result_2 = 30 + s1;
Score result_3 = operator+(80, s1);
std::cout << result_1 << result_2 << result_3 << std::endl;
return 0;
}
```
使用 firend 函數的原因:**不能使用類別函式,但是又必須使用 class 的成員 Score,所以使用 friend**;不用在 ostream 原因很簡單,因為不好維護,不可能因為新增一個類別就去修改 ostream 對象
> 類別函式 : 沒有人在使用 result_1 <<,所以不能使用
### 成員函數 vs 非成員函數
| Type | 引數 | Example |
| -------- | -------- | -------- |
| 成員函數 | ==有隱式引數== | operator+(int i) + 隱式 this |
| 非成員函數 | ==都是顯示引數== | operator+(int i, Object & o) 引數都是顯式 |
* 並且不可同時定義兩個相同格式的函數,**會導致==模糊性錯誤==**,編譯期間就會錯誤
```cpp=
S1 = S2 + S3;
S1 = S2.operator+(S3); // 成員函數
S1 = operator+(S2, S3); // 非-成員函數
```
## 類別自動轉換 & 類型轉換
* 類別的下一個主題是型態轉換,**C++ 內建型態轉換,++當一個敘述是標準型態,指定給另一個標準型態時可以自動轉換++** (**==前提兩個類型要相容==**)
```cpp=
int main() {
long count = 8;
double time = 11;
int side = 3.33;
std::cout << "long : " << count << std::endl;
std::cout << "double : " << time << std::endl;
std::cout << "int : " << side << std::endl;
int * p1 = 10; // "1. "
int * p2 = (int *) 10; // "2. "
std::cout << "ptr1 : " << p1 << std::endl;
std::cout << "ptr2 : " << p2 << std::endl; // ptr2 : 0x0a
return 0;
}
```
1. 類型不相同,無法相互轉換,`int *` **指標雖然在電腦中是使用整數儲存,但是 ==指標與整數的概念完全不一樣==**
2. 仍然有方法可以透過轉換,讓 10 (整數型態 int) 強制轉換成指標 `(int*)`
**--實作--**
> 
### 類別型態建構 & 單一引數 & 轉換函數
* 在 C++ 中,**任何有 ++單一引數++ 的 construct,就像是==將該引數值,轉換成++類別型態++==**,該 **++單一引數的建構函數++ 就可作為 ==轉換函數==**
:::info
透過**類別型態找尋建構函數,建構出物件的過程稱為 ==自動轉換==**
:::
```cpp=
// 以下範例使用 石 & 磅 的轉換
/**
* class 宣告
*/
#include "iostream"
class stonewt {
private:
static const int Lbs_per_stn = 14; // 石 : 磅 = 1 : 14
int t_value = 0;// test 值是否被複製
int stone;
double p_lbs; // point lbs
double t_lbs; // total lbs
public:
stonewt();
stonewt(double lbs); // 磅 //"4. "
stonewt(int stone, double lbs); // 石 + 磅
~stonewt();
void Set_Value(int v);
void Show_Value();
void Show_Lbs();
void Show_Stone();
};
// -----------------------------------------------------------------
/**
* 定義
*/
#include "stonewt.h"
stonewt::stonewt() {
std::cout << "construct...empty" << std::endl;
stone = t_lbs = p_lbs = 0;
}
stonewt::stonewt(double lbs) {
std::cout << "construct...lbs" << std::endl;
this->stone = int(lbs) / Lbs_per_stn; // 石 整數
this->p_lbs = (int(lbs) & Lbs_per_stn) + (lbs - int(lbs)); // 磅 小數
this->t_lbs = lbs; // 全部磅數
}
stonewt::stonewt(int stone, double lbs) {
std::cout << "construct...stone & lbs" << std::endl;
this->stone = stone;
this->p_lbs = lbs;
this->t_lbs = stone * Lbs_per_stn + lbs;
}
stonewt::~stonewt() {
std::cout << "...destruct, address: " << this << std::endl;
}
void stonewt::Show_Lbs() {
std::cout << "total lbs: " << t_lbs << std::endl;
}
void stonewt::Show_Stone() {
std::cout << "total stone: " << stone << "." << p_lbs << std::endl;
}
void stonewt::Set_Value(int v) {
t_value = v;
}
void stonewt::Show_Value() {
std::cout << "value: " << t_value << std::endl;
}
// -----------------------------------------------------------------
/**
* 客戶端使用
*/
#include "./typeChange/stonewt.h"
int main() {
stonewt s; // "1. "
s.Set_Value(100);
s.Show_Value();
std::cout << "原來物件地址: " << &s << std::endl;
std::cout << "\n使用 \"=\" 指定" << std::endl;
s = 19.6; // "2. " 19.6 會創建 暫時物件!
s.Show_Value(); // "3."
return 0;
}
```
1. 使用 default 建構函數創建 (無引數 construct)
2. 當在使用 **==等號==賦予值,就會==再次創建一次暫時物件==,並使用 double 引數的創建函數,並使用 19.6 作為初始值,該物件只是暫時的**
> 暫時物件在賦予後立刻會銷毀
3. 這是為了測試,**證明++暫時物件 (19.6) 會複製到原本物件 s++,這種過程稱為==自動轉換==**
> 原本賦予值得 value = 100,暫時物件初始化 value = 0,暫時物件呼叫 `stonewt(double lbs)` 建構函數,只會複製其他成員,而沒有複製 value
4. 使用 `=` 會自動判斷類別型態,呼叫對應的建構函數,而 **double 就是類別型態**
**--實作結果--**
> 從結果也可以看到 **暫時物件會先被銷毀**
>
> 
### explicit 顯式型態
* **explicit 可以==關閉自動轉換==**,也就是關閉自動透過型態搜尋建構函數的功能
:::warning
**`explicit` 必須定義在 class 宣告中,不可用於定義中**
:::
* function 傳值會產生暫時物件,它會呼叫**複製建構函數**
```cpp=
#include "iostream"
class stonewt {
private:
static const int Lbs_per_stn = 14; // 石 : 磅 = 1 : 14
int stone;
double p_lbs; // point lbs
double t_lbs; // total lbs
public:
stonewt();
explicit stonewt(double lbs); // 磅; 在宣告中使用 `explicit`,禁止自動轉換
stonewt(int stone, double lbs); // 石 + 磅
~stonewt();
void Show_Lbs() const;
void Show_Stone() const;
};
```
> 
### 函數 - 暫時物件
* **暫時物件是==隱式轉換的一個中間產物==**,在 function 中同樣可以使用
```cpp=
class stonewt {
...
public:
...
void Show_Stone() const; // 新增
}
void stonewt::Show_Stone() const {
std::cout << "total stone: " << stone << "." << p_lbs << std::endl;
}
// ---------------------------------------------------------------------
/**
* 使用者
*/
#include "./typeChange/stonewt.h"
using std::cout;
using std::endl;
void display_1(const stonewt &s);
void display_2(const stonewt s);
int main() {
cout << "\n display_1 只使用引數傳入" << endl;
display_1(422.3); //"1. "
cout << "\n display_2 只使用引數傳入" << endl;
display_2(422.3);
return 0;
}
void display_1(const stonewt &s) { //"2. "
s.Show_Stone();
}
void display_2(const stonewt s) {
s.Show_Stone();
}
```
1. `display_1` & `display_2` 的第一個引數是 stonewt 物件,遇到 **double 的引數傳入時編譯器會 ==++自動尋找++配對的上的建構函數==**,並呼叫對應的建構函數 (如上一個小節所說)
2. 接收 reference,使用原物件 (以目前來講就是使用 `暫時物件` 的引用)
> 
### 反轉函數 - conversion function
* 一般來說我們會把數值轉換為物件,但相同 **的物件也可以反轉為另一個使用者需要的值**;**C++ 的運算子函數格式稱為 ==反轉函數==**
```cpp=
stonewt s1(275.3); // 一般使用,將 275.3 轉換為 stonewt 物件
double d = s1; // 反轉
```
* 函數格式 : **==operator <類型>()==;++<類型> 代表了轉換的目標型態++**,並且轉換函數遵循以下條件
1. 必須是 **類別成員**
2. 不能指定回傳值 (有一定的格式)
3. 不能有引數
```cpp=
#include <iostream>
//using namespace std;
using std::cout;
using std::endl;
class MyClass {
private:
int studentCount;
public:
MyClass(int count = 0);
~MyClass();
// 反轉函數
operator int();
operator std::string();
};
MyClass::MyClass(int count) : studentCount(count) {
cout << "MyClass construct." << endl;
}
MyClass::operator int() {
return studentCount;
}
MyClass::operator std::string() {
return std::to_string(studentCount);
}
MyClass::~MyClass() {
cout << "MyClass deconstruct." << endl;
}
int main() {
MyClass clz = 10;
int val = clz; // 呼叫反轉函數
cout << "convert int: " << val << endl;
std::string valStr = clz; // 呼叫反轉函數
cout << "convert string: " << valStr << endl;
return 0;
}
```
> 
* 反轉函數的使用又分為,**^1.^ ++隱性指定(implicit)++、^2.^ ++明確指定(explicit)++**
* ==**隱性指定**==:**指定++反轉函數 operator int/double 類型++**
```cpp=
int i1 = s1;
double d1 = s1;
```
* ==**顯示指定**==:呼叫目標對象
```cpp=
int i2 = int(s1);
double d2 = (double)s1;
```
```cpp=
/**
* class 宣告
*/
class stonewt {
private:
static const int Lbs_per_stn = 14; // 石 : 磅 = 1 : 14
int stone;
double p_lbs; // point lbs
double t_lbs; // total lbs
public:
stonewt();
stonewt(double lbs); // 磅
stonewt(int stone, double lbs); // 石 + 磅
~stonewt();
stonewt(int v);
void Show_Lbs() const;
void Show_Stone() const;
// convert func "1. "
operator int() const; // const 可有可無
operator double() const;
explicit operator float() const; // "3." 關閉隱式轉換
};
/**
* class 定義
*/
stonewt::operator int() const {
return int(t_lbs + 0.5); // 四捨五入
}
stonewt::operator double() const {
return t_lbs;
}
stonewt::operator float() const {
return t_lbs;
}
/**
* 客戶端 使用
*/
#include "./typeChange/stonewt.h"
using std::cout;
using std::endl;
int main() {
stonewt s1(275.3);
s1.Show_Lbs();
// 隱式指定
int i1 = s1;
cout << "int i1 = s1 : " << i1 << endl;
double d1 = s1;
cout << "double d1 = s1 : " << d1 << endl;
int i2 = int(s1);
cout << "int i2 = int(s1) : " << i2 << endl;
double d2 = double(s1);
cout << "double d2 = double(s1) : " << d2 << endl;
double d3 = int(s1);
cout << "double d3 = int(s1) : " << d3 << endl;
float f1 = float(s1);
cout << "float f1 = float(s1) : " << f1 << endl;
//float f2 = s1; //"3. " can't implicit change, because use explicit
return 0;
}
```
1. **反轉函數++沒有回傳值++、沒有引數,如果有使用 const function 描述會讓使用者使用的更加放心**
2. 顯式指定,由使用者規定自己需要的轉型,可規定轉型 int,但用 double 參數接收,結果會是 operator int funtion 的操作結果
3. 可以**使用 explicit 關閉隱式自動轉換**
**--實做--**
> 
### 成員函數 & friend 夥伴函數
* `成員函數`、`friend` 兩種方法都可重新定義符號操作
```cpp=
/**
* 宣告
*/
class stonewt {
private:
...
public:
...
//member
const stonewt operator+(const stonewt& s) const;
// friend
friend const stonewt operator+(const stonewt& s, const stonewt& s2); // 非成員函數不可使用 const
};
/**
* 定義新增
*/
// member
const stonewt stonewt::operator+(const stonewt& s) const {
std::cout<< "member..." << std::endl;
stonewt newS = s.t_lbs + this->t_lbs;
return newS;
}
// friend
const stonewt operator+(const stonewt& s1, const stonewt& s2) {
std::cout<< "friend..." << std::endl;
stonewt newS = s1.t_lbs + s2.t_lbs;
return newS;
}
```
* 上面定義兩個相同功能的函數,**==功能相同導致 candidate (候選) 發生==**,**會產生++混淆錯亂++**
> 
## Appendix & FAQ
:::info
:::
###### tags: `C++` `friend & operator` `explicit`