函数模板
函数模板由一个包含模板参数的头文件(其语法已在 上文介绍过)和一个函数定义组成,在函数定义中,模板参数表示任意类型。
作为第一个例子,假设有一个 Swap 函数用于交换两个数组元素 (TemplatesSorting.mq5)。模板参数 T 用作输入数组变量的类型以及局部变量 temp 的类型。
template<typename T>
|
函数主体中的所有语句和表达式都必须适用于实数类型,模板随后将针对这些实数类型进行实例化。在本例中,使用了赋值运算符 '='。虽然它对于内置类型始终存在,但对于用户定义类型,可能需要显式重载。
编译器默认为类和结构体生成拷贝运算符的实现,但用户可以隐式或显式地删除(参见 delete关键字)。具体来说,正如我们在 对象类型转换一节中看到的,类中存在常量字段会导致编译器移除其隐式拷贝选项。因此,上述 Swap 模板函数不能用于此类的对象:编译器会报错。
对于 Swap 函数使用的类/结构体,最好不仅包含赋值运算符,还包含拷贝构造函数,因为 temp 变量的声明实际上是一个带有初始化的构造,而不是赋值。使用拷贝构造函数时,函数的第一行会一次性执行(temp 是基于 array[i] 创建的);如果没有拷贝构造函数,则会先调用默认构造函数,然后对 temp 执行运算符 '='。
我们来看看如何在快速排序算法中使用 Swap 模板函数:另一个 QuickSort 模板函数实现了该算法。
template<typename T>
|
请注意,QuickSort 模板的 T 参数指定了输入参数 array 的类型,然后该数组会被传递给 Swap 模板。因此,QuickSort 模板的类型推断 T 将自动确定 Swap 模板的类型 T。
内置函数 ArraySize(与许多其他函数一样)能够处理任意类型的数组:从某种意义上说,它也是一个模板,尽管它是直接在终端中实现的。
排序是通过 if 语句中的 '>' 比较运算符完成的。如前所述,必须为任何要排序的 T 类型定义此运算符,因为它适用于 T 类型数组的元素。
我们来了解一下内置类型数组的排序是如何进行的。
void OnStart()
|
两次调用 QuickSort 模板函数会根据传入数组的类型自动推断 T 的类型。因此,我们将获得两个分别对应 double 和 string 类型的 QuickSort 实例。
为了检查自定义类型的排序,我们创建一个包含 x 整数字段的 ABC 结构体,并在构造函数中用随机数填充此结构体。在结构体中重载 '>' 运算符也很重要。
struct ABC
|
由于结构体值是随机生成的,我们会得到不同的结果,但它们始终按升序排列。
在这种情况下,也会自动推断出 T 类型。但在某些情况下,显式指定是将类型传递给函数模板的唯一方法。因此,如果模板函数必须返回唯一类型的值(与其参数的类型不同),如果没有参数,则只能显式指定。
例如,以下 createInstance 模板函数需要在调用指令中显式指定类型,因为无法根据返回值自动“计算”类型 T。如果不这样做,编译器会生成“模板不匹配”错误。
class Base
|
如果有多个模板参数,并且返回值的类型未绑定到函数的任何输入参数,则在调用时还需要指定特定类型:
template<typename T,typename U>
|
请注意,如果显式指定了模板的类型,则所有参数都需要这样做,即使第二个参数 U 可以从传递的自变量中推断出来。
编译器生成模板函数的所有实例后,将参与标准流程,从所有具有相同名称且参数数量合适的 函数重载 中选择最佳候选项。在所有重载选项(包括已创建的模板实例)中,选择类型最接近(转换次数最少)的选项。
如果模板函数包含某些特定类型的输入参数,则只有当这些类型与自变量完全匹配时,它才被视为候选:任何转换需求都会导致模板因不合适而被“丢弃”。
非模板重载优先于模板重载,越特化(“范围越狭窄”),就越容易在模板重载中胜出。
如果显式指定了模板自变量(类型),并且这些类型不同,则将对相应的函数自变量(传递的值)应用 隐式类型转换 规则(如有必要)。
如果一个函数的多个变体匹配度相同,我们将收到“对具有相同参数的重载函数的调用有歧义”错误。
例如,如果除了 MyCast 模板之外,还定义了一个将字符串转换为布尔类型的函数:
bool MyCast(const string u)
|
则调用 MyCast<double,string>("123.0") 将开始抛出指示的错误,因为这两个函数仅在返回值上有所不同:
'MyCast<double,string>' - ambiguous call to overloaded function with the same parameters
|
描述模板函数时,建议将所有模板参数包含在函数参数中。类型只能从自变量推断,而不能从返回值推断。
如果函数有一个带有默认值的模板化类型参数 T,并且在调用时省略了相应的参数,那么编译器也将无法推断 T 的类型,并抛出“无法应用模板”错误。
class Base
|