深入理解c++中的函數模板

非類型模板參數

模板參數分類類型形參非類型形參
類型形參:出現在模板參數列表中,跟在class或者typename之類的參數類型名稱。
非類型形參,就是用一個常量作為類(函數)模板的一個參數,在類(函數)模板中可將該參數當成常量來使用。

namespace bite
{template<class T, size_t N>class array{public:void push_back(constT& data){//N=10;_array[_size++] = data;}T& operator[](size_t){assert(index < _size)return _array[index];}bool empty()const{return 0 == _size;}size_t size()const{return _size;}private:T _array[N];size_t _size;};
}

注意事項

  1. 浮點數、類對象以及字符串是不允許作為非類型模板參數的。
  2. 非類型的模板參數必須在編譯期就能確認結果。

模板的特化

模板特化的概念

通常情況下,使用模板可以實現一些與類型無關的代碼,但對于一些特殊類型的可能會得到一些錯誤的結果,比如

class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){}bool operator>(const Date&d)const{return _day > d._day;}friend ostream& operator<<(ostream& _cout, const Date& d){_cout << d._year << "/" << d._month << "/" << d._day << endl;return _cout;}
private:int _year;int _month;int _day;
};
template<class T>
T& Max(T& left, T& right)
{return left > right ? left : right;
}char *p1 = "world";char *p2 = "hello";cout << Max(p1, p2) << endl;cout << Max(p2, p1) << endl;

這個代碼他就無法比較字符串類型的變量的大小
此時,就需要對模板進行特化。即:在原模板類的基礎上,針對特殊類型所進行特殊化的實現方式

函數模板特化

如果不需要通過形參改變外部實參加上const
例如

template<class T>
const T& Max(const T& left, const T& right)
{return left > right ? left : right;
}
//函數模板的特化
template<>
char *& Max<char*>(char*& left, char*& right)
{//>0大于 =0等于 <0小于if (strcmp(left, right) > 0){return left;}return right;
}

注意事項

  1. 必須要先有一個基礎的函數模板
  2. 關鍵字template后面接一對空的尖括號<>
  3. 函數名后跟一對尖括號,尖括號中指定需要特化的類型
  4. 函數形參表: 必須要和模板函數的基礎參數類型完全相同,如果不同編譯器可能會報一些奇怪的錯誤。

一般函數模板沒必要特化,直接把相應類型的函數給出就行

char* Max(char *left, char* right)
{if (strcmp(left, right) > 0){return left;}return right;
}

類模板的特化

全特化

//全特化-----對所有類型參數進行特化
template<class T1, class T2>
class Data
{
public:Data() { cout << "Data<T1, T2>" << endl; }
private:T1 _d1;T2 _d2;
};template<>
class Data<int, int>
{
public:Data() { cout << "Data<int, int>" << endl; }
private:int _d1;int _d2;
};int main()
{Data<int, double>d1;Data<int, int>d2;return 0;
}

偏特化

部分特化
//偏特化,將模板參數列表中的參數部分參數類型化
template<class T1>
class Data<T1,int>
{
public:Data() { cout << "Data<T1, int>" << endl; }
private:T1 _d1;int _d2;
};int main()
{Data<int, double>d1;Data<int, int>d2;Data<double, int>d3;system("pause");return 0;
}
參數更進一步的限制
//偏特化:讓模板參數列表中的類型限制更加的嚴格
template<class T1,class T2>
class Data<T1*, T2*>
{
public:Data() { cout << "Data<T1*, T2*>" << endl; }
private:T1* _d1;T2* _d2;
};int main()
{Data<int*, int>d1;Data<int, int*>d2;Data<int*, int*>d3;//特化Data<int*, double*>d4;//特化system("pause");return 0;
}

模板特化的作用之類型萃取

編寫一個通用類型的拷貝函數

template<class T>
void Copy(T* dst, T* src, size_t size)
{memcpy(dst, src, sizeof(T)*size);
}

上述代碼雖然對于任意類型的空間都可以進行拷貝,但是如果拷貝自定義類型對象就可能會出錯,因為自定義類型對象有可能會涉及到深拷貝(比如string),而memcpy屬于淺拷貝。如果對象中涉及到資源管理,就只能用賦值。

class String
{
public:String(const char* str = ""){if (str == nullptr)str = "";this->_str = new char[strlen(str) + 1];strcpy(this->_str, str);}String(const String& s):_str ( new char[strlen(s._str)+1]){strcpy(this->_str, s._str);}String& operator=(const String &s){if (this != &s){char *str = new char[strlen(s._str) + 1];strcpy(str, s._str);delete[]_str;		_str = s._str;}}~String(){delete[]_str;}private:char* _str;
};//寫一個通用的拷貝函數,要求:效率盡量高
template<class T>
void Copy(T* dst, T* src, size_t size)
{memcpy(dst, src, sizeof(T)*size);
}void TestCopy()
{int array1[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };int array2[10];Copy(array2, array1, 10);String s1[3] = { "111", "222", "333" };String s2[3];Copy(s2, s1, 3);
}

