C++函数模板与模板函数

  • 格式:pdf
  • 大小:139.62 KB
  • 文档页数:6

C++函数模板与模板函数

1.函数模板的声明和模板函数的⽣成

1.1 函数模板的声明

函数模板可以⽤来创建⼀个通⽤的函数,以⽀持多种不同的形参,避免重载函数的函数体重复设计。它的最⼤特点是把函数使⽤的数据类型作为参数。

函数模板的声明形式为:template

(参数表)

{

函数体

}

其中,template是 定义模板函数的关键字;template后⾯的尖括号不能省略;typename(或class)是声明数据类型参数标识符的关键字,⽤以说明它后⾯的标 识符是数据类型标识符。这样,在以后定义的这个函数中,凡希望根据实参数据类型来确定数据类型的变量,都可以⽤数据类型参数标识符来说明,从⽽使这个变量 可以适应不同的数据类型。例如:template

T fuc(T x, int y)

{

T x;

//……

}

如果主调函数中有以下语句:double d;

int a;

fuc(d,a);

则系统将⽤实参d的数据类型double去代替函数模板中的T⽣成函数:double fuc(double x,int y)

{

double x;

//……

}

函数模板只是声明了⼀个函数的描述即模板,不是⼀个可以直接执⾏的函数,只有根据实际情况⽤实参的数据类型代替类型参数标识符之后,才能产⽣真正的函数。

关键字typename也可以使⽤关键字class,这时数据类型参数标识符就可以使⽤所有的C++数据类型。

1.2 模板函数的⽣成函数模板的数据类型参数标识符实际上是⼀个类型形参,在使⽤函数模板时,要将这个形参实例化为确定的数据类型。将类型形参实例化的参数称为模板实参,⽤模板实参实例化的函数称为模板函数。模板函数的⽣成就是将函数模板的类型形参实例化的过程。例如:

使⽤中应注意的⼏个问题:

⑴ 函数模板允许使⽤多个类型参数,但在template定义部分的每个形参前必须有关键字typename或class,即:template

(参数表)

{

函数体

}

⑵ 在template语句与函数模板定义语句之间不允许有别的语句。如下⾯的声明是错误的:template

int I;

T min(T x,T y)

{

函数体

}

⑶ 模板函数类似于重载函数,但两者有很⼤区别:函数重载时,每个函数体内可以执⾏不同的动作,但同⼀个函数模板实例化后的模板函数都必须执⾏相同的动作。

2 函数模板

经常有碰到函数模块的应⽤,很多书上也只是略有⼩讲⼀下,今天⼜狂碰到函数模块,⽆奈特地找来C++编程经典<>翻阅⼀遍,终于有所全⾯了解.....

2.1 问题

强类型语⾔要求我们为所有希望⽐较的类型都实现⼀个实例int min( int a, int b ) {

return a < b ? a : b;

}

double min( double a, double b ) {

return a < b ? a : b;

}

有⼀种⽅法可替代为每个min()实例都显式定义⼀个函数的⽅法这种⽅法很有吸引⼒但是也很危险.那就是⽤预处理器的宏扩展设施例如 :#define min(a,b) ((a) < (b) ? (a) : (b))

在复杂调⽤的情况下,它的⾏为是不可预期的,这是因为它的两个参数值都被计算两次. ⼀次是在a 和b 的测试中另⼀次是在宏的返回值被计算期间.#include

#define min(a,b) ((a) < (b) ? (a) : (b))

const int size = 10;

int ia[size];

int main() {int elem_cnt = 0;

int *p = &ia[0];

// 计数数组元素的个数

while ( min(p++,&ia[size]) != &ia[size] )

++elem_cnt;

cout << "elem_cnt : " << elem_cnt

<< "\texpecting: " << size << endl;

return 0;

}

执⾏该程序的结果是下⾯不正确的计算结果: elem_cnt : 5 expecting: 10min()的宏扩展在这种情况下会失败因为应⽤在指针实参p 上的后置递增操作随每次扩展⽽被应⽤了两次

2.2 解决办法

函数模板提供了⼀种机制通过它我们可以保留函数定义和函数调⽤的语义在⼀个程序位置上封装了⼀段代码确保在函数调⽤之前实参只被计算⼀次.

函数模板提供⼀个种⽤来⾃动⽣成各种类型函数实例的算法程序员对于函数接⼝参数和返回类型中的全部或者部分类型进⾏参数化(parameterize)⽽函数体保持不变.

下⾯是min()的函数模板定义template

Type min( Type a, Type b ) {

return a < b ? a : b;

}

2.3 具体操作

