日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

神奇的C++模版

發布時間:2025/3/21 c/c++ 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 神奇的C++模版 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

介紹
   如果你是個模板的高手,你就可以將ATL的學習作為一種享受。在這一節中,我將要嘗試解釋一些ATL使用的模板技術。我不能保證你讀完本節后能成為一個模板高手,只能是盡我所能讓你在讀完本文后能夠更輕松地理解ATL的源碼。
程序35.

#include <iostream> using namespace std;template <typename T> T Maximum(const T& a, const T& b) {return a > b ? a : b; } int main() {cout << Maximum(5, 10) << endl;cout << Maximum(''A'', ''B'') << endl;return 0; }程序的輸出為:10 B 在這里,由于模板函數的關系,我們就沒有必要分別重載int和char數據類型的函數版本了。其中很重要的一點是,函數的兩個參數類型必須一致。但是如果我們傳入了不同的數據類型,我們就需要告知編譯器應該把這個參數考慮為哪種數據類型。
程序36.#include <iostream> using namespace std;template <typename T> T Maximum(const T& a, const T& b) {return a > b ? a : b; }int main() {cout << Maximum<int>(5, ''B'') << endl;cout << Maximum<char>(5, ''B'') << endl;return 0; }程序的輸出為:66 B 我們也可以編寫類模板,下面就是一個簡單版本的堆棧類模板。
程序37.#include <iostream> using namespace std;template <typename T> class Stack { private:T* m_pData;int m_iTop;public:Stack(int p_iSize = 0) : m_iTop(0) {m_pData = new T[p_iSize];}void Push(T p_iData) {m_pData[m_iTop++] = p_iData;}T Pop() {return m_pData[--m_iTop];}T Top() {return m_pData[m_iTop];}~Stack() {if (m_pData) {delete [] m_pData;}}private:Stack(const Stack<T>&);Stack<T>& operator = (const Stack<T>&); };int main() {Stack<int> a(10);a.Push(10);a.Push(20);a.Push(30);cout << a.Pop() << endl;cout << a.Pop() << endl;cout << a.Pop() << endl;return 0; }這個程序中沒有任何錯誤檢驗,不過這個程序的目的只是示范模板的用法,而不是真的要寫一個專業的堆棧類。
   程序的輸出為:30 20 10我們也可以將數據類型作為一個模板參數來傳遞,并且為它設置一個默認值。讓我們來稍微修改一下程序37(譯注:原文為“程序36”,應為37),并將堆棧的尺寸作為一個模板參數來傳遞,而不是作為構造函數的參數。
程序38.#include <iostream> using namespace std;template <typename T, int iSize = 10> class Stack { private:T m_pData[iSize];int m_iTop;public:Stack() : m_iTop(0) {}void Push(T p_iData) {m_pData[m_iTop++] = p_iData;}T Pop() {return m_pData[--m_iTop];}T Top() {return m_pData[m_iTop];}private:Stack(const Stack<T>&);Stack<T>& operator = (const Stack<T>&); };int main() {Stack<int, 10> a;a.Push(10);a.Push(20);a.Push(30);cout << a.Pop() << endl;cout << a.Pop() << endl;cout << a.Pop() << endl;return 0; } 程序的輸出和前一個相同。這個程序最重要的一點為:template <typename T, int iSize = 10>現在就有一個問題:哪一個更好呢?通常,傳遞模板參數的辦法是優于給構造函數傳遞參數的。為什么呢?因為在你將堆棧尺寸作為模板參數傳遞的時候,這個給定數據類型的數組就會被自動創建;而給構造函數傳遞參數則意味著構造函數會在運行時使用new或malloc一系列功能來分配內存。如果我們已經確定在創建好堆棧之后就不再更改它的尺寸(就像上面程序中private段中拷貝構造函數和賦值運算符中的那樣)了,那么無疑使用模板參數是更加適合的。
   (譯注:作者Amjad在上面兩個程序中并未實現拷貝構造函數和賦值運算符,這大概是由于這兩者對于本文的內容無關緊要之故吧。在此我要指出的是正如作者所說,“不是真的要寫一個專業的堆棧類”、“沒有任何錯誤檢驗”,并且這其中類的組織結構使得精確實現拷貝構造函數和賦值運算符有一定的難度,尤其是程序37——我們無法從一個已經定義好的堆棧獲得它的最大容量。)
   你也可以將用戶定義的類作為一個類型參數來傳遞,但是請確認這個類擁有在那個模板函數或類模板中重載的所有運算符。
   例如,請看程序35那個求最大值的函數。這個程序使用了一個operator >,所以如果我們傳遞自己的類的話,那么這個類必須重載了>運算符。下面這個例子示范了這一點。
程序39.#include <iostream> using namespace std;template <typename T> T Maximum(const T& a, const T& b) {return a > b ? a : b; }class Point { private:int m_x, m_y;public:Point(int p_x = 0, int p_y = 0) : m_x(p_x), m_y(p_y) {}bool friend operator > (const Point& lhs, const Point& rhs) {return lhs.m_x > rhs.m_x && lhs.m_y > rhs.m_y;}friend ostream& operator << (ostream& os, const Point& p) {return os << "(" << p.m_x << ", " << p.m_y << ")";} };int main() {Point a(5, 10), b(15, 20);cout << Maximum(a, b) << endl;return 0; }程序的輸出為:(15, 20)同樣,我們也能夠將一個類模板作為一個模板參數傳遞。現在讓我們來編寫這樣一個Point類,并將其作為一個模板參數傳遞給Stack類模板。
程序40.#include <iostream> using namespace std;template <typename T> class Point { private:T m_x, m_y;public:Point(T p_x = 0, T p_y = 0) : m_x(p_x), m_y(p_y) {}bool friend operator > (const Point<T>& lhs, const Point<T>& rhs) {return lhs.m_x > rhs.m_x && lhs.m_y > rhs.m_y;}friend ostream& operator << (ostream& os, const Point<T>& p) {return os << "(" << p.m_x << ", " << p.m_y << ")";} };template <typename T, int iSize = 10> class Stack { private:T m_pData[iSize];int m_iTop;public:Stack() : m_iTop(0) {}void Push(T p_iData) {m_pData[m_iTop++] = p_iData;}T Pop() {return m_pData[--m_iTop];}T Top() {return m_pData[m_iTop];}private:Stack(const Stack<T>&);Stack<T>& operator = (const Stack<T>&); };int main() {Stack<Point<int> > st;st.Push(Point<int>(5, 10));st.Push(Point<int>(15, 20));cout << st.Pop() << endl;cout << st.Pop() << endl;return 0; }程序的輸出為:(15, 20) (5, 10)這個程序中最重要的部分為:Stack<Point<int> > st;在這里,你必須在兩個大于號之間放置一個空格,否則編譯器就會將它看作>>(右移運算符)并產生錯誤。
   對于這個程序我們還可以這么做,就是為模板參數傳遞默認的類型值,也就是將template <typename T, int iSize = 10>換為template <typename T = int, int iSize = 10>現在我們就沒有必要一定在創建Stack類對象的時候傳遞數據類型了,但是你仍然需要書寫這一對尖括弧以告知編譯器使用默認的數據類型。 你可以這么創建對象:Stack<> st;當你在類的外部定義(譯注:原文此處是“declare”,我以為應該是“define”更準確一些。)類模板的成員函數的時候,你仍然需要寫出帶有模板參數的類模板全稱。
程序41.#include <iostream> using namespace std;template <typename T> class Point { private:T m_x, m_y;public:Point(T p_x = 0, T p_y = 0);void Setxy(T p_x, T p_y);T getX() const;T getY() const;friend ostream& operator << (ostream& os, const Point<T>& p) {return os << "(" << p.m_x << ", " << p.m_y << ")";} };template <typename T> Point<T>::Point(T p_x, T p_y) : m_x(p_x), m_y(p_y) { }template <typename T> void Point<T>::Setxy(T p_x, T p_y) {m_x = p_x;m_y = p_y; }template <typename T> T Point<T>::getX() const {return m_x; }template <typename T> T Point<T>::getY() const {return m_y; }int main() {Point<int> p;p.Setxy(20, 30);cout << p << endl;return 0; }程序的輸出為:(20, 30)讓我們來稍微修改一下程序35,傳遞字符串值(而不是int或float)作為參數,并看看結果吧。
程序42.#include <iostream> using namespace std;template <typename T> T Maximum(T a, T b) {return a > b ? a : b; }int main() {cout << Maximum("Pakistan", "Karachi") << endl;return 0; }程序的輸出為Karachi。(譯注:在我的Visual Studio.net 2003下的輸出卻為Pakistan,這不同的原因是編譯器組織字符串地址的方式不同決定的,但是Maximum函數的結果是應該返回內存高位的那個地址的,這和作者說的道理是一致的。)為什么呢?因為這里char*作為模板參數傳遞, Karachi在內存中存儲的位置更高,而>運算符僅僅比較這兩個地址值而不是字符串本身。
   那么,如果我們希望基于字符串的長度來比較而不是地址的話,應該怎么做呢?
   解決的辦法是對char*數據類型進行模板的特化。下面是一個模板特化的例子。
