kyle shanks
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Make a copy
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Make a copy Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    1
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    --- 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; } } ``` > ![](https://i.imgur.com/wYLgahe.png) ### 私有繼承類 (隱式) * 私有繼承會將基類的所有成員都繼承 (包括私有),但是當**使用此類時,並無法使用到基類的成員**,當沒有指定為 `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; } ``` > ![](https://i.imgur.com/MSApIv9.png) ### 私有成員 & 私有繼承 * 上數個兩個方法,大部分會選用 `私有成員`,主要原因有 :::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; } ``` > ![](https://i.imgur.com/c8AYjWD.png) ### 繼承的整理 | 成員 | 公用 public 繼承 | 保護 protected 繼承 | 私有 private 繼承 | | -------- | -------- | -------- | -------- | | 公用成員 | 衍生類可用 | 衍生類別的保護成員 | 衍生類的私有 | | 保護成員 | 衍生類可用 | 衍生類別的保護成員 | 衍生類的私有 | | 私有成員 | 只能透過 Function 訪問 | 只能透過 Function 訪問 | 只能透過 Function 訪問 | | 隱式向上轉型 | OK | OK (限於衍生類中) | NO | ## 多重繼承 (multiple inheritance, MI) **==多重繼承表示 is-a 的關係==,is-a 關係是 public 公開繼承關係,has-a 是 private、protected 關係 (因為外部不可以使用繼承的基類)** > ![](https://i.imgur.com/AJ3AKhZ.png) :::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; } ``` **--實作--** > ![](https://i.imgur.com/cViKsga.png) ### 多重繼承 - 問題 * 在建立 `WaiterSinger` 時,其 **真正的問題是為有 ++兩份 Worker 物件++**,因為 `WaiterSinger` constructor 會呼叫兩個基類 `Waiter` & `Singer`,這時這 **兩個基類又會建立個別的 Worker** 這時再呼叫 `WaiterSinger` function (Show、Set) 就會造成模糊問題 > ![](https://i.imgur.com/XURl4gc.png) ### 虛擬基類 virtual - 解決方案 * **虛擬基類可以使此物件 ++只繼承++ ==共同基礎類別的++一個物件++== (以上面來說就是 `WaiterSinger` 只會有一個 `Worker` 物件)** * 在 `Singer` & `Waiter` **繼承時使用關鍵字 virtual**,放置不分前後,都是相同效果 ```cpp= // 虛擬碼 // 以下兩種寫法都可以 class Singer : virtual public Worker {} class Waiter : public virtual Worker {} // 多重繼承類 class SingerWaiter : public Singer, public Waiter {} ``` > ![](https://i.imgur.com/WZC1c9d.png) * **在使用抽象衍生類時 (`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 區塊),所以必須記得刪除 **--實作結果--** > ![](https://i.imgur.com/BHnA8Mw.png) ## 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; } ``` * 實體化的方式是宣告樣板型態的物件,且**將通用名稱替換成需要的型態** > 函數 : 編譯程式會用傳入函數的引數型態,決定要產生何種型態的函數 > > ![](https://i.imgur.com/IES47u3.png) ### 樣板 - 指標堆疊、動態創建 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; } ``` > ![](https://i.imgur.com/KmcHN1Y.png) ### 樣板 - 非型態引數 * 通常模板的功能是參數化類別型態,這是因為**型態參數的概念**,而**非型態引數是使用++特定的型態++**,看看以下例子 * 非型態又稱為**運算式引數**,**它有一定的++限制++、++特點++(下面程式第 3 點)** > ![](https://i.imgur.com/tAnEYET.png) ### 遞迴樣板 * **==樣板中可以再有樣板==**,這樣就如同一個二維陣列,如同 `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]) **--實做--** > ![](https://i.imgur.com/j9Qr5C0.png) ### 多個類型參數 * **如同 [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 計算單個模板的時候也需要寫出模板 `<>`** **--實作--** > ![](https://i.imgur.com/tbkiCCp.png) ### 預設型態樣板 * 如同建構函數中的預設參數,`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. 在使用時就同預設建構參數,**如果有預設型態,可以不用指定有預設的型態** **--實作--** > ![](https://i.imgur.com/4vwmDv5.png) ## 模板特定化 | 處理方式 | 解釋 | 範例 | | -------- | -------- | -------- | | 隱式實體化 (`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(); ``` * 宣告在全域,**可以指定實例化類別**,可實例化方法、類,**==如果已經顯式實體化類就不能實體化方法==,因為會 ++導致方法重複定義++** > ![](https://i.imgur.com/XjRhFCI.png) ### 顯示特定化 * 簡稱為 **特定化,一般來說定義只有一個**,但特定化可以針對不同型態 * **顯示特定化是針對特定型態的定義,此定義會用來 ++取代通用樣板++**,就像是在比較大小的樣板,用在數字上只要複寫 `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(); } ``` **--實作--** > ![](https://i.imgur.com/Ow1DSIX.png) ### 部分特定化 * 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. 在實作虛擬類繼承時,多重繼承的類呼叫基類建構函數有警告...好像是 **++順序錯誤++** ??? > ![](https://i.imgur.com/QsGdjs3.png) ::: ###### tags: `C++`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully