函数模板实例
值得简要介绍一下如何在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()的运算符,编译器负责其余的操作!