程序43.#include <iostream> using namespace std;template <typename T> T Maximum(T a, T b) {return a > b ? a : b; }template <> char* Maximum(char* a, char* b) {return strlen(a) > strlen(b) ? a : b; }int main() {cout << Maximum("Pakistan", "Karachi") << endl;return 0; }至于類模板,也可以用相同的辦法進行特化。
程序44.#include <iostream> using namespace std;template <typename T> class TestClass { public:void F(T pT) {cout << "T version" << ''\t'';cout << pT << endl;} };template <> class TestClass<int> { public:void F(int pT) {cout << "int version" << ''\t'';cout << pT << endl;} };int main() {TestClass<char> obj1;TestClass<int> obj2;obj1.F(''A'');obj2.F(10);return 0; }程序的輸出為:T version A int version 10ATL中就有若干類是類似這樣的特化版本,例如在ATLBASE.H中定義的CComQIPtr。
   模板也可以在不同的設計模式中使用,例如策略設計模式可以使用模板實現。
程序45.#include <iostream> using namespace std;class Round1 { public:void Play() {cout << "Round1::Play" << endl;} };class Round2 { public:void Play() {cout << "Round2::Play" << endl;} };template <typename T> class Strategy { private:T objT; public:void Play() {objT.Play();} };int main() {Strategy<Round1> obj1;Strategy<Round2> obj2;obj1.Play();obj2.Play();return 0; }在這里,Round1和Round2為一個游戲中不同的關卡類,并且Strategy類依靠傳遞的模板參數來決定該做些什么。
   程序的輸出為:Round1::Play Round2::PlayATL就是使用Strategy設計模式來實現線程的。
   代理設計模式也可以使用模板實現,智能指針就是一個例子。下面就是一個沒有使用模板的簡單版本智能指針。
程序46.#include <iostream> using namespace std;class Inner { public:void Fun() {cout << "Inner::Fun" << endl;} };class Outer { private:Inner* m_pInner;public:Outer(Inner* p_pInner) : m_pInner(p_pInner) {}Inner* operator -> () {return m_pInner;} };int main() {Inner objInner;Outer objOuter(&objInner);objOuter->Fun();return 0; }程序的輸出為:Inner::Fun()簡單地說來,我們僅僅重載了->運算符,但是在實際的智能指針中,所有必須的運算符(例如=、==、!、&、*)都需要被重載。以上的智能指針有一個大問題:它只能包含指向Inner對象的指針。我們可以編寫Outer類模板來取消這一限制,現在讓我們來略微修改一下程序。
程序47.#include <iostream> using namespace std;class Inner { public:void Fun() {cout << "Inner::Fun" << endl;} };template <typename T> class Outer { private:T* m_pInner;public:Outer(T* p_pInner) : m_pInner(p_pInner) {}T* operator -> () {return m_pInner;} };int main() {Inner objInner;Outer<Inner> objOuter(&objInner);objOuter->Fun();return 0; }程序的輸出和前一個一樣,但是現在Outer類就可以包含任何類型了,只需要把類型作為模板參數傳遞進來即可。
   ATL中有兩個智能指針,CComPtr和CComQIPtr。
   你可以用模板做一些有趣的事情,例如你的類可以在不同的情況下成為不同基類的子類。
程序48.#include <iostream> using namespace std;class Base1 { public:Base1() {cout << "Base1::Base1" << endl;} };class Base2 { public:Base2() {cout << "Base2::Base2" << endl;} };template <typename T> class Drive : public T { public:Drive() {cout << "Drive::Drive" << endl;} };int main() {Drive<Base1> obj1;Drive<Base2> obj2;return 0; }程序的輸出為:Base1::Base1 Drive::Drive Base2::Base2 Drive::Drive 在這里,Drive類是繼承自Base1還是Base2是由在對象創建的時候傳遞給模板的參數決定的。
   ATL也使用了這一技術。當你使用ATL創建COM組件的時候,CComObject就會繼承自你的類。在這里ATL利用了模板,因為它不會預先知道你用來作COM組件而創建的類的名稱。CComObject類定義于ATLCOM.H文件之中。
   在模板的幫助下,我們也可以模擬虛函數。現在讓我們重新回憶一下虛函數,下面是一個簡單的例子。
