該篇主要內(nèi)容為介紹函數(shù)模板以及函數(shù)模板的基本用法。
系統(tǒng):Windows11
IDE:Visual Studio2022
項目名字:funcTemp
舉例說明:編程過程中,數(shù)據(jù)類型千奇百怪,僅表示數(shù)字的常見的就有int,float,double等多種類型。這些數(shù)字在數(shù)學(xué)中,都可以進(jìn)行相加,相減,相乘,相除,多次方運算等等多種運算模式。但是在計算機中,并不能一概而論,每種不同的數(shù)據(jù)類型,計算機都很較真的做了區(qū)分,int和int進(jìn)行計算,double和double進(jìn)行計算。拿減法來說,就會產(chǎn)生以下的代碼。
int Sub(int a, int b)
{
return a - b;
}
double Sub(double a, double b)
{
return a - b;
}
如上面代碼所示,一個減法,卻要寫出兩個函數(shù),如果是更復(fù)雜的運算,函數(shù)內(nèi)容更多,幾十行甚至上百行代碼,達(dá)成同一個目的,僅僅因為類型不同,就需要重寫一遍函數(shù),就會造成代碼冗余,不好看,使得工程變得非常龐大。為了解決這個問題所以我們需要模板。
接著上面的例子,減法運算,我們可以用一個函數(shù)解決
template <typename T>
T Sub(T a, T b)
{
return a - b;
}
調(diào)用時,類型T會被編譯器自動的,識別為所給參數(shù)的類型。
調(diào)用舉例
int y = Sub(5, 3);
調(diào)用驗證,使用VS自帶的工具,搜索Developer Command Prompt for vs2022,打開后進(jìn)入命令行,進(jìn)入到工程編譯的文件夾路徑下。會生成一個funcTemp.obj的文件。使用dumpbin命令來驗證調(diào)用過程中,編譯器自動識別類型T的正確性。
dumpbin /all funcTemp.obj > funcTemp.txt
就會在該文件夾下生成一個funcTemp.txt文件。查看funcTemp.txt文件。搜索函數(shù)名,就可以看到,sub()函數(shù),自動的把T類型識別為了int類型
COMDAT; sym= "int __cdecl Sub<int>(int,int)" (??$Sub@H@@YAHHH@Z)
1.template :表示函數(shù)模板的開始,是C++中的關(guān)鍵字
2.typename:typename修飾的是類型T,語法規(guī)定的寫法。也可以用class代替,不過我的習(xí)慣是函數(shù)模板中用typename,在類模板中用class。
3.T:T表示一個類型,可以是任意的,也可以是X,Y,L等等
4.函數(shù)體中,與普通函數(shù)類似,只需要主要如果是T類型的,就一定寫T就好。
template <typename T>
T Sub(T a, T b)
{
return a - b;
}
上面介紹的內(nèi)容中,T要么是指代了int要么是指代了double,如果是int-double呢,或者int+double+int呢。所以函數(shù)模板也可以“傳入多個T類型”。簡單的例子如下。
template <typename T,typename U,typename K>
T Sub(T a, U b, K c)
{
return a - b - c;
}
普通函數(shù)可以重載,只要參數(shù)數(shù)量不同,參數(shù)類型不同,即可完成重載。與函數(shù)重載非常類似。下面用代碼說明。
template<typename T>
void myFunc(T a)
{
std::cout << "myFunc(T a) is done" << std::endl;
}
template<typename T>
void myFunc(T* a)
{
std::cout << "myFunc(T* a) is done" << std::endl;
}
上面模板函數(shù)完成了重載,第一個傳入的是T類型的參數(shù),第二個傳入的是T*類型參數(shù)。參數(shù)類型不同,就是一個重載的(模板)函數(shù)。
模板函數(shù)屬于是泛化,具有不確定性,廣泛性的模式,與之相反的,全特化,就是確定的,指定的模式。
寫一個模板函數(shù)
template<typename T,typename U>
void myFunc(T* a , U& b)
{
std::cout << "myFunc(T* a , U& b) is done" << std::endl;
}
這里的T與U都是不確定的,在調(diào)用的時候,編譯器自動編譯確定類型。
下面寫一個全特化版本
template<>
void myFunc(char* a , int& b)
{
std::cout << "myFunc(char* a , int& b) is done" << std::endl;
}
這里注意看,<>里面是空的!,函數(shù)體里確定了,第一個參數(shù)是char*類型,第二個參數(shù)是int& 類型。但是要知道的是,看起來挺像重載,但記住,全特化不等于重載。全特化只是實例化了一個函數(shù)模板。
在模板函數(shù)中,偏特化并不方便實現(xiàn),只能通過重載的方式來實現(xiàn),但是很少會在實際開發(fā)中使用到,這里不過多說明(真的用不到,類里面才會比較方便使用)。
注意看以下代碼。
template <typename T,typename U,typename V>
V Add(T a, U b)
{
return a + b;
}
int main()
{
std::cout << Add(2 , 3.5) << std::endl;
return 0;
}
編譯時會報錯,原因是返回類型V并不能被編譯器推斷出來。改進(jìn)的方法有兩種,我比較傾向于第二種。
方法一:指定返回值類型,并且將返回值類型放到第一位,注意對比上面的代碼,V被放到了第一位,調(diào)用的時候也顯式的指出了V的類型。
template <typename V,typename T,typename U>
V Add(T a, U b)
{
return a + b;
}
int main()
{
std::cout << Add<double>(2 , 3.5) << std::endl;
return 0;
}
方法二:使用auto關(guān)鍵字。這樣返回值類型就不用管傳入的類型,讓編譯器自己去推斷。會更加方便,我比較傾向于這種方式。
template <typename T,typename U>
auto Add(T a, U b)
{
return a + b;
}
int main()
{
std::cout << Add(2 , 3.5) << std::endl;
return 0;
}
在一些開發(fā)過程中,傳入的參數(shù)可能不是我們想要的參數(shù),總會多一些煩人的小數(shù)點,我們可以用隱式轉(zhuǎn)換,改變傳入的參數(shù)類型,下面函數(shù),傳入的類型為double類型,我們不想要2.3,我們只要2,就必須顯式的指出傳入類型。
template < typename U>
auto MySquare(U a)
{
return a * a;
}
int main()
{
std::cout << MySquare(2.3) << std::endl;
std::cout << MySquare<int>(2.3) << std::endl;
return 0;
}
一個程序中,可以有函數(shù)模板,可以特化,可以有功能相同的普通函數(shù),那么編譯器會優(yōu)先調(diào)用哪一個類型的函數(shù)呢?
//模板函數(shù)
template <typename T>
void Temp(T a)
{
std::cout << "temp is run" << std::endl;
}
//全特化
template <>
void Temp(int a)
{
std::cout << "spacial temp is run" << std::endl;
}
//功能相同的普通函數(shù)
void Temp(int a)
{
std::cout << "general func is run" << std::endl;
}
int main()
{
Temp(8);
return 0;
}
看結(jié)果,是普通函數(shù)被調(diào)用了,我們把普通函數(shù)屏蔽了之后,是特化的模板函數(shù)被調(diào)用了。所以可以得出結(jié)論,函數(shù)調(diào)用的順序是普通函數(shù) > 特化的模板函數(shù) > 泛化的模板函數(shù)
模板函數(shù)的基礎(chǔ)使用方法,基本上就這么多了,如果有錯誤歡迎指正。這是一些看王建偉老師編寫的《C++新經(jīng)典 模板與泛型編程》*一書過程中的理解。歡迎交流。
快搜