網(wǎng)站首頁 編程語言 正文
在了解string之前,我們需要了解模板等等的一些鋪墊知識,讓我們開始吧!
一、泛型編程
泛型編程是什么意思呢?我們通過下面的例子來具體了解:
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
double temp = left;
left = right;
right = temp;
}
int main()
{
int a = 1, b = 2;
Swap(a, b);
double c=1.33,d=2.33;
Swap(a,b)
}
就拿交換函數(shù)來說,當我們交換不同類型的變量的值,那就需要不停的寫交換函數(shù)的重載,這樣代碼復用率就較低,那我們能不能創(chuàng)造一個模板呢??
一個Swap的模板,但是我可以用不同的類型去實現(xiàn)這個模板,繼而試用它。
如果在 C++ 中,也能夠存在這樣一個 模具 ,通過給這個模具中 填充不同材料 ( 類型 ) ,來 獲得不同材料的鑄件 ( 即生成具體類型的代碼)。 泛型編程:編寫與類型無關的通用代碼,是代碼復用的一種手段。模板是泛型編程的基礎。
二、模板(初階)
模板分為:函數(shù)模板和類模板
1.函數(shù)模板
1.單參數(shù)類型
函數(shù)模板代表了一個函數(shù)家族,該函數(shù)模板與類型無關,在使用時被參數(shù)化,根據(jù)實參類型產(chǎn)生函數(shù)的特定 類型版本。 就拿Swap來說: typename 是 用來定義模板參數(shù) 關鍵字,T是類型(也可以用class,(class T))
template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
int main()
{
int a = 1, b = 2;
Swap(a, b);
int x = 1, y = 2;
Swap(x, y);
double m = 1.1, n = 2.2;
Swap(m, n);
char p = 'a', q = 'b';
Swap(p, q);
Swap(m,a);//不同類型
}
那么,具體是怎樣實現(xiàn)的呢?
函數(shù)模板是一個藍圖,它本身并不是函數(shù),是編譯器用使用方式產(chǎn)生特定具體類型函數(shù)的模具。所以其實模 板就是將本來應該我們做的重復的事情交給了編譯器。
編譯器通過類型推演,將函數(shù)模板進行實例化,對應的T就會替換成具體的類型,模板實例化是用幾個實例化幾個,不是所有不同類型都提前模板實例化。
1.當變量類型相同,但是變量不同,調用Swap();模板實例化只會實例化一個,因為雖然變量不同,但類型相同,模板實例化就是將T換成具體的類型。
2.當Swap(m,a),變量是不同類型時,會發(fā)生什么??
因為在推演void Swap(T& left, T& right);時,T的類型不明確,就會發(fā)生錯誤(推演報錯),直接報錯
但如果不用模板,我們自己這樣:
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
int main()
{
int a=2;
double b=2.22;
Swap(a,b);
}
可能有人就會想:在Swap(a,b)中,會不會a和b發(fā)生飲食類型轉化呢?較小的類型轉化成較大的類型。
當然不會:隱式類型轉化只有在 賦值:b=3;(產(chǎn)生臨時變量);函數(shù)傳參的時候(產(chǎn)生臨時變量),才會發(fā)生隱式類型轉化。
函數(shù)形參是引用,當類型是引用時,我們就要小心:是否會發(fā)生權限放大?當b傳值時,中間的臨時變量具有常性(只讀),而形參是可讀可寫,權限就會放大,也是不可以通過的,除非加了const,但是加了const就無法交換了,所以這樣還是行不通的!
自動推演實例化和顯式實例化:
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.1, d2 = 20.2;
// 自動推演實例化
cout << Add(a1, a2) << endl;
cout << Add(d1, d2) << endl;
cout << Add((double)a1, d2) << endl; //強制類型轉化也是產(chǎn)生臨時變量,不是改變a1
cout << Add(a1, (int)d2) << endl;
// 顯示實例化
cout << Add<double>(a1, d2) << endl;//隱式類型轉化
cout << Add<int>(a1, d2) << endl;
return 0;
}
在自動推演實例化中,必須強轉,不然還是和之前問題一樣,該語句不能通過編譯,因為在編譯期間,當編譯器看到該實例化時,需要推演其實參類型通過實參a1將T推演為int,通過實參d1將T推演為double類型,但模板參數(shù)列表中只有一個T,編譯器無法確定此處到底該將T確定為int 或者 double類型而報錯。 (推演報錯)
不強轉情況:顯示實例化,:在函數(shù)名后的<>中指定模板參數(shù)的實際類型(我讓你怎么來你就怎么來!)
在函數(shù)名后加入了指定模板參數(shù)后,就會在實例化時,T直接是指定的類型,這樣就會發(fā)生隱式類型轉換。
注意:在模板中,編譯器一般不會進行類型轉換操作,因為一旦轉化出問題,編譯器就需要背黑鍋 Add(a1, d1); 此時有兩種處理方式:1. 用戶自己來強制轉化 2. 使用顯式實例化
2.多參數(shù)類型
template<typename T1,typename T2),T1,T2為不同類型,當然參數(shù)個數(shù)大于等于2
template<class t1,class t2>
t1 Add(const t1& left, const t2& right)
{
return left + right;
}
int main()
{
int a = 1, b = 2;
double m = 2.22, n = 3.33;
cout << Add(a, b) << endl;
cout << Add(m, n) << endl;
cout << Add(a, m) << endl;
cout << Add(n, b) << endl;
}
此時,當Add(不同類型時),就不會發(fā)生推演錯誤,你是什么類型就會推演成什么模板函數(shù)。
3.模板函數(shù)和自定義函數(shù)
當模板函數(shù)和自己實現(xiàn)的函數(shù)是否可以同時存在時?
//專門處理int的加法函數(shù)
int Add(int left, int right)
{
return left + right;
}
// 通用加法函數(shù)
template<class T>
T Add(T left, T right)
{
return left + right;
}
int main()
{
int a = 1, b = 2;
Add(a, b);
Add<int>(a, b);
return 0;
}
當自己寫的函數(shù)和模板函數(shù)同時存在時,二者不會沖突,在之前我們講過他們的函數(shù)名修飾規(guī)則是不同的。
同時存在,且調用時,首先會調用自己寫的函數(shù)。因為模板函數(shù)相當于一個半成品,他需要推演實例化才會生成具體的函數(shù),所以當然先使用自己實現(xiàn)的。
如果一定要使用模板函數(shù)的話,就需要顯示實例化:Add<int>(a,b);
這就叫泛型編程,與具體的類型無關!
2.類模板
類模板與函數(shù)模板不同的是:類模板統(tǒng)一顯式實例化,不需要推演,或者說沒有推演的時機,而函數(shù)模板實參傳遞形參時,就會發(fā)生推演實例化。
格式:
template<typename T>
class Stack
{
public:
Stack(int capacity = 4)
{
_a = (T*)malloc(sizeof(T)*capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
_top = 0;
_capacity = capacity;
}
......
private:
T* _a;
int _top;
int _capacity;
};
int main()
{
// 顯示實例化
Stack<double> st1; // double
st1.Push(1.1);
Stack<int> st2; // int
st2.Push(1);
// s1,s2是同一個類模板實例化出來的,但是模板參數(shù)不同,他們就是不同類型
return 0;
}
可能有人會問:s1=s2; 會不會發(fā)生隱式類型轉換呢?當然不會,隱式類型轉換只有在類型相近才會發(fā)生。
接下來創(chuàng)建一個數(shù)組類模板:
namespace mj
{
template<class T>
class array
{
public:
inline T& operator[](size_t i) //這里引用做返回值的目的是除了減少拷貝構造,還有可以修改返回值,直接可以修改數(shù)組里的值
{
assert(i < N); //嚴格控制越界訪問的情況
return _a[i];
}
private:
T _a[N];
};
}
int main()
{
mj::array<int> a1;
for (size_t i = 0; i < N; ++i)
{
//相當于: a1.operator[](i)= i;
a1[i] = i;
}
for (size_t i = 0; i < N; ++i)
{
// a1.operator[](i)
cout << a1[i] << " ";
}
cout << endl;
for (size_t i = 0; i < N; ++i)
{
a1[i]++;
}
for (size_t i = 0; i < N; ++i)
{
cout << a1[i] << " ";
}
cout << endl;
return 0;
}
我們可以發(fā)現(xiàn),類對象居然也可以使用數(shù)組那一套了??當然是取決于運算符重載。
他與普通數(shù)組最大的區(qū)別是:
1. 普通數(shù)組對于數(shù)組越界的這種情況,只能隨機的抽查!而我們自己實現(xiàn)的類模板可以嚴格的控制越界訪問這種情況!別說越界修改,越界訪問都不行!
2.效率上因為[]是運算符重載,使用就會調用函數(shù)開辟棧幀,但是若定義到類中,并且加inline,就對于效率來說,那真是完美!
3.模板不支持分離編譯
我們在實現(xiàn)數(shù)據(jù)結構的時候,是不是會經(jīng)常去分幾個文件去實現(xiàn)不同模塊的功能?
(個人習慣).h文件中,我們寫聲明;.cpp文件中,我們寫定義;test.cpp中,我們測試使用
但今天學習的模板就不能這么做了!!具體不能怎么做,我們上代碼:
如果這樣寫的話,他就會報鏈接錯誤(就是在使用時找不到定義)
我們知道,在預處理階段,就會將.h頭文件展開,test.cpp中只有聲明,在調用函數(shù)時,就會去找他的地址(call stack()),那么在編譯的時候,編譯器允許只有聲明沒有函數(shù),相當于你可以先給他一個承諾,兌不兌現(xiàn)后面再說。
但在鏈接的時候,test.cpp中,卻不能找到它的地址,這是為什么??這就是模板和其他的區(qū)別!
鏈接錯誤原因:
.cpp中的定義,不是實例化模板,他只是一個模板,沒有任何實例化成任何類型。所以你在使用類模板的時候,壓根就找不到它的定義,當然也找不到地址了,這不就鏈接錯誤了嗎?
看上圖:stack<int> st; 顯示實例化,但是.h中只有聲明,test.cpp用的地方實例化了,但是定義的地方stack.cpp卻沒有實例化,只是一個模板。
用的地方在實例化,但是有聲明,沒有定義;
定義的地方?jīng)]有實例化。
解決方法:
那轉來轉去就是一個問題:stack.cpp中定義沒有實例化!!
辦法一:
你沒有實例化,我給你補上:在定義后面加一個實例化
template<class T>
Stack<T>::Stack(int capacity = 4)
{
cout << "Stack(int capacity = )" << capacity << endl;
_a = (T*)malloc(sizeof(T)*capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
_top = 0;
_capacity = capacity;
template
class Stack<int>;
但是就會有另一個問題,我如果使用的時候,創(chuàng)建不同類型,那模板實例化就要有不同類型,那就要一直補實例化,總不肯用一個補一個吧。
方法二:
那就是模板的編譯不分離:(不要將定義和聲明一個到.cpp,一個到.h)
當放在一個文件中時,在編譯時,.h 文件展開后,定義和聲明都在test.cpp中,那直接就會完成模板實例化,就有了函數(shù)地址,不需要再去鏈接了。
鏈接:只有聲明沒有定義才會到處去找定義。
那有人就會問,加inline可以嗎?
inline當然不可以,加了inline后,直接不產(chǎn)生符號表,還存在什么地址嗎?
直接放類中也不行,當數(shù)據(jù)量大的時候,都擠到一推,代碼閱讀性很差,會傻傻搞不清!
原文鏈接:https://blog.csdn.net/ChaoFreeandeasy_/article/details/127603684
相關推薦
- 2022-04-01 PyPy?如何讓Python代碼運行得和C一樣快_python
- 2022-04-09 Python調用win10toast框架實現(xiàn)定時調起系統(tǒng)通知_python
- 2022-11-07 如何使用python生成大量數(shù)據(jù)寫入es數(shù)據(jù)庫并查詢操作_python
- 2022-04-07 C++?string與int的相互轉換(使用C++11)_C 語言
- 2022-08-26 利用Python實現(xiàn)自動化監(jiān)控文件夾完成服務部署_python
- 2022-12-07 C++?兩個vector對象拼接方式_C 語言
- 2022-03-13 在Linux系統(tǒng)中安裝Docker的過程_docker
- 2024-01-09 IDEA錯誤: 找不到或無法加載主類 com.atguigu.springcloud.EurekaS
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細win安裝深度學習環(huán)境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結構-簡單動態(tài)字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支