程序49.#include <iostream> using namespace std;class Base { public:virtual void fun() {cout << "Base::fun" << endl;}void doSomething() {fun();} };class Drive : public Base { public:void fun() {cout << "Drive::fun" << endl;} };int main() {Drive obj;obj.doSomething();return 0; }程序的輸出為:Drive::fun在模板的幫助下,我們可以實現與之相同的行為。
程序50.#include <iostream> using namespace std;template <typename T> class Base { public:void fun() {cout << "Base::fun" << endl;}void doSomething() {T* pT = static_cast<T*>(this);pT->fun();} };class Drive : public Base<Drive> { public:void fun() {cout << "Drive::fun" << endl;} };int main() {Drive obj;obj.doSomething();return 0; }程序的輸出和前一個是一樣的,所以我們可以用模板來模擬虛函數的行為。
   程序中一個有趣的地方為class Drive : public Base<Drive> {這表明我們可以將Drive類作為一個模板參數來傳遞。程序中另外一個有趣的地方是基類中的doSomething函數。T* pT = static_cast<T*>(this); pT->fun();在這里基類的指針被轉換為派生類的指針,因為派生類是作為Base類的模板參數傳遞的。這個函數可以通過指針來執行,由于指針指向了派生類的對象,所以派生類的對象就被調用了。
   但是這就有一個問題了:我們為什么要這樣做?答案是:這樣可以節省虛函數帶有的額外開銷,也就是虛函數表指針、虛函數表以及節省了調用虛函數所花費的額外時間。這就是ATL中使組件盡可能小、盡可能快的主要思想。
   現在,你的腦海中可能會浮現另外一個問題。如果依靠這一開銷更少的技術可以模擬虛函數的話,那我們為什么還要調用虛函數呢?我們不應該用這一技術替換所有的虛函數嗎?對于這一問題,我可以簡短地回答你:不,我們不能用這一技術替換虛函數。
   其實這一技術還存在一些問題。第一,你不能從Drive類進行更深層的繼承,如果你試著這么做,那么它將不再會是虛函數的行為了。而對于虛函數來說,這一切就不會發生。一旦你將函數聲明為虛函數,那么在派生類中的所有函數都會成為虛函數,無論繼承鏈有多深。現在我們看看當從Drive中再繼承一個類的時候會發生什么。
程序51.#include <iostream> using namespace std;template <typename T> class Base { public:void fun() {cout << "Base::fun" << endl;}void doSomething() {T* pT = static_cast<T*>(this);pT->fun();} };class Drive : public Base<Drive> { public:void fun() {cout << "Drive::fun" << endl;} };class MostDrive : public Drive { public:void fun() {cout << "MostDrive::fun" << endl;} };int main() {MostDrive obj;obj.doSomething();return 0; }程序的輸出和前一個一樣。但是對于虛函數的情況來說,輸出就應該是:MostDrive::fun這一技術還有另外一個問題,就是當我們使用Base類的指針來存儲派生類的地址的時候。
程序52.#include <iostream> using namespace std;template <typename T> class Base { public:void fun() {cout << "Base::fun" << endl;}void doSomething() {T* pT = static_cast<T*>(this);pT->fun();} };class Drive : public Base<Drive> { public:void fun() {cout << "Drive::fun" << endl;} };int main() {Base* pBase = NULL;pBase = new Drive;return 0; }這個程序會給出一個錯誤,因為我們沒有向基類傳遞模板參數。現在我們稍微修改一下,并傳遞模板參數。
程序53.#include <iostream> using namespace std;template <typename T> class Base { public:void fun() {cout << "Base::fun" << endl;}void doSomething() {T* pT = static_cast<T*>(this);pT->fun();} };class Drive : public Base<Drive> { public:void fun() {cout << "Drive::fun" << endl;} };int main() {Base<Drive>* pBase = NULL;pBase = new Drive;pBase->doSomething();return 0; }現在程序正常工作,并給出了我們所期望的輸出,也就是:Drive::fun但是在Base類有多個繼承的時候,就會出現問題。為了更好地弄懂這一點,請看下面的程序。
程序54.#include <iostream> using namespace std;template <typename T> class Base { public:void fun() {cout << "Base::fun" << endl;}void doSomething() {T* pT = static_cast<T*>(this);pT->fun();} };class Drive1 : public Base<Drive1> { public:void fun() {cout << "Drive1::fun" << endl;} };class Drive2 : public Base<Drive2> { public:void fun() {cout << "Drive2::fun" << endl;} };int main() {Base<Drive1>* pBase = NULL;pBase = new Drive1;pBase->doSomething();delete pBase;pBase = new Drive2;pBase->doSomething();return 0; }程序會在下面的代碼處給出錯誤:pBase = new Drive2;

因為pBase是一個指向Base<Drive1>的指針,而不是Base<Drive2>。簡單地說來,就是你不能使Base類的指針指向不同的Drive類。換句話說,你不能使用Base類指針的數組存儲不同的派生類,而在虛函數之中則是可行的。

原文:http://www.vckbase.com/document/viewdoc/?id=1352

總結

以上是生活随笔為你收集整理的神奇的C++模版的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。