---
title: 'C++ 繼承變形 & 模板'
disqus: kyleAlien
---
C++ 繼承變形 & 模板
===
## OverView of Content
如有引用參考請詳註出處,感謝 :smile:
以下會說明私有繼承、保護繼承、樣板類別
[TOC]
## 私有繼承 Has-a 關係
在繼承類別中有提到 [**Has-a 關係**](https://hackmd.io/oM3IAtEVSCSn5Qh_ve6nOw#依賴-has-a) 有兩種方是可達成,各有各地訪問訪法,要特別注意使用特色規則
:::success
OOP UML 來說就是 **聚合** 的概念
:::
### 私有成員 (顯式)
* 將需要的類別加在 private 區塊,**作為私有成員變數,在內部就可以直接使用**
:::info
* **將關係類變為私有成員變數**
1. **初始化列表**使用 **++成員名稱++**
> 符號 `:`,後續還有要初始化就使用 `,` 串接
2. Has-a **++有++ 物件名稱**
3. 使用**成員問算子訪問成員方法**
:::
1. 宣告 Class:並在 `private` 區塊創建 (聚合) 私有成員 `valarray<double>`
```cpp=
/**
* 宣告
*/
#include <iostream>
#include <string>
#include <valarray>
using namespace std;
class newString {
private:
typedef std::valarray<double> valArray; // 聚合 valarray 物件
string name;
valArray score;
ostream& showScore(ostream& o) const;
public:
newString() : name("Null Name"), score() {}
newString(const newString &nStr); // 複製建構函數
newString(const string &str, int count) : name(str), score(count) {}
newString(const string &str, valArray &va) : name(str), score(va) {}
newString(const char* c, const double *val, int count) :
name(c), score(val, count) {}
// 規定顯示建構函數 (避免指定建構函數,也就是單一參數匹配)
explicit newString(const string& str) : name(str), score() {}
explicit newString(int count) : name("Null Name"), score(count) {}
virtual ~newString();
double average() const; // 平均值
const string& getName() const { // inline
return this->name;
}
double &operator[] (int index);
double operator[] (int index) const;
friend ostream& operator << (ostream& o, const newString& nstr);
};
```
* 使用 `explicit` 關鍵字 [**關閉類別自動轉換**](https://hackmd.io/Roh5BF4MRN2YPgmZZ7c3Sw?both#explicit-顯式型態),**限制部分操作,比起編譯期間出錯要好**
* 使用內嵌函數作為建構子,**並使用初始化列表**,初始化內部成員,初始化列表在**公開繼承也可以使用,==並且初始化列表 ++只作用在建構函數++==**
> 一般函數不可用初始化列表
:::warning
- 元素建構順序 ?
初始化列表建構順序,**順序是按照 ++宣告變數的順序++** 而建構的,不會依照初始化列表的順序
```java=
string name; // 第一建構
valArray score; // 第二建構
```
:::
2. 定義類函數實現
```cpp=
#include "newString.h"
newString::newString(const newString &nStr) {
this->name = nStr.name;
this->score = nStr.score;
}
double newString::average() const {
int size = score.size();
if(size <= 0) {
return 0;
}
return score.sum() / size;
}
double& newString::operator[] (int index) {
// 返回可操作值
return score[index];
}
double newString::operator[] (int index) const {
// read-only
return score[index];
}
ostream& newString::showScore(ostream& o) const {
int size = score.size();
if(size <= 0) {
o << "Empty score list.";
} else {
for(int i = 0; i < size; i++) {
o << "score[" << i << "]= " << score[i] <<endl;
}
}
return o;
}
ostream& operator << (ostream& o, const newString& nStr) {
nStr.showScore(o) << "Name : " << nStr.name << endl;
return o;
}
newString::~newString() {
cout << "new String deconstrucror: " << this->name << endl;
}
```
* 透過 **覆寫 `operator[]` 並++返回 array 原型的 reference++**,就可以直接修改 array 內部資料,以這個例子來說就是呼叫 valArray 的成員函數 valArray<double\>::operator[](),在返回 reference double
* 返回的若為 double 類型,代表它並沒有辦法改變 array,just can read-only
3. 測試使用
```cpp=
int main() {
const static int count = 4;
const static int val[4] = {90, 61, 72, 33};
newString s[count] = {
// 第二個參數是指定內部 array 大小
newString(string("Alien"), 4),
newString(string("Pan"), 2),
newString(string("Kyle"), 3),
newString(string("Hello"), 4),
};
for(int j = 0; j < count; j++) {
newString &cache = s[j];
for(int i = 0; i < count; i++) {
cache[i] = val[i];
}
}
for(int i = 0; i < count; i++) {
cout << s[i];
cout << "Average: " << s[i].average() << "\n" << endl;
}
}
```
> 
### 私有繼承類 (隱式)
* 私有繼承會將基類的所有成員都繼承 (包括私有),但是當**使用此類時,並無法使用到基類的成員**,當沒有指定為 `public` 繼承時,**++==預設為 private 繼承==++**
:::info
* **使用 private 繼承的特色在 **==只獲取實作,不獲取界面==**;class 後使用 `:` 繼承類**
:::warning
C++ 支援多繼承,符號 `:`,後續還有要初始化就使用 `,` 串接
:::
1. **初始化列表**使用 **++類別名稱++**
> 符號 `:`,後續還有要初始化就使用 `,` 串接
2. Has-a **++沒有++ 物件名稱**
3. 使用 **++範疇運算子++訪問匿名成員方法**
4. 新類別並不需要私有區段
:::
1. 宣告抽象類,並繼承 (可多繼承) 需要的類別,這些繼承的類別稱為 **隱式成員**
```cpp=
/**
* 宣告
*/
#include <iostream>
#include <string>
#include <valarray> // 坑... 沒有 include 無法使用
using namespace std;
class PrivateScore : private string, private valarray<double> {
private:
typedef std::valarray<double> valArray;
public:
PrivateScore() : string("NULL"), valarray() {};
PrivateScore(const PrivateScore & u);
PrivateScore(const string &str, int i) : string(str), valArray(i) {};
PrivateScore(const string &str, valArray &a) : string(str), valarray(a) {};
PrivateScore(const char *str, const double *val, int count) :
string(str), valArray(val, count) {};
explicit PrivateScore(const string& str) : string(str), valarray() {};
explicit PrivateScore(int i) : string("NULL"), valArray(i) {};
virtual ~PrivateScore();
double average() const;
const string& getName() const {
return (const string&) *this;
};
ostream& showScore(ostream& o) const;
// operator
double& operator[](int index);
double operator[](int index) const; // read-only
// friend
friend std::ostream& operator<<(std::ostream& o, const PrivateScore& s);
};
```
* 初始化串列 **==使用 class 類名來初始化==**
2. 實現類方法
```cpp=
#include "PrivateScore.h"
double PrivateScore::average() const {
int size = this->valarray::size();
if(size <= 0) {
return 0;
}
return this->valarray::sum() / size;
}
double& PrivateScore::operator[](int index) {
// 必須使用範疇運算子
return this->valarray::operator [](index);
}
double PrivateScore::operator[](int index) const {
// 必須使用範疇運算子
return this->valarray::operator[](index);
}
ostream& PrivateScore::showScore(ostream& o) const {
int size = this->valarray::size();
if(size <= 0) {
o << "Empty score list.";
} else {
for(int i = 0; i < size; i++) {
o << "score[" << i << "]= " << this->valarray::operator[](i) << endl;
}
}
return o;
}
ostream& operator << (ostream& o, const PrivateScore& nStr) {
// 將累型態轉換再使用
nStr.showScore(o) << "Name : " << (const string&) nStr << endl;
return o;
}
PrivateScore::~PrivateScore() {
// 將累型態轉換再使用
cout << "PrivateScore deconstructor." << (string &) *this << endl;
}
```
* 由於是私有繼承,class 成員會被隱藏,**匿名成員物件要使用時要透過 ++型態轉換++,顯示指定要轉型的類**
> 成員函數可以使用範疇運算子,但是 **friend 函式並非成員函數,所以必須透過 ++型態轉換++**
* 對於 **私有繼承成員要使用其函數必須透過 ++範疇運算子++ 呼叫**
3. 測試
```cpp=
int main() {
typedef std::string string;
cout << "Hello World" << endl;
const static int count_1 = 4;
const static int val_2[4] = {90, 61, 72, 33};
PrivateScore s[count_1] = {
PrivateScore(string("Alien"), 4),
PrivateScore(string("Pan"), 2),
PrivateScore(string("Kyle"), 3),
PrivateScore(string("Hello"), 4),
};
for(int j = 0; j < count_1; j++) {
PrivateScore &cache = s[j];
for(int i = 0; i < count_1; i++) {
cache[i] = val_2[i];
}
}
for(int i = 0; i < count_1; i++) {
cout << s[i];
cout << "Average: " << s[i].average() << "\n" << endl;
}
return 0;
}
```
> 
### 私有成員 & 私有繼承
* 上數個兩個方法,大部分會選用 `私有成員`,主要原因有
:::warning
1. 因為私有繼承太過於抽象,不易理解,總是要轉型
2. **繼承限制++單一物件++** (無法繼承兩個 valarray),無法同時宣告多個同樣物件來使用,而私有成員就可以
:::
* **`私有繼承`仍有 `私有成員` 所沒有的特質**
:::info
1. **可以存取 protected 成員**,protected 允許衍生類在內
2. **私有函數可已重新定義 [++虛擬函數++](https://hackmd.io/oM3IAtEVSCSn5Qh_ve6nOw#同名異式的公用繼承)**,而私有成員不行重新定義虛擬函數
:::
## 保護繼承
**保護類在第三代的繼承仍可使用 ++第一代++ 的 private 成員**,**但在 ==protected 可以在之後繼承都可使用 ++第一代++ 的 private 成員==**
```cpp=
class ProtectedScore : protected std::string,
protected std::valarray<double> {
...
}
```
### Using 重新定義存取方式
* 使用保護繼承,可以使用 `private`、`protected` 保護成員,當需要使用到基類的函數時 **通常是使用定義一個 public function 讓使用者使用**,但還有另外一個方法,**using 宣告**
* 用 [**using 宣告**](https://hackmd.io/uf-tHfltRVCV45D1h6ZiGA#using-宣告-amp-using-指令) 可讓衍生類的使用者直接使用到基類函數,**必須將 using 宣告定義在 public 區塊**
:::danger
* **==using 宣告的方法 ++只能用於繼承++==,++不能用於私有成員++**
:::
:::info
* **using 宣告指++使用成員名稱++,沒有括號、函數簽名**
:::
1. **抽象宣告**:同時在這邊對外 **使用 using 宣告要公開的函數**
```cpp=
#include <iostream>
#include <string>
#include <valarray>
using namespace std;
class ProtectedScore : protected string,
protected valarray<double> {
private:
typedef std::valarray<double> valarray;
protected:
using valarray::max;
public:
using valarray::min;
ProtectedScore(const string &str, int count) : string(str), valarray(count) {};
ProtectedScore();
virtual ~ProtectedScore();
double average() const;
ostream& showScore(ostream& o) const;
// operator
double& operator[](int index);
double operator[](int index) const; // read-only
// friend
friend std::ostream& operator<<(std::ostream& o, const ProtectedScore& s);
};
```
* using 宣告 **放置在 class public 區塊**,宣告時只包含函式的名稱 (沒有括弧、引數...等等)
* **私有、保護繼承透過 using 宣告也可以使用基類的函式**
2. Class 定義:這部分跟之前差不多,但是在內部呼叫了 protect 的 max 方法
```cpp=
#include "ProtectedScore.h"
double ProtectedScore::average() const {
int size = this->valarray::size();
if(size <= 0) {
return 0;
}
return this->valarray::sum() / size;
}
double& ProtectedScore::operator[](int index) {
return this->valarray::operator [](index);
}
double ProtectedScore::operator[](int index) const {
return this->valarray::operator[](index);
}
ostream& ProtectedScore::showScore(ostream& o) const {
int size = this->valarray::size();
if(size <= 0) {
o << "Empty score list.";
} else {
for(int i = 0; i < size; i++) {
o << "score[" << i << "]= " << this->valarray::operator[](i) << endl;
}
}
return o;
}
ostream& operator << (ostream& o, const ProtectedScore& nStr) {
nStr.showScore(o) << "Name : "
<< (const string&) nStr << endl;
// 呼叫 protected 的 max 方法
cout << "Max:" << nStr.max() << endl;
return o;
}
ProtectedScore::~ProtectedScore() {
cout << "ProtectedScore deconstructor." << (string &) *this << endl;
}
```
3. **測試**:外部可以使用 using 宣告的函數 (min)
```cpp=
int main() {
typedef std::string string;
cout << "Hello World" << endl;
const static int count_1 = 4;
const static int val_2[4] = {90, 61, 72, 33};
ProtectedScore s[count_1] = {
ProtectedScore(string("Alien"), 4),
ProtectedScore(string("Pan"), 2),
ProtectedScore(string("Kyle"), 3),
ProtectedScore(string("Hello"), 4),
};
for(int j = 0; j < count_1; j++) {
ProtectedScore &cache = s[j];
for(int i = 0; i < count_1; i++) {
cache[i] = val_2[i];
}
}
for(int i = 0; i < count_1; i++) {
cout << s[i];
cout << "Average: " << s[i].average() << endl;
cout << "Min:" << s[i].min() << endl << "\n";
}
return 0;
}
```
> 
### 繼承的整理
| 成員 | 公用 public 繼承 | 保護 protected 繼承 | 私有 private 繼承 |
| -------- | -------- | -------- | -------- |
| 公用成員 | 衍生類可用 | 衍生類別的保護成員 | 衍生類的私有 |
| 保護成員 | 衍生類可用 | 衍生類別的保護成員 | 衍生類的私有 |
| 私有成員 | 只能透過 Function 訪問 | 只能透過 Function 訪問 | 只能透過 Function 訪問 |
| 隱式向上轉型 | OK | OK (限於衍生類中) | NO |
## 多重繼承 (multiple inheritance, MI)
**==多重繼承表示 is-a 的關係==,is-a 關係是 public 公開繼承關係,has-a 是 private、protected 關係 (因為外部不可以使用繼承的基類)**
> 
:::warning
* 問題
上圖會產生的問題是,**繼承兩個衍生類別會繼承到兩個相同名稱的函數**
> 可能 `Singer` 有 name,而 `Waiter` 也有 name 函數,那 `SingWaiter` 呼叫 name 時就會衝突
:::
### 基礎繼承
* 先創建一個基本繼承,再來看看多重繼承有相同函數的問題、解決方法
1. **基礎基類**
```cpp=
/**
* 宣告
*/
#include <string>
using std::string;
#include <iostream>
using std::cout;
using std::endl;
class Worker {
private:
string name;
long id;
public:
Worker(long n = 0, string s = "NULL") : name(s), id(n) {};
Worker(const string & s, long n) : name(s), id(n) {};
// 純虛寒數
virtual ~Worker() = 0;
// 虛擬函數
virtual void Set();
virtual void Show() const;
};
// -----------------------------------------------------------------------
using std::cin;
// Worker class
Worker::~Worker() {} // must implements, even if pure !
void Worker::Set() {
cout << "Enter Worker's name: ";
getline(cin, name);
cout << "Enter Worker's id: ";
cin >> id;
while(cin.get() != '\n') continue; // 換行才結束
}
void Worker::Show() const {
cout << "name: " << name << endl;
cout << "id: " << id << endl;
}
```
2. **創建兩個衍生類** (Waiter、Singer)
```cpp=
class Waiter : public Worker { // public 公用繼承
private:
int age;
public:
Waiter() : Worker(), age(18) {}
Waiter(const Worker & w, int g = 18) : Worker(w), age(g) {}
Waiter(const string& s, long n, int g = 18) : Worker(s, n), age(g) {}
// 實現虛擬函數
void Set();
void Show() const;
};
class Singer : public Worker { // public 公用繼承
protected:
enum {
other, alto, contralto, soprano, bass, baritone, tenor, total
};
private:
static char* pv[total];
int voice;
public:
Singer() : Worker(), voice(other){}
Singer(const Worker & w, int v = other) : Worker(w), voice(other){ }
Singer(const string& s, long n, int v = other) : Worker(s, n), voice(other){ }
// 實現虛擬函數
void Set();
void Show() const;
};
// --------------------------------------------------------------------------
// Waiter class
void Waiter::Set() {
// 使用範疇運算子指定
Worker::Set();
cout << "Enter Waiter age: ";
cin >> age;
while(cin.get() != '\n') continue;
}
void Waiter::Show() const {
// 使用範疇運算子指定
Worker::Show();
cout << "age: " << age << endl;
}
// Singer class
char * Singer::pv[total] = {"other", "alto", "contralto", "soprano", "bass", "baritone", "tenor"};
void Singer::Set() {
// 使用範疇運算子指定
Worker::Set();
cout << "Enter number for singer's vocal range:" << endl;
for(int i = 0; i < total; i++) {
cout << i << ": " << pv[i] << " "; // 顯示所有選項
}
cout << endl;
cin >> voice;
while(cin.get() != '\n') continue;
}
void Singer::Show() const {
// 使用範疇運算子指定
Worker::Show();
cout << "voice: " << pv[voice] << endl;
}
```
* 上面清楚定義類型 (使用 `::` 範疇運算子),所以再呼叫 Function 時不會有模糊性,但是**如果定義一個多重繼承的 WaiterSinger,就會導致 ++WaiterSinger 內部有多個定義++**,帶這個問題往下看多重繼承
3. 測試:
```cpp=
/**
* 使用
*/
#include "Worker.h"
int main() {
static const int max = 4;
Waiter bob("Bob", 314L, 23); // name, id, age;
Singer alien("Alien", 523L, 6);
Waiter w_temp; // 坑...不能有括弧 w_temp()
Singer s_temp;
Worker * ws[max] = { &bob, &alien, &w_temp, &s_temp };
for(int i = 2; i < max; i++) {
ws[i]->Set(); // 設定後面兩個
}
for(int i = 0; i < max; i++) {
ws[i]->Show();
cout << endl;
}
return 0;
}
```
**--實作--**
> 
### 多重繼承 - 問題
* 在建立 `WaiterSinger` 時,其 **真正的問題是為有 ++兩份 Worker 物件++**,因為 `WaiterSinger` constructor 會呼叫兩個基類 `Waiter` & `Singer`,這時這 **兩個基類又會建立個別的 Worker**
這時再呼叫 `WaiterSinger` function (Show、Set) 就會造成模糊問題
> 
### 虛擬基類 virtual - 解決方案
* **虛擬基類可以使此物件 ++只繼承++ ==共同基礎類別的++一個物件++== (以上面來說就是 `WaiterSinger` 只會有一個 `Worker` 物件)**
* 在 `Singer` & `Waiter` **繼承時使用關鍵字 virtual**,放置不分前後,都是相同效果
```cpp=
// 虛擬碼
// 以下兩種寫法都可以
class Singer : virtual public Worker {}
class Waiter : public virtual Worker {}
// 多重繼承類
class SingerWaiter : public Singer, public Waiter {}
```
> 
* **在使用抽象衍生類時 (`Singer`、`Waiter`) 要特別注意==建構函數==,中間類並不會呼叫基類建構函數;就像在 `Waiter` constructor 並不會調用 `Worker` constructor,==必須明確指定基類的建構函數==**
```cpp=
SingerWaiter::SingerWaiter(const Worker& w, int a = 18, int v = Singer::other)
: Waiter(w,a), Singer(w,v) { } // 並不會傳達至基類 Worker
SingerWaiter::SingerWaiter (const Worker& w, int a = 18, int v = Singer::other)
: Work(w), // 明確指定,會傳至基類
Waiter(w,a), Singer(w,v) { }
```
* 接下來要考慮 **要使用哪一個函數**,因為如果**使用 *繼承呼叫* 時就會呼叫到兩次基類 `Worker`,使用 ==模組化== 就可以避免這個問題 (++細分資料內容++),並將該資料放置在 `protected` 保護,只讓內部類訪問**
* 將上點的範例做修改,主要修改的地方有
1. **基類模組化,把需要函數放置 `protected` 區塊**
```cpp=
class Worker {
private:
string name;
long id;
protected:
// 模組定義在 protected 區塊,限制只給內部使用
virtual void Data() const;
virtual void Get();
public:
Worker(long n = 0, string s = "NULL") : name(s), id(n) {};
Worker(const string & s, long n) : name(s), id(n) {};
virtual ~Worker() = 0;
// 轉變為純虛擬函數 (子類必須實現)
virtual void Set() = 0;
virtual void Show() const = 0;
};
```
2. **虛擬化繼承共通的類 virtual public**:它的特色是如果有使用到多繼承,其內部就會只有一個共同基類 (以目前來說就是 `Worker`)
```cpp=
class Waiter : virtual public Worker { // 虛擬類 - 繼承
private:
int age;
protected:
void Data() const;
void Get();
public:
Waiter() : Worker(), age(18) {}
Waiter(const Worker & w, int g = 18) : Worker(w), age(g) {}
Waiter(const string& s, long n, int g = 18) : Worker(s, n), age(g) {}
void Set();
void Show() const;
};
// ----------------------------------------------------------------------
class Singer : public virtual Worker { // 虛擬類 - 繼承
protected:
enum {
other, alto, contralto, soprano, bass, baritone, tenor, total
};
void Data() const;
void Get();
private:
static char* pv[total];
int voice;
public:
Singer() : Worker(), voice(other){}
Singer(const Worker & w, int v = other) : Worker(w), voice(other){}
Singer(const string& s, long n, int v = other) : Worker(s, n), voice(other){}
void Set();
void Show() const;
};
```
3. 在多重繼承類的 constructor **顯式指定基類 constructor**
```cpp=
// 多重繼承虛擬類 (virtual class)
class SingerWaiter : public Singer, public Waiter {
protected:
void Data() const;
void Get();
public:
SingerWaiter() : Worker(), // 顯式指定
Waiter(), Singer() {}
SingerWaiter(const string& s, long n, int a = 0, int v = Singer::other)
: Worker(s, n), // 顯式指定
Waiter(s, a), Singer(s, n, v) {}
SingerWaiter(const Worker& w, int a = 0, int v = Singer::other)
: Worker(w), // 顯式指定
Waiter(w, a), Singer(w, v) {}
SingerWaiter(const Waiter& wt, int v = Singer::other)
: Worker(wt), Waiter(wt), Singer(wt, v) {}
SingerWaiter(const Singer& s, int a = 18)
: Worker(s), Waiter(s, a), Singer(s) {}
void Set();
void Show() const;
};
```
4. 模組化基礎、衍生類別的資料,才能分開調用
```cpp=
#include "Worker.h"
// Worker class 移除 Show & Set 的定義,改為定義模組
Worker::~Worker() {} // must implements, even if pure !
void Worker::Data() const {
cout << "name: " << name << endl;
cout << "id: " << id << endl;
}
void Worker::Get() {
cout << "Enter Worker's name: ";
getline(cin, name);
cout << "Enter Worker's id: ";
cin >> id;
while(cin.get() != '\n') continue; // 換行才結束
}
// -------------------------------------------------------------------------
// Waiter class
void Waiter::Data() const {
cout << "age: " << age << endl;
}
void Waiter::Get() {
cout << "Enter Waiter age: ";
cin >> age;
while(cin.get() != '\n') continue;
}
void Waiter::Set() {
// 先呼叫基類 Get
Worker::Get();
// 再呼叫自身的 Get
Get();
}
void Waiter::Show() const {
// 先呼叫基類 Show
Worker::Data();
// 再呼叫自身的 Show
Data();
}
// -------------------------------------------------------------------------
// Singer class
char * Singer::pv[total] = {"other", "alto", "contralto", "soprano", "bass", "baritone", "tenor"};
void Singer::Data() const {
cout << "voice: " << pv[voice] << endl;
}
void Singer::Get() {
cout << "Enter number for singer's vocal range:" << endl;
for(int i = 0; i < total; i++) {
cout << i << ": " << pv[i] << " "; // 顯示所有選項
}
cout << endl;
cin >> voice;
while(cin.get() != '\n') continue;
}
void Singer::Set() {
Worker::Get();
Get();
}
void Singer::Show() const {
Worker::Data();
Data();
}
// SingerWaiter class
void SingerWaiter::Data() const { // 模組化
Singer::Data();
Waiter::Data();
}
void SingerWaiter::Show() const {
Worker::Data(); // 只呼叫了一次基類
Data();
}
void SingerWaiter::Get() { // 模組化
Singer::Get();
Waiter::Get();
}
void SingerWaiter::Set() {
Worker::Get(); // 只呼叫了一次基類
Get();
}
```
5. 使用
```cpp=
/**
* 使用
*/
#include "Worker.h"
#include <cstring>
int main() {
static const int max = 5;
Worker *ws[max];
int i = 0;
for(i = 0; i < max; i++) {
char choice;
cout << "Enter the employee category: \n"
<< "w: waiter, s: singer, t: singer_waiter, q: quit"
<< endl;
cin >> choice;
while(strchr("wstq", choice) == NULL) { // 比對字串
cout << "Enter: " << endl;
cin >> choice;
}
if(choice == 'q') {
cout << "Quit\n";
break;
}
switch(choice) { // 動態,新增物件
case 'w':
cout << "Add one waiter\n";
ws[i] = new Waiter;
break;
case 's':
cout << "Add one singer\n";
ws[i] = new Singer;
break;
case 't':
cout << "Add one singer_waiter\n";
ws[i] = new SingerWaiter;
break;
}
cin.get();
ws[i]->Set(); // 設定物件
}
cout << "Here is your employ info:\n";
for(int j = 0; j < i; j++) {
cout << endl;
ws[j]->Show();
}
for(int j = 0; j < i; j++) {
delete ws[j]; // 刪除動態區 heap 記憶體
}
return 0;
}
```
* 上面使用動態創建,由於不知道使用者要選擇的類,所以使用動態創建類 (存放在 heap 區塊),所以必須記得刪除
**--實作結果--**
> 
## C++ 模板
**模板撿來說就是,==參數化型態==**,其關鍵字就是 **`template<class Type>`,Type 的位子可以任意的通用型態名稱**
:::info
template 中的 class 是較舊的用法,可能會導致他人誤以為 class T 為一個類別,但其實並不是
**新的用法是使用 `typename`**
> template<++typename++ Type\>
:::
### 定義 class 樣板
* 將 template 定義在 class 之上,**模板 class 並不算一個類,它只告訴編譯器要++如何生成類別++、++成員函數定義++,實現樣板稱為實體化 (instantiation)、持定化 (specialization)**
* **==樣板必須與 [特定樣板](https://hackmd.io/7Csy-1LVTouQFl2e2sBvtA#%E7%89%B9%E5%AE%9A%E5%8C%96%E6%A8%A1%E6%9D%BF---explicit-%E9%A1%AF%E7%A4%BA) 實體化的程式碼 ++放在一起++== 否則不能運作**
:::warning
**除非使用特定的 export (但 C++11 已停止使用) 否則 ++模板必須與實作放置++ 在一起,最好的方式就是一同放置標頭檔 (`.h` 檔)**
:::
1. 模板 (泛型) 化 Class 類
```cpp=
template<typename T>
class TemplateStack {
private:
enum {MAX = 10};
int index;
T List[MAX]; // 模板類 T
public:
TemplateStack() : index(0) { }
bool isEmpty() const;
bool isFull() const;
// 模板類 T
bool push(T t);
bool pop(T& t);
virtual ~TemplateStack() { }
};
```
2. 實作 Class 的模板函數
```cpp=
#include <iostream>
using std::cout;
using std::endl;
template<typename T>
bool TemplateStack<T>::isEmpty() const {
return index == 0;
}
template<typename T>
bool TemplateStack<T>::isFull() const {
return index == MAX;
}
template<typename T>
bool TemplateStack<T>::push(T t) {
if(isFull()) {
cout << "Stack full, cannot push" << endl;
return false;
} else {
List[index++] = t;
return true;
}
}
// template<class & typename> 相同都可以使用
template<class T>
bool TemplateStack<T>::pop(T &t) {
if(isEmpty()) {
cout << "Stack Empty, cannot pop" << endl;
return false;
} else {
t = List[--index];
return true;
}
}
```
3. 使用模板類
```cpp=
using std::string;
using std::cin;
int main() {
TemplateStack<int> stack1;
int i = 10;
stack1.push(i);
int res = -1;
stack1.pop(res);
cout << "res: " << res << endl;
string str;
char ch;
TemplateStack<string> stack2;
while(cin >> ch && ch != 'Q') {
switch(ch) {
case 'A':
cout << "type input : ";
cin >> str;
if(stack2.isFull()) {
goto FIN;
} else {
stack2.push(str);
}
break;
case 'P':
if(stack2.isEmpty()) {
goto FIN;
} else {
stack2.pop(str);
cout << "pop: " << str << endl;
}
break;
}
}
FIN:
while(!stack2.isEmpty()) {
string str;
stack2.pop(str);
cout << "str: " << str << endl;
}
cout << "Bye" << endl;
return 0;
}
```
* 實體化的方式是宣告樣板型態的物件,且**將通用名稱替換成需要的型態**
> 函數 : 編譯程式會用傳入函數的引數型態,決定要產生何種型態的函數
>
> 
### 樣板 - 指標堆疊、動態創建 new
* 如果說上一個範例不使用 `string` 類,而是使用 `char *` 指標呢 ? 也是 **可以產生指標堆疊,但是要 ++注意指標位置是否是相同++**
:::warning
1. 將 `string` 換成 `char*` 這方法會立即失敗 !
> 因為使用 cin 輸入必須要一個實例化的物件
2. 將 `string` 換成 `char *str[40];` 在 pop function 時會錯誤
> `pop` 接收的參數為 reference 必須參考某些型態的左值,而非陣列名稱,並且在 reference 賦值時不能賦予陣列單個數值
3. 將 `string` 為 `char *str = new char str[40];` 這就符合 pop 的標準,它是一個指標,可間接改值,但還是不行
> 因為每次 pop 時都會丟入同一個地址,地址並沒有更新,會導致 pop 最後一個存入的字串
:::
* 以下使用 **new 關鍵字** 去創建 Array,這種動態創建要注意三個點
:::success
* 如果你的類會與記憶體中的「**堆**」有關係,則須注意...
* 解構函數,在解構函數要釋放 **堆** 的記憶體
* **[複製函數](https://hackmd.io/rYFShCgjTHCcKsaNneFfmA?both#淺拷貝-amp-深拷貝),要去拷貝並創建一個 堆 空間**
* **[指定操作符](https://hackmd.io/rYFShCgjTHCcKsaNneFfmA?both#指定運算子---顯式深層拷貝) (`=`),==刪除原先的 堆 空間、創建新的 堆 空建==、並拷貝其資料**
:::
1. 抽象像宣告
```cpp=
#include <iostream>
using std::cout;
using std::endl;
template<typename T>
class DynamicStack {
private:
enum {DEF_SIZE = 5};
T *t;
int size, index;
void copyArray(const DynamicStack &d);
public:
DynamicStack(int s = DEF_SIZE) : size(s), index(0) {
t = new T[size];
}
// 隱式拷貝函數
DynamicStack(const DynamicStack<T> & d);
// 顯示拷貝符號
DynamicStack<T> & operator=(const DynamicStack & d); // DynamicStack 等於 DynamicStack<T>
bool isEmpty() const { return index == 0; }
bool isFull() const { return index == size; }
bool push(T t);
bool pop(T &t);
virtual ~DynamicStack() {
delete[] t;
}
};
```
* **可以在樣板宣告 & 樣板函數中(實體化) 可以使用 ++沒有 DynamicStack<T\>++ 的 DynamicStack 類**
2. 類實現
```cpp=
#include "DynamicStack.h"
template<typename T> // 隱式深層拷貝 copy construct
DynamicStack<T>::DynamicStack(const DynamicStack<T> & d) : index(0) {
this->size = d.size;
this->t = new T[d.size];
copyArray(d);
}
//"2. "
template<typename T> // 顯示深層拷貝 = 指定函數
DynamicStack<T> & DynamicStack<T>::operator=(const DynamicStack<T> & d) {
if(this == &d) {
return *this;
}
delete[] this->t;
this->size = d.size;
this->t = new T[this->size];
copyArray(d);
return *this;
}
template<typename T>
bool DynamicStack<T>::push(T iT) {
if(isFull()) {
return false;
} else {
t[index++] = iT;
return true;
}
}
template<typename T>
bool DynamicStack<T>::pop(T &oT) {
if(isEmpty()) {
return false;
} else {
oT = t[--index];
return true;
}
}
template<typename T> // copy ptr in array
void DynamicStack<T>::copyArray(const DynamicStack &d) {
for(int i = 0; i < size; i++) {
this->t[i] = d.t[i];
}
}
```
* **在類別之外,包括 ==標示回傳型態==、==使用範疇運算子== 時都需要 ++完整的加上模板 DynamicStack<T\>++**
3. 測試
```cpp=
int main() {
static const int NUM = 10;
const char* in[NUM] = { // 要存入元素
"Apple", "Box", "Car", "Dex_File", "Elements",
"Fork", "Game", "Hello", "Illegal", "Joke"
};
DynamicStack<const char*> stack(NUM); // <const char*> != <char*>
const char* out; // 輸出元素
while(!stack.isFull()) {
static int i = 0;
stack.push(in[i++]);
}
while(!stack.isEmpty()) {
static int i = 0;
stack.pop(out);
cout << i++ <<" out: " << out << endl;
}
return 0;
}
```
> 
### 樣板 - 非型態引數
* 通常模板的功能是參數化類別型態,這是因為**型態參數的概念**,而**非型態引數是使用++特定的型態++**,看看以下例子
* 非型態又稱為**運算式引數**,**它有一定的++限制++、++特點++(下面程式第 3 點)**
> 
### 遞迴樣板
* **==樣板中可以再有樣板==**,這樣就如同一個二維陣列,如同 `TpArray<TpArray<double, 5>, 3> twodee;` **與二維陣列不同的是它的 ++順序相反++**
```cpp=
// 普通二維
int normal2Array[5][3]; // 5 個內部各有 3 個元素的陣列
// 模板二維
TpArray<TpArray<double, 5>, 3> twodee; // 3 個內部各有 5 個元素的陣列
```
* 創建一個 5 次分數、記錄 3 次,並**使用 ++遞迴 template++ 創建**
```cpp=
/**
* 使用
*/
#include "templateArray.h"
int main() {
TpArray<double, 5> sum; // 5次 總和
TpArray<double, 5> aves; // 5次 平均
//"1. "
TpArray<TpArray<double, 5>, 3> twodee; // 存放 5 次的成績,總共存 3 次
int i = 0, j = 0;
for(; i < 3; i++) {
sum[i] = 0;
for(j = 0; j < 5; j++) {
twodee[i][j] = (i + 1) * (j + 1);
//"2. "
sum[i] += twodee[i][j];
}
aves[i] = sum[i] / 5;
}
i = j = 0;
for(; i < 3; i++) {
for(j = 0; j < 5; j++) {
cout << twodee[i][j] << " ";
}
cout << " -> Sum: " << sum[i];
cout.width(3); // 小數點三位
cout << " -> Average: " << aves[i] << endl;
}
return 0;
}
void basic() {
TpArray<double, 3> a1;
TpArray<double, 5> a2;
for(int i = 0; i < 3; i++) {
a1[i] = 3.69 + i;
cout << a1[i] << " ";
}
cout << endl;
for(int i = 0; i < 3; i++) {
a2[i] = 1.23 + i;
cout << a2[i] << " ";
}
}
```
1. **創建遞迴樣板相當於一個夠多維度的陣列**,以這個範例來說就是**使用遞迴模板創建了 2 維陣列**
2. **==可直接使用陣列的方式賦予值==** (創建時順序式相反的,如果使用陣列來接收應該是 int twodee[3][5])
**--實做--**
> 
### 多個類型參數
* **如同 [Java 泛型](https://hackmd.io/Dlm0Ov0URya2t6mbTgNQkQ#泛型類、接口、方法),可以有多個參數化類型**,`tmplate<class T1, class T2, Class T3\>`,STL (stander Template Library) 內部也有一個 pair 類似於 Java 的 Map
```cpp=
#include <iostream>
using std::cout;
using std::endl;
//"1. "
template<typename T1, class T2>
class HandlerPair{
private:
T1 key;
T2 value;
public:
HandlerPair(const T1 & t1, const T2 & t2) : key(t1), value(t2) {}
T1 & getKey() { return key; }
T2 & getValue();
};
//"2. "
template<class T1, typename T2>
T2 & HandlerPair<T1, T2>::getValue() {
return value;
}
int main() {
using std::string;
HandlerPair<string, double> infos[] =
{
HandlerPair<string, double>("Alien", 84.3),
HandlerPair<string, double>("Kyle", 90.3),
HandlerPair<string, double>("Pan", 88.8),
};
//"3. "
int size = sizeof(infos) / sizeof(HandlerPair<string, double>);
for(int i = 0; i < size; i ++) {
cout << "info -> Key: " << infos[i].getKey() <<
", Value: " << infos[i].getValue() << endl;
}
}
```
1. **typename & class 是可以互相切換互補的**
2. 在類 (class) 以外不可省略 `template<T1, T2>`,必須要詳細寫出
3. sizeof(Array) 可以計算出該陣列所佔用的位元數 (Byte 單位),**sizeof 計算單個模板的時候也需要寫出模板 `<>`**
**--實作--**
> 
### 預設型態樣板
* 如同建構函數中的預設參數,`template<class T1, class T2 = int> `使用等於符號指定其預設值
```cpp=
#include <iostream>
using std::cout;
using std::endl;
//"1. "
template<typename T1, class T2 = double>
class HandlerPair{
private:
T1 key;
T2 value;
public:
HandlerPair(const T1 & t1, const T2 & t2) : key(t1), value(t2) {}
T1 & getKey() { return key; }
T2 & getValue();
};
template<class T1, typename T2>
T2 & HandlerPair<T1, T2>::getValue() {
return value;
}
int main() {
using std::string;
HandlerPair<string, double> infos[] =
{
//"2. "
HandlerPair<string>("Alien", 84.3),
HandlerPair<string>("Kyle", 90.3),
HandlerPair<string>("Pan", 88.8),
};
int size = sizeof(infos) / sizeof(HandlerPair<string, double>);
for(int i = 0; i < size; i ++) {
cout << "info -> Key: " << infos[i].getKey() <<
", Value: " << infos[i].getValue() << endl;
}
}
```
1. 模板預設參數型態
2. 在使用時就同預設建構參數,**如果有預設型態,可以不用指定有預設的型態**
**--實作--**
> 
## 模板特定化
| 處理方式 | 解釋 | 範例 |
| -------- | -------- | -------- |
| 隱式實體化 (`implicit instantiation`) | 宣告物件表示目的型態,**編譯程式會使用通用樣板提供** | TpArray<double, 5> sum; |
| 顯式實體化 (`explicit instantiation`) | 使用關鍵子 template 宣告類別,並指定目的型態 | **template class** TpArray<double, 5>; |
| **顯式特定化 (`implicit specialization`)** | 樣板以**特定型態**描述類別 | |
### 隱式實體化
* **隱式實體化**:宣告指標,再使用 new 關鍵字定義;之前的範例皆是使用隱式實體化
```cpp=
// 使用通用模板創造
TpArray<double, 5> sum;
// 指標宣告,無實體化
TpArray<double, 5> *ptrSum;
ptrSum = new TpArray<double, 5>; // 這時才產生實體化,並存放在 heap 記憶體區塊
```
### 顯式實體化
* 使用關鍵子 template 宣告類別,並指定目的型態時,編譯程式會產生此類別宣告的實體化,**該宣告應與此類別在同樣的名稱空間中**
```cpp=
template<typename T1, class T2 = double>
class HandlerPair{
private:
T1 key;
T2 value;
public:
HandlerPair(const T1 & t1, const T2 & t2) : key(t1), value(t2) {}
T1 & getKey() { return key; }
T2 & getValue() { return value; };
void f(){}
};
// 顯示實體化
template class HandlerPair<string, double>; // 產生 HandlerPair 的類
// 以下不可 !!! 同時與 類實體化 一起使用
template void HandlerPair<string, double>::f();
template string& HandlerPair<string, double>::getKey();
template float& HandlerPair<string, double>::getValue();
```
* 宣告在全域,**可以指定實例化類別**,可實例化方法、類,**==如果已經顯式實體化類就不能實體化方法==,因為會 ++導致方法重複定義++**
> 
### 顯示特定化
* 簡稱為 **特定化,一般來說定義只有一個**,但特定化可以針對不同型態
* **顯示特定化是針對特定型態的定義,此定義會用來 ++取代通用樣板++**,就像是在比較大小的樣板,用在數字上只要複寫 `T::operator>()` 就可以,但是如果使用在 `char` 比較就要使用 `strcmp()` 取代 `>`
```cpp=
// 特定化 - 舊格式
class ClassName<特定格式> {
};
// 特定化 - 新格式
template <> class ClassName<特定格式> {
};
```
* 特定化類別使用
```cpp=
#include <iostream>
using std::cout;
using std::endl;
using std::string;
template<typename T1, class T2>
class HandlerPair {
private:
T1 key;
T2 value;
public:
HandlerPair(const T1 & t1, const T2 & t2) : key(t1), value(t2) {}
T1 & getKey() { return key; }
T2 & getValue() { return value; };
void f();
};
template<typename T1, class T2>
void HandlerPair<T1, T2>::f() {
cout << "General Function" << endl;
}
// ------------------------------------------------------------------
// 特定化
template <>
class HandlerPair<string, const char*> {
public:
void f();
};
// 特定化實現
void HandlerPair<string, const char*>::f() {
cout << "Specialization Function" << endl;
}
// ------------------------------------------------------------------
int main() {
HandlerPair<string, double> genernal("Alien", 12);
genernal.f();
HandlerPair<string, const char*> special;
special.f();
}
```
**--實作--**
> 
### 部分特定化
* C++ 也接受 **部分特定化 (partial specialization),這部分的限制了樣板的通用性**
```cpp=
// general
template <class T1, class T2>
class Pair {
...
};
// special -1
template <class T1>
class Pair<T1, int> { // 指定 pair 第二個型態為 int
...
};
// special -2
template <class T1>
class Pair<float, int> { // 指定 pair 第一個型態為 float、第二個型態為 int
...
};
int main() {
Pair<double, float> Pair; // 通用模板
Pair<double, int> Pair_s1; // special -1
Pair<float, int> Pair_s2; // special -2
return 0;
}
```
## Appendix & FAQ
:::info
1. 在實作虛擬類繼承時,多重繼承的類呼叫基類建構函數有警告...好像是 **++順序錯誤++** ???
> 
:::
###### tags: `C++`