如果是自定義類型用memcpy,那么會存在

  1. 淺拷貝----導致代碼崩潰
  2. 內存泄露—S2數組中每個String類型對象原來的空間丟失了

增加一個拷貝函數處理淺拷貝

template<class T>
void Copy2(T* dst, T*src, size_t size)
{for (size_t i = 0; i < size; ++i){dst[i] = src[i];}
}
  1. 優點:一定不會出錯
  2. 缺陷:效率比較低,讓用戶做選擇,還需要判斷調用哪個函數

讓函數自動去識別所拷貝類型是內置類型或者自定義類型

bool IsPODType(const char* strType)
{//此處將所有的內置類型枚舉出來const char * strTypes[] = { "char", "short", "int", "long", "long long", "float", "double" };for (auto e : strTypes){if (strcmp(strType, e) == 0)return true;}return false;
}template<class T>
void Copy(T* dst, T*src, size_t size)
{//通過typeid可以將T的實際類型按照字符串的方式返回if (IsPODType(typeid(T).name()){//T的類型:內置類型memcpy(dst, src, sizeof(T)*size);}else{//T的類型:自定義類型----原因:自定義類型種可能會存在淺拷貝for (size_t i = 0; i < size; ++i){dst[i] = src[i];}}
}

在編譯期間就確定類型—類型萃取

如果把一個成員函數的聲明和定義放在類里面,編譯器可能會把這個方法當成內聯函數來處理

class String
{
public:String(const char* str = ""){if (str == nullptr)str = "";this->_str = new char[strlen(str) + 1];strcpy(this->_str, str);}String(const String& s):_str(new char[strlen(s._str) + 1]){strcpy(this->_str, s._str);}String& operator=(const String& s){if (this != &s){char* str = new char[strlen(s._str) + 1];strcpy(str, s._str);delete[] _str;_str = str;}return *this;}~String(){delete[]_str;}private:char* _str;
};//確認T到底是否是內置類型
//是
//不是
//對應內置類型
struct TrueType
{static bool Get(){return true;}
};//對應自定義類型
struct FalseType
{static bool Get(){return true;}
};template<class T>
struct TypeTraits
{typedef FalseType PODTYPE;};template<>
struct TypeTraits<char>
{typedef TrueType PODTYPE;
};template<>
struct TypeTraits<short>
{typedef TrueType PODTYPE;
};
template<>
struct TypeTraits<int>
{typedef TrueType PODTYPE;
};//........還有很多內置類型template<class T>
void Copy(T* dst, T* src, size_t size)
{// 通過typeid可以將T的實際類型按照字符串的方式返回if (TypeTraits<T>::PODTYPE::Get()){// T的類型:內置類型memcpy(dst, src, sizeof(T)*size);}else{// T的類型:自定義類型---原因:自定義類型中可能會存在淺拷貝for (size_t i = 0; i < size; ++i)dst[i] = src[i];}
}

STL中的類型萃取

// 代表內置類型
struct __true_type {};
// 代表自定義類型
struct __false_type {};template <class type>
struct __type_traits
{typedef __false_type is_POD_type;
};// 對所有內置類型進行特化
template<>
struct __type_traits<char>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<signed char>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<unsigned char>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<int>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<float>
{typedef __true_type is_POD_type;
};
template<>
struct __type_traits<double>
{typedef __true_type is_POD_type;
};
// 注意:在重載內置類型時,所有的內置類型都必須重載出來,包括有符號和無符號,比如:對于int類型,必
須特化三個,int -- signed int -- unsigned int
// 在需要區分內置類型與自定義類型的位置,標準庫通常都是通過__true_type與__false_type給出一對重載// 函數,然后用一個通用函數對其進行封裝
// 注意:第三個參數可以不提供名字,該參數最主要的作用就是讓兩個_copy函數形成重載
template<class T>
void _copy(T* dst, T* src, size_t n, __true_type)
{memcpy(dst, src, n*sizeof(T));
}
template<class T>
void _copy(T* dst, T* src, size_t n, __false_type)
{for (size_t i = 0; i < n; ++i)dst[i] = src[i];
}
template<class T>
void Copy(T* dst, T* src, size_t n)
{_copy(dst, src, n, __type_traits<T>::is_POD_type());
}

分離編譯

