C++ 点滴积累(5)

来源:互联网 发布:网络歌曲陪我去流浪 编辑:程序博客网 时间:2024/06/05 18:55

1. 函 数 模 板

10.1 函数模板定义
有时候强类型语言对于实现相对简单的函数似乎是个障碍例如虽然下面的函数min()的算法很简单但是强类型语言要求我们为所有希望比较的类型都实现一个实例

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))
虽然该定义对于简单的min()调用都能正常工作如
min( 10, 20 );
min( 10.0, 20.0 );
但是在复杂调用下它的行为是不可预期的这是因为它的机制并不像函数调用那样工作只是简单地提供参数的替换结果是它的两个参数值都被计算两次一次是在a 和b 的测试中另一次是在宏的返回值被计算期间例如

#include <iostream>using namespace std;#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;}

这个程序给出了计算整型数组ia 的元素个数的一种明显绕弯的的方法min()的宏扩展在这种情况下会失败因为应用在指针实参p 上的后置递增操作随每次扩展而被应用了两次
执行该程序的结果是下面不正确的计算结果
elem_cnt : 5 expecting: 10

函数模板提供了一种机制通过它我们可以保留函数定义和函数调用的语义在一个程序位置上封装了一段代码确保在函数调用之前实参只被计算一次而无需像宏方案那样绕过C++的强类型检查函数模板提供一个种用来自动生成各种类型函数实例的算法程序员对于函数接口参数和返回类型中的全部或者部分类型进行参数化parameterize 而函数体保持不变如用一个函数的实现在一组实例上保持不变并且每个实例都处理一种惟一的数据类型如函数min() 则该函数就是模板的最佳候选者

例如下面是min()的函数模板定义

#include <iostream>using namespace std;template <class Type>Type min( Type a, Type b ) {return 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;}

