C++基礎教程面向物件(學習筆記(66))
函式模板例項
值得簡要介紹一下如何在C ++中實現模板函式,因為未來的課程將基於其中一些概念。事實證明,C ++不直接編譯模板函式。相反,在編譯時,當編譯器遇到對模板函式的呼叫時,它會複製模板函式並用實際型別替換模板型別引數。具有實際型別的函式稱為函式模板例項。
我們來看看這個過程的一個例子。首先,我們有一個模板化的函式:
template <typename T> // 這是模板引數宣告
const T& max(const T& x, const T& y)
{
return (x > y) ? x : y;
}
編譯程式時,編譯器遇到對模板化函式的呼叫:
int i = max(3, 7); // 呼叫max(int, int)
編譯器說,“哦,我們想呼叫max(int,int)”。編譯器複製函式模板並建立模板例項max(int,int):
const int& max(const int &x, const int &y)
{
return (x > y) ? x : y;
}
現在這是一個可以編譯成機器語言的“普通函式”。
現在,讓我們稍後在您的程式碼中說,您使用不同的型別再次呼叫max():
double d = max(6.34, 18.523); //呼叫max(double, double)
C ++自動為max(double,double)建立模板例項:
const double& max(const double &x, const double &y)
{
return (x > y) ? x : y;
}
然後編譯它。
編譯器非常聰明,知道它只需要為每組唯一型別引數(每個檔案)建立一個模板例項。值得注意的是,如果您建立模板函式但不呼叫它,則不會建立模板例項。
運算子,函式呼叫和函式模板
模板函式可以使用內建型別(例如char,int,double等)和類,但有一點需要注意。當編譯器編譯模板例項時,它就像普通函式一樣編譯它。在普通函式中,必須定義與型別一起使用的任何運算子或函式呼叫,否則將出現編譯器錯誤。同樣,必須為函式模板例項化的任何型別定義模板函式中的任何運算子或函式呼叫。讓我們更詳細地看一下。
首先,我們將建立一個簡單的類:
class Cents
{
private:
int m_cents;
public:
Cents(int cents)
: m_cents(cents)
{
}
};
現在,讓我們看看當我們嘗試使用Cents類呼叫模板化的max()函式時會發生什麼:
template <typename T> // 這是模板引數宣告
const T& max(const T& x, const T& y)
{
return (x > y) ? x : y;
}
class Cents
{
private:
int m_cents;
public:
Cents(int cents)
: m_cents(cents)
{
}
};
int main()
{
Cents nickle(5);
Cents dime(10);
Cents bigger = max(nickle, dime);
return 0;
}
C ++將為max()建立一個模板例項,如下所示:
const Cents& max(const Cents &x, const Cents &y)
{
return (x > y) ? x : y;
}
然後它會嘗試編譯這個函式。看到這裡的問題?C ++不知道如何評估x > y!因此,這將產生一個相當溫和的編譯錯誤,如下所示:
1> c:\ consoleapplication1 \ main.cpp(4):錯誤C2676:二進位制’>’:'const Cents’沒有定義此運算子或轉換為預定義運算子可接受的型別 1> c:\ consoleapplication1 \ main.cpp(23):注意:參見函式模板例項化’const T&max的引用(const T&,const T&)'正在編譯中 1>用 1> [ 1> T = Cents 1>] 頂部錯誤訊息指出Cents類沒有過載operator>的事實。底部錯誤指出產生錯誤的模板化函式呼叫以及模板化引數的型別。
要解決這個問題,只需要為我們希望使用max()的任何類過載>運算子:
class Cents
{
private:
int m_cents;
public:
Cents(int cents)
: m_cents(cents)
{
}
friend bool operator>(const Cents &c1, const Cents &c2)
{
return (c1.m_cents > c2.m_cents);
}
};
現在C ++將知道如何比較x > y,x和y是Cents類的物件!因此,我們的max()函式現在將使用兩個Cents型別的物件。
另一個例子
讓我們再做一個函式模板的例子。以下函式模板將計算陣列中多個物件的平均值:
template <class T>
T average(T *array, int length)
{
T sum = 0;
for (int count=0; count < length; ++count)
sum += array[count];
sum /= length;
return sum;
}
現在讓我們看看它的實際效果:
#include <iostream>
template <class T>
T average(T *array, int length)
{
T sum = 0;
for (int count=0; count < length; ++count)
sum += array[count];
sum /= length;
return sum;
}
int main()
{
int array1[] = { 5, 3, 2, 1, 4 };
std::cout << average(array1, 5) << '\n';
double array2[] = { 3.12, 3.45, 9.23, 6.34 };
std::cout << average(array2, 4) << '\n';
return 0;
}
這會產生以下值:
3 5.535
如您所見,它適用於內建型別!
值得注意的是,因為我們的返回型別與我們的陣列元素具有相同的模板型別,所以執行整數平均將產生整數結果(刪除任何小數值)。這類似於整數除法將產生整數結果的方式。我們已經定義了以這種方式工作的東西並沒有錯,但它可能是意料之外的,所以對這個類的使用者的好評不會在這裡出錯。
現在讓我們看看當我們在Cents類上呼叫這個函式時會發生什麼:
#include <iostream>
class Cents
{
private:
int m_cents;
public:
Cents(int cents)
: m_cents(cents)
{
}
friend bool operator>(const Cents &c1, const Cents &c2)
{
return (c1.m_cents > c2.m_cents);
}
};
template <class T>
T average(T *array, int length)
{
T sum = 0;
for (int count=0; count < length; ++count)
sum += array[count];
sum /= length;
return sum;
}
int main()
{
Cents array3[] = { Cents(5), Cents(10), Cents(15), Cents(14) };
std::cout << average(array3, 4) << '\n';
return 0;
}
編譯器瘋狂併產生大量錯誤訊息!
c:\ consoleapplication1 \ main.cpp(55):錯誤C2679:二進位制’<<’:找不到帶有’Cents’型別右手運算元的運算子(或者沒有可接受的轉換) 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(497):注意:可能是’std :: basic_ostream>&std :: basic_ostream> :: operator <<(std :: basic_streambuf> *)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(477):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(const void *)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(457):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(long double)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(437):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(double)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(417):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(float)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(396):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(unsigned int64)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(376):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<( int64)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(355):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(unsigned long)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(335):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(long)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(315):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(unsigned int)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(290):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(int)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(270):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(unsigned short)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(236):注意:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(短)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(216):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(bool)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(209):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(std :: ios_base&(__ cdecl *)(std :: ios_base&))’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(202):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(std :: basic_ios>&(__ cdecl *)(std :: basic_ios>&))’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(196):note:或’std :: basic_ostream>&std :: basic_ostream> :: operator <<(std :: basic_ostream>&(__ cdecl *)(std :: basic_ostream>&))’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(694):note:或’std :: basic_ostream>&std :: operator <<>(標準:: basic_ostream>&,const char *)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(741):note:或’std :: basic_ostream>&std :: operator <<>(標準:: basic_ostream>&,char)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(779):note:或’std :: basic_ostream>&std :: operator <<>(標準:: basic_ostream>&,const char *)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(826):note:或’std :: basic_ostream>&std :: operator <<>(標準:: basic_ostream>&,char)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(952):note:或’std :: basic_ostream>&std :: operator <<>(標準:: basic_ostream>&,const簽名char *)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(959):note:或’std :: basic_ostream>&std :: operator <<>(標準:: basic_ostream>&,簽名char)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(966):note:或’std :: basic_ostream>&std :: operator <<>(標準:: basic_ostream>&,const unsigned char *)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(973):note:或’std :: basic_ostream>&std :: operator <<>(標準:: basic_ostream>&,unsigned char)’ 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(983):注意:或’std :: basic_ostream>&std :: operator <<,T>(標準:: basic_ostream> &&,const _Ty&)’ 1>用 1> [ 1> T = Cents, 1> _Ty =美分 1>] 1> c:\ program files(x86)\ microsoft visual studio 14.0 \ vc \ include \ ostream(1021):注意:或’std :: basic_ostream>&std :: operator <<>(標準:: basic_ostream>&,const std :: error_code&)’ 1> c:\ consoleapplication1 \ main.cpp(55):注意:嘗試匹配引數列表’(std :: ostream,Cents)’ 還記得我說的瘋狂錯誤資訊嗎?我們擊中了母!儘管看起來令人生畏,但這些實際上非常簡單。第一行告訴你它找不到Cents類的過載運算子<<。中間的所有行都是它試圖匹配但失敗的所有不同功能。最後一個錯誤指出了產生這一錯誤的函式呼叫。
請記住,average()返回一個Cents物件,我們嘗試使用<<運算子將該物件流式傳輸到std :: cout。但是,我們還沒有為我們的Cents類定義<<運算子。我們這樣做:
class Cents
{
private:
int m_cents;
public:
Cents(int cents)
: m_cents(cents)
{
}
friend bool operator>(const Cents &c1, const Cents &c2)
{
return (c1.m_cents > c2.m_cents);
}
friend std::ostream& operator<< (std::ostream &out, const Cents ¢s)
{
out << cents.m_cents << " cents ";
return out;
}
};
如果我們再次編譯,我們將得到另一個錯誤:
c:test.cpp(14):錯誤C2676:二進位制’+ =’:'Cents’沒有定義此運算子或轉換為預定義運算子可接受的型別 這個錯誤實際上是由我們呼叫average(Cents *,int)時建立的函式模板例項引起的。請記住,當我們呼叫模板化函式時,編譯器會“模板化”函式的副本,其中模板型別引數(佔位符型別)已被函式呼叫中的實際型別替換。當T是Cents物件時,這是average()的函式模板例項:
template <class T>
Cents average(Cents *array, int length)
{
Cents sum = 0;
for (int count=0; count < length; ++count)
sum += array[count];
sum /= length;
return sum;
}
我們收到錯誤訊息的原因是由於以下行:
sum += array[count];
在這種情況下,sum是一個Cents物件,但是我們沒有為Cents物件定義+ =運算子!我們需要定義這個函式,以便average()能夠使用Cents。展望未來,我們可以看到average()也使用了/ =運算子,因此我們將繼續並定義它:
class Cents
{
private:
int m_cents;
public:
Cents(int cents)
: m_cents(cents)
{
}
friend bool operator>(const Cents &c1, const Cents &c2)
{
return (c1.m_cents > c2.m_cents);
}
friend std::ostream& operator<< (std::ostream &out, const Cents ¢s)
{
out << cents.m_cents << " cents ";
return out;
}
Cents& operator+=(Cents cents)
{
m_cents += cents.m_cents;
return *this;
}
Cents& operator/=(int value)
{
m_cents /= value;
return *this;
}
};
最後,我們的程式碼將編譯並執行!結果如下:
11 cents
如果這看起來像很多工作,那真的只是因為我們的Cents課程開始時是如此簡單。這裡的關鍵點實際上是我們根本不需要修改average()來使它適用於Cents型別的物件(或任何其他型別)。我們只需定義用於實現Cents類的average()的運算子,編譯器負責其餘的操作!