預處理----->編譯---->匯編----->鏈接
在這里插入圖片描述

// a.h
template<class T>
T Add(const T& left, const T& right);
// a.cpp
template<class T>
T Add(const T& left, const T& right)
{return left + right;
}
// main.cpp
#include"a.h"
int main()
{Add(1, 2);Add(1.0, 2.0);return 0;
}

沒有模板的函數,沒有問題
有模板的函數,編譯可以過,但是鏈接會出錯

函數模板編譯:

  1. 實例化之前:編譯器只做一些簡答的語法檢測,不會生成處理具體類型的代碼。并不會確認函數的入口地址
  2. 實例化期間:編譯器會推演形參類型來確保模板參數列表中T的實際類型,在生成具體類型的代碼
  3. 不支持分離編譯

解決分離編譯

  1. 將聲明和定義放到一個文件 “xxx.hpp” 里面或者xxx.h其實是可以的。
  2. 模板定義的位置顯式實例化。

模板總結

優點

  1. 模板復用了代碼,節省資源,更快的迭代開發,C++的標準模板庫(STL)因此而產生
  2. 增強了代碼的靈活性

缺點

  1. 模板會導致代碼膨脹問題,也會導致編譯時間變長
  2. 出現模板編譯錯誤時,錯誤信息非常凌亂,不易定位錯誤

更加深入學習參考這個鏈接

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383133.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383133.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383133.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

c++中的IO流

c語言中的IO操作 標準類型的輸入輸出: 輸入------>數據來源是通過鍵盤進行輸入輸出------>程序中的數據輸出到控制臺 c語言中: scanf:輸入 printf:輸出 兩個函數的相同點 1 —格式串 2 —不定參數 兩個函數的缺陷 1 —用戶要提供數據的格式—用戶要記憶大量的格式串—…

201301 JAVA2~3級---走格子

請編寫一個函數&#xff08;允許增加子函數&#xff09;&#xff0c;計算n x m的棋盤格子&#xff08;n為橫向的格子數&#xff0c;m為豎向的格子數&#xff09;沿著各自邊緣線從左上角走到右下角&#xff0c;總共有多少種走法&#xff0c;要求不能走回頭路&#xff0c;即&…

復習Linux基本操作----常見指令

Linux基本操作 ls命令 ls(list):相當于windows上的文件資源管理器 語法&#xff1a; ls [選項][目錄或文件] 功能&#xff1a;對于目錄&#xff0c;該命令列出該目錄下的所有子目錄與文件。對于文件&#xff0c;將列出文件名以及其他信息。 常用選項&#xff1a; -a 列出目…

復習Linux基礎操作---權限操作

shell命令以及運行原理 Linux嚴格意義上說的是一個操作系統&#xff0c;我們稱之為“核心&#xff08;kernel&#xff09;“ &#xff0c;但我們一般用戶&#xff0c;不能直接使用kernel。而是通過kernel的“外殼”程序&#xff0c;也就是所謂的shell&#xff0c;來與kernel溝…

【劍指offer】_01 (二維數組中的查找)

題目描述 在一個二維數組中&#xff08;每個一維數組的長度相同&#xff09;&#xff0c;每一行都按照從左到右遞增的順序排序&#xff0c;每一列都按照從上到下遞增的順序排序。請完成一個函數&#xff0c;輸入這樣的一個二維數組和一個整數&#xff0c;判斷數組中是否含有該…

【劍指offer】_02替換空格

題目描述 請實現一個函數&#xff0c;將一個字符串中的每個空格替換成“%20”。例如&#xff0c;當字符串為We Are Happy.則經過替換之后的字符串為We%20Are%20Happy。 解題思路 首先我們先算出整個字符串的長度&#xff0c;還有總共多少個空格。因為空格只占一個字節&#…

【劍指offer】_04 重建二叉樹

題目描述 輸入某二叉樹的前序遍歷和中序遍歷的結果&#xff0c;請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6}&#xff0c;則重建二叉樹并返回。 解題思路 用前序的順序…

再談c++中的多態

何為多態 多態的概念&#xff1a;通俗來說&#xff0c;就是多種形態&#xff0c;具體點就是去完成某個行為&#xff0c;當不同的對象去完成時會產生出不同的狀態。 多態的實現 在繼承的體系下 基類中必須有虛函數(被virtual關鍵字修飾的成員函數)&#xff0c;在派生類中必須…

再談c++中的繼承

繼承的概念 繼承(inheritance)機制是面向對象程序設計使代碼可以復用的最重要的手段&#xff0c;它允許程序員在保持原有類特性的基礎上進行擴展&#xff0c;增加功能&#xff0c;這樣產生新的類&#xff0c;稱派生類。繼承呈現了面向對象程序設計的層次結構&#xff0c;體現了…