如果用函数模板代替前面程序中的预处理器宏min() 则程序的输出是正确的
elem_cnt : 10 expecting: 10
C++标准库为一些常用的算法如这里定义的min() 提供了函数模板这些算法将在第12 章描述为了介绍函数模板我们对于C++标准库中定义的一些算法给出了相应的简化版本
关键字template 总是放在模板的定义与声明的最前面关键字后面是用逗号分隔的模板参数表template parameter list 它用尖括号<> 一个小于号和一个大于号括起来该列表是模板参数表不能为空模板参数可以是一个模板类型参数template type
parameter 它代表了一种类型也可以是一个模板非类型参数template nontype parameter
它代表了一个常量表达式
模板类型参数由关键字class 或typename 后加一个标识符构成在函数的模板参数表中
这两个关键字的意义相同它们表示后面的参数名代表一个潜在的内置或用户定义的类型
模板参数名由程序员选择在本例中我们用Type 来命名min()的模板参数但实际上可以
是任何名字譬如
template <class Glorp>
Glorp min( Glorp a, Glorp b ) {
return a < b ? a : b;
}
当模板被实例化时实际的内置或用户定义类型将替换模板的类型参数类型int double char* vector<int>或list<double>*都是有效的模板实参类型
模板非类型参数由一个普通的参数声明构成模板非类型参数表示该参数名代表了一个潜在的值而该值代表了模板定义中的一个常量例如size 是一个模板非类型参数它代表arr 指向的数组的长度
template <class Type, int size>
Type min( Type (&arr) [size] );
当函数模板min()被实例化时size 的值会被一个编译时刻已知的常量值代替
函数定义或声明跟在模板参数表后除了模板参数是类型指示符或常量值外函数模板
的定义看起来与非模板函数的定义相同我们来看一个例子
template <class Type, int size>
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 表示min()的返回类型参数r_array 的类型以及局部变量min_val
的类型size 表示r_array 引用的数组的长度在程序的运行过程中Type 会被各种内置类
型和用户定义的类型所代替而size 会被各种常量值所取代这些常量值是由实际使用的
min()决定的记住一个函数的两种用法是调用它和取它的地址类型和值的替换过程
被称为模板实例化template instantiation 我们将在下一节介绍模板实例化
我们的min()函数模板的函数参数表看起来可能有些短如7.3 节所讨论的一个数组参
数总是被作为指向数组首元素的指针来传递数组实参的第一维在函数定义内是未知的为
了缓解这个问题我们在此处把min()的参数声明为数组的引用这解决了用户必须传递第二
个实参来指定数组长度的问题但是缺点是用在不同长度的int 数组时会生成或实例化不
同的min()实例
当一个名字被声明为模板参数之后它就可以被使用了一直到模板声明或定义结束为
止模板类型参数被用作一个类型指示符可以出现在模板定义的余下部分它的使用方式
与内置或用户定义的类型完全一样比如用来声明变量和强制类型转换模扳非类型参数被
用作一个常量值可以出现在模板定义的余下部分它可以用在要求常量的地方或许是在
数组声明中指定数组的大小或作为枚举常量的初始值
// size 指定数组参数的大小并初始化一个 const int 值
template <class Type, int size>
Type min( const Type (&r_array)[size] )
{
const int loc_size = size;
Type loc_array[loc_size];
// ...
}
如果在全局域中声明了与模板参数同名的对象函数或类型则该全局名将被隐藏在
下面的例子中tmp 的类型不是double 是模板参数Type
typedef double Type;
template <class Type>
Type min( Type a, Type b )
{
// tmp 类型为模板参数 Type
// 不是全局 typedef
Type tmp = a < b ? a : b;
return tmp;
}
在函数模板定义中声明的对象或类型不能与模板参数同名
template <class Type>
Type min( Type a, Type b )
{
// 错误: 重新声明模板参数 Type
typedef double Type;
Type tmp = a < b ? a : b;
return tmp;
}
模板类型参数名可以被用来指定函数模板的返回位
409 第十章 函数模板
// ok: T1 表示 min() 的返回类型
// T2 和 T3 表示参数类型
template <class T1, class T2, class T3>
T1 min( T2, T3 );
模板参数名在同一模板参数表中只能被使用一次例如下面代码就有编译错误
// 错误: 模板参数名 Type 的非法重复使用
template <class Type, class Type>
Type min( Type, Type );
但是模板参数名可以在多个函数模板声明或定义之间被重复使用
// ok: 名字 Type 在不同模板之间重复使用
template <class Type>
Type min( Type, Type );
template <class Type>
Type max( Type, Type );
一个模板的定义和多个声明所使用的模板参数名无需相同例如下列三个min()的声明
都指向同一个函数模板
// 三个 min() 的声明都指向同一个函数模板
// 模板的前向声明
template <class T> T min( T, T );
template <class U> U min( U, U );
// 模板的真正定义
template <class Type>
Type min( Type a, Type b ) { /* ... */ }
模板参数在函数参数表中可以出现的次数没有限制在下面的例子中Type 用来表示两
个不同函数参数的类型
#include <vector>
// ok: 在模板函数的参数表中多次使用 Type
template <class Type>
Type sum( const vector<Type> &, Type );
如果一个函数模板有一个以上的模板类型参数则每个模板类型参数前面都必须有关键
字class 或typename
// ok: 关键字 typename 和 class 可以混用
template <typename T, class U>
T minus( T*, U );
// 错误: 必须是 <typename T, class U> 或 <typename T, typename U>
template <typename T, U>
T sum( T*, U );
在函数模板参数表中关键字typename 和class 的意义相同可以互换使用它们两个
都可以被用来声明同一模板参数表中的不同模板类型参数就如前面的函数模板minus()所做
的看起来好像用typename 而不是class 来指派模板类型参数更为直观毕竟关键字
typename 名字能更清楚地表明后面的名字是个类型名但是关键字typename 是最近才被

原创粉丝点击