首页 > 编程笔记
C++函数模板(入门必读)
模板是 C++ 的新特性,这个概念在C语言中是没有的。函数模板不是一个实实在在的函数,而是对逻辑功能相同、但数据类型不同的一组函数的统一描述。
例如定义一个简单的求数组中最小值的函数,要求可以处理各种数据类型。如果不使用模板,那么开发者不得不针对每种类型定义一个函数:
显然,这不是好的实现方案,比如一旦算法逻辑发生改变,或者要纠正某个错误,所有的重载函数都需要修改。
简单分析一下上述代码就会发现,所有这些函数在算法逻辑上都是相同的,不同的只是所处理数据的类型。如果能将函数的返回类型以及参数类型当做变量对待,当需要处理不同类型数据的函数时,只要将这些“类型变量”赋值为所需的类型就好了。这样,只编写一次通用算法逻辑就可以处理所有的数据类型,这就是函数模板的含义。
下面的代码使用函数模板改进了上述求最小值的 myMin() 函数:
语法格式如下:
typename 关键字也可以用 class 关键字代替,两者意义相同,都可以用来声明类型参数。但是用 typename 更好一些,可以明白地表示后面的参数是一个“类型名”。而且 typename 是 C++ 标准化的产物,而 class 关键字则是为了支持 C++ 标准化之前的程序而保留下来的。
例如,对于一个数组求最小值,可以用一个非类型的参数表示数组长度。
以上面定义好的 myMin() 模板函数为例,调用它时可以显式指定模板参数:
显式实例化的作用主要是解决模板参数推演时的二义性问题。既然指定了模板参数,那么在使用函数模板时就不必进行参数推演了,也就避免了参数推演的二义性问题。
例如,同样是比较大小,整型和浮点型数的比较就与字符串类型的比较不同。整型和浮点型数据只要调用各种比较运算符(<、>、== 等)即可,但是字符串的比较就需要调用专门的比较函数,例如 strcmp() 和 wstrcmp()。而自定义数据类型,如结构体、类,比较大小就更加特殊。在这种情况下就需要用到函数模板的特化。
例如,一个函数模板 myMax() 的定义如下:
在函数模板特化定义中,先是关键字 template 和一对尖括号“<>”,然后是函数模板特化的定义。例如,针对 const char * 类型,模板 myMax() 显式特化为:
例如定义一个简单的求数组中最小值的函数,要求可以处理各种数据类型。如果不使用模板,那么开发者不得不针对每种类型定义一个函数:
int myMin(int arr[], int n){...}; int myMin(double arr[], double n){...}; ......这里只写出了针对 int 型和 double 型数组的函数。为了适应各种情形,还应当编写针对 float, char,short,long,unsigned int 等所有数据类型的函数,甚至还要支持自定义数据类型(如结构体、类)。
显然,这不是好的实现方案,比如一旦算法逻辑发生改变,或者要纠正某个错误,所有的重载函数都需要修改。
简单分析一下上述代码就会发现,所有这些函数在算法逻辑上都是相同的,不同的只是所处理数据的类型。如果能将函数的返回类型以及参数类型当做变量对待,当需要处理不同类型数据的函数时,只要将这些“类型变量”赋值为所需的类型就好了。这样,只编写一次通用算法逻辑就可以处理所有的数据类型,这就是函数模板的含义。
下面的代码使用函数模板改进了上述求最小值的 myMin() 函数:
template<typename T> T myMin(T arr[], T n){...};其中,T 是一个参数,它可以是任意指定的类型。显然通过使用函数模板,避免了定义多余的重载函数,提高了开发效率。
函数模板的定义
函数模板的定义以 template 关键字开始,后跟模板参数列表,该列表通常包含一个或多个类型参数,这些参数用于指定函数接受哪些类型的输入。语法格式如下:
template <typename T,...> 返回值类型 函数名(T 参数1, T 参数2, ...) { // 函数体 }其中,T 是一个类型参数,可以用于函数返回类型、参数类型以及函数体内的变量类型。一个函数模板中,类型参数可以指定多个,中间用逗号分隔即可。
typename 关键字也可以用 class 关键字代替,两者意义相同,都可以用来声明类型参数。但是用 typename 更好一些,可以明白地表示后面的参数是一个“类型名”。而且 typename 是 C++ 标准化的产物,而 class 关键字则是为了支持 C++ 标准化之前的程序而保留下来的。
函数模板的使用
下面是一个简单的例子,展示了如何使用函数模板来创建一个 swap() 函数:#include <iostream> // 函数模板定义 template <typename T> void swap(T &a, T &b) { T temp = a; a = b; b = temp; } int main() { int x = 5, y = 10; std::cout << "Before swap: x = " << x << ", y = " << y << std::endl; swap(x, y); // 使用int类型的swap std::cout << "After swap: x = " << x << ", y = " << y << std::endl; double m = 5.5, n = 10.5; std::cout << "Before swap: m = " << m << ", n = " << n << std::endl; swap(m, n); // 使用double类型的swap std::cout << "After swap: m = " << m << ", n = " << n << std::endl; return 0; }输出结果为:
Before swap: x = 5, y = 10
After swap: x = 10, y = 5
Before swap: m = 5.5, n = 10.5
After swap: m = 10.5, n = 5.5
函数模板的非类型参数
在函数模板的模板参数列表中,除了放置类型参数外,还可以使用非类型参数,目的就是为函数引入一个常量,以供定义函数时使用,当然这个常量也可以作为函数参数的默认值使用。例如,对于一个数组求最小值,可以用一个非类型的参数表示数组长度。
template <typename T,int size> // 类型参数 T,非类型参数 size 表示数组长度 T myMin(T arr[]) { T minVal = 0; for (int i = 0; i < size; i++) { if (arr[i] < minVal) { minVal = arr[i]; } } return minVal; }
函数模板的显式实例化
对函数模板做显式实例化,指的就是手动指定函数模板的模板参数。以上面定义好的 myMin() 模板函数为例,调用它时可以显式指定模板参数:
unsigned int arr[5]={1,4,3,5,6}; myMin<unsigned int, 5>(arr);上述例子中,通过模板实参表 <unsigned int> 强制将函数模板 myMin() 的模板参数 T 指定为 unsigned int。
显式实例化的作用主要是解决模板参数推演时的二义性问题。既然指定了模板参数,那么在使用函数模板时就不必进行参数推演了,也就避免了参数推演的二义性问题。
函数模板的特化
很多时候,定义一个适合所有类型的函数模板非常困难,这主要是因为对于某种逻辑操作,各种数据类型的实现并不相同。例如,同样是比较大小,整型和浮点型数的比较就与字符串类型的比较不同。整型和浮点型数据只要调用各种比较运算符(<、>、== 等)即可,但是字符串的比较就需要调用专门的比较函数,例如 strcmp() 和 wstrcmp()。而自定义数据类型,如结构体、类,比较大小就更加特殊。在这种情况下就需要用到函数模板的特化。
例如,一个函数模板 myMax() 的定义如下:
template <typename T> T myMax(T t1, T t2) { return t1 > t2 ? t1 : t2; }如果用 const char* 型实参实例化 myMax() 模板,程序员的本意是比较字符串的大小,但实例化的结果却是比较两个指针的大小。为了获得正确的语义,必须针对 const char* 类型,为函数模板 myMax() 提供特化的版本。
在函数模板特化定义中,先是关键字 template 和一对尖括号“<>”,然后是函数模板特化的定义。例如,针对 const char * 类型,模板 myMax() 显式特化为:
template <> const char * myMax(const char * t1, const char * t2) { return strcmp(t1, t2) > 0? t1 : t2; }由于有了这个特化版本,当在程序中调用函数 myMax(const char*,const char*) 时,真正被调用的是特化的版本,而不是用类型 const char* 实例化的模板。