紅黑樹概念及其相關操作的實現

紅黑樹的概念 紅黑樹&#xff0c;是一種二叉搜索樹&#xff0c;但它并不像AVL樹一樣&#xff0c;每個結點綁定一個平衡因子。但在每個結點上增加一個存儲位表示結點的顏色&#xff0c;可以是Red或Black。 通過 對任何一條從根到葉子的路徑上各個結點著色方式的限制&#xff0c…

模擬實現STL中map和set容器

紅黑樹的迭代器 //紅黑樹的迭代器 template<class T> struct RBTreeIterator {typedef RBTreeNode<T>Node;typedef RBTreeIterator<T> Self; public:RBTreeIterator(Node* pNode nullptr):_pNode(pNode){}//具有指針操作T& operator*(){return _pNode-…

【劍指offer】_05 連續子數組最大和

題目描述 HZ偶爾會拿些專業問題來忽悠那些非計算機專業的同學。今天測試組開完會后,他又發話了:在古老的一維模式識別中,常常需要計算連續子向量的最大和,當向量全為正數的時候,問題很好解決。但是,如果向量中包含負數,是否應該包含某個負數,并期望旁邊的正數會彌補它呢&#…

排序上---(排序概念,常見排序算法,直接插入,希爾排序,直接選擇排序,堆排序)

排序的概念 排序&#xff1a;所謂排序&#xff0c;就是使一串記錄&#xff0c;按照其中的某個或某些關鍵字的大小&#xff0c;遞增或遞減的排列起來的操作。穩定性&#xff1a;假定在待排序的記錄序列中&#xff0c;存在多個具有相同的關鍵字的記錄&#xff0c;若經過排序&…

排序下---(冒泡排序,快速排序,快速排序優化,快速排序非遞歸,歸并排序,計數排序)

排序上 排序上 交換類排序 基本思想&#xff1a;所謂交換&#xff0c;就是根據序列中兩個記錄鍵值的比較結果來對換這兩個記錄在序列中的位置&#xff0c;交換排序的特點是&#xff1a;將鍵值較大的記錄向序列的尾部移動&#xff0c;鍵值較小的記錄向序列的前部移動。 冒泡…

哈希的概念及其操作

哈希概念 順序結構以及平衡樹中&#xff0c;元素關鍵碼與其存儲位置之間沒有對應的關系&#xff0c;因此在查找一個元素時&#xff0c;必須要經過關鍵碼的多次比較。順序查找時間復雜度為O(N)&#xff0c;平衡樹中為樹的高度&#xff0c;即O( Log2N)&#xff0c;搜索的效率取決…

軟件工程---1.概述

軟件的特征 抽象&#xff1a; 不可觸摸&#xff0c;邏輯實體&#xff0c;可記錄&#xff0c;但看不到復制成本低&#xff1a;不受物質材料的限制&#xff0c;不受物理定律或加工過程的制約&#xff0c;與開發成本相比&#xff0c;復制成本很低無折舊、受硬件制約、未完全擺脫手…

軟件工程---2.軟件過程

三個模型 瀑布模型增量模型集成和配置模型 沒有適用于所有不同類型軟件開發的過程模型。 瀑布模型 需求定義系統和軟件的設計實現與單元測試集成與系統測試運行與維護 瀑布模型的特征 從上一項活動中接受該項活動的工作成果&#xff08;工作產品&#xff09;&#xff0c;作…

軟件工程---3.敏捷軟件開發

敏捷軟件開發 極限編程&#xff08;XP&#xff0c; Beck1999&#xff09;Scrum方法&#xff08;Schwaber and Beedle 2001&#xff09;DSDM方法&#xff08;Stapleton 2003&#xff09; 敏捷軟件的開發宣言 個體和交互勝過過程和工具可以工作的軟件勝過面面俱到的文檔客戶合…

軟件工程---4.需求工程

需求工程定義 找出、分析、文檔化并且檢查需求的過程被稱為需求工程 需求的兩個描述層次 用戶需求&#xff0c;指高層的抽象需求。使用自然語言、圖形描述需求。系統需求&#xff0c;指底層的詳細需求。使用系統需求文檔&#xff08;有時被稱為功能規格說明&#xff09;應該…

軟件工程---5.系統建模

從不同視角對系統建模 外部視角&#xff0c;上下文模型&#xff0c;對系統上下文或環境建模交互視角&#xff0c;交互模型&#xff08;功能模型&#xff09;&#xff0c;對系統與參與者或系統內構件之間的交互建模結構視角&#xff0c;結構模型&#xff08;靜態模型&#xff0…