关键字template 总是放在模板的定义与声明的最前⾯关键字后⾯是⽤逗号分隔的模板参数表(template parameter list)它⽤尖括号<> ⼀个⼩于号和⼀个⼤于号括起来该列表是模板参数表不能为空模板参数可以是⼀个模板类型参数(template typeparameter)它代表了⼀种类型也可以是⼀个模板⾮类型参数(template nontype parameter)它代表了⼀个常量表达式模板类型参数由关键字class 或typename 后加⼀个标识符构成在函数的模板参数表中这两个关键字的意义相同。

模板⾮类型参数由⼀个普通的参数声明构成模板⾮类型参数表⽰该参数名代表了⼀个潜在的值⽽该值代表了模板定义中的⼀个常量例如size是⼀个模板⾮类型参数它代表arr 指向的数组的长度template

Type min( Type (&arr) [size] );

当函数模板min()被实例化时size 的值会被⼀个编译时刻已知的常量值代替。函数定义或声明跟在模板参数表后除了模板参数是类型指⽰符或常量值外函数模板的定义看起来与⾮模板函数的定义相同template

Type min( const Type (&r_array)[size] )

{

/* 找到数组中元素最⼩值的参数化函数 */

Type min_val = r_array[0];

for ( int i = 1; i < size; ++i )if ( r_array[i] < min_val )

min_val = r_array[i];

return min_val;

}

在程序的运⾏过程中Type 会被各种内置类型和⽤户定义的类型所代替⽽size 会被各种常量值所取代这些常量值是由实际使⽤的min()决定的记住⼀个函数的两种⽤法是调⽤它和取它的地址

当 ⼀个名字被声明为模板参数之后它就可以被使⽤了⼀直到模板声明或定义结束为⽌模板类型参数被⽤作⼀个类型指⽰符可以出现在模板定义的余下部分它的使⽤⽅式 与内置或⽤户定义的类型完全⼀样⽐如⽤来声明变量和强制类型转换模扳⾮类型参数被⽤作⼀个常量值可以出现在模板定义的余下部分它可以⽤在要求常量的地⽅或 许是在数组声明中指定数组的⼤⼩或作为枚举常量的初始值

2.4 ⼏点注意

① 如果在全局域中声明了与模板参数同名的对象函数或类型则该全局名将被隐藏在下⾯的例⼦中tmp 的类型不是double 是模板参数Type

typedef double Type;

template

Type min( Type a, Type b )

{

// tmp 类型为模板参数 Type

// 不是全局 typedef

Type tmp = a < b ? a : b;

return tmp;

}

② 在函数模板定义中声明的对象或类型不能与模板参数同名

template

Type min( Type a, Type b )

{

// 错误: 重新声明模板参数 Type

typedef double Type;

Type tmp = a < b ? a : b;

return tmp;

}

③ 模板类型参数名可以被⽤来指定函数模板的返回位

// ok: T1 表⽰ min() 的返回类型

// T2 和 T3 表⽰参数类型

template

T1 min( T2, T3 );

④ 模板参数名在同⼀模板参数表中只能被使⽤⼀次,但是模板参数名可以在多个函数模板声明或定义之间被重复使⽤// 错误: 模板参数名 Type 的⾮法重复使⽤

template

Type min( Type, Type );

// ok: 名字 Type 在不同模板之间重复使⽤

template

Type min( Type, Type );

template

Type max( Type, Type );

⑤ 如果⼀个函数模板有⼀个以上的模板类型参数则每个模板类型参数前⾯都必须有关键字class 或typename

// ok: 关键字 typename 和 class 可以混⽤

template

T minus( T*, U );

// 错误: 必须是 或

template

T sum( T*, U );

⑥ 为了分析模板定义编译器必须能够区分出是类型以及不是类型的表达式对于编译器来说它并不总是能够区分出模板定义中的哪些表达式是类型例如如果编译器在模板定义中遇到表达式Parm::name 且Parm 这个模板类型参数代表了⼀个类那么name 引⽤的是Parm 的⼀个类型成员吗.template

Parm minus( Parm* array, U value )

{

Parm::name * p; // 这是⼀个指针声明还是乘法乘法

}

编译器不知道name 是否为⼀个类型因为它只有在模板被实例化之后才能找到Parm 表⽰的类的定义为了让编译器能够分析模板定义⽤户必须指⽰编译器哪些表达式是类型表达式告诉编译器⼀个表达式是类型表达式的机制是在表达式前加上关键字typename 例如如果我们想让函数模板minus()的表达式Parm::name 是个类型名因⽽使整个表达式是⼀个指针声明我们应如下修改template

Parm minus( Parm* array, U value )

{

typename Parm::name * p; // ok: 指针声明

}

关键字typename 也可以被⽤在模板参数表中以指⽰⼀个模板参数是⼀个类型

⑦ 如同⾮模板函数⼀样函数模板也可以被声明为inline 或extern 应该把指⽰符放在模板参数表后⾯⽽不是在关键字template 前⾯

// ok: 关键字跟在模板参数表之后

template

inline