C++—模板(1)模板与函数模板

1、引入

如何编写一个通用加法函数?
第一个方法是使用函数重载, 针对每个所需相同行为的不同类型重新实现这个函数。C++的这种编程机制给编程者极大的方便,不需要为功能相似、参数不同的函数选用不同的函数名,也增强了程序的可读性。简单示例:

1 int Add(const int &_iLeft, const int &_iRight)
2 {
3 return (_iLeft + _iRight) ;
4 }f
5 loat Add(const float &_fLeft, const float &_fRight)
6 {
7 return (_fLeft + _fRight) ;
8 }

【 缺点】
1、 只要有新类型出现, 就要重新添加对应函数。
2、 除类型外, 所有函数的函数体都相同, 代码的复用率不高
3、 如果函数只 是返回值类型不同, 函数重载不能解决
4、 一个方法有问题, 所有的方法都有问题, 不好维护。
还有一个方法是使用公共基类, 将通用的代码放在公共的基础类里面,让需要这部分功能的类去继承它。但是这也有【 缺点】:
1、 借助公共基类来编写通用代码, 将失去类型检查的优点;
2、 对于以后实现的许多类, 都必须继承自 某个特定的基类, 代码维护更加困难。
此外还可以使用特殊的预处理程序

1 #define ADD(a, b) ((a) + (b) )

【 缺点】不是函数, 不进行参数类型检测, 安全性不高
还有什么 办法吗?
-----------泛型编程
泛型编程: 编写与类型无关的逻辑代码, 是代码复用的一种手段。 模板是泛型编程的基础。
泛型编程最初诞生于C++中,由Alexander Stepanov[2]和David Musser[3]创立。目的是为了实现C++的STL(标准模版库)。其语言支持机制就是模板(Templates)。模板的精神其实很简单:参数化类型。换句话说,把一个原本特定于某个类型的算法或类当中的类型信息抽掉,抽出来做成模板参数T。

模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、返回值取得任意类型。

  模板是一种对类型进行参数化的工具;通常有两种形式:函数模板和类模板;函数模板针对仅参数类型不同的函数;类模板针对仅数据成员和成员函数类型不同的类。使用模板的目的就是能够让程序员编写与类型无关的代码。比如编写了一个交换两个整型int 类型的swap函数,这个函数就只能实现int 型,对double,字符这些类型无法实现,要实现这些类型的交换就要重新编写另一个swap函数。使用模板的目的就是要让这程序的实现与类型无关,比如一个swap模板函数,即可以实现int 型,又可以实现double型的交换。模板可以应用于函数和类。下面分别介绍。

注意:模板的声明或定义只能在全局,命名空间或类范围内进行。即不能在局部范围,函数内进行,比如不能在main函数中声明或定义一个模板。

2、函数模板

函数模板: 代表了 一个函数家族, 该函数与类型无关, 在使用时被参数化, 根据实参类型产生函数的特定类型版本。
模板函数的一般格式
template<typename Param1, typename Param2, . . . , class Paramn>

返回值类型 函数名 (参数列表)

{(函数体) . . . }
这里的template和typename都是关键字,typename是用来定义模板参数关键字, 也可以使用class,在这里typename 和class没区别。 但是建议尽量使用typename。注意: 不能使用 struct代替typename。<>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。一但声明了模板函数就可以用模板函数的形参名声明类中的成员变量和成员函数,即可以在该函数中使用内置类型的地方都可以使用模板形参名。模板形参需要调用该模板函数时提供的模板实参来初始化模板形参,一旦编译器确定了实际的模板实参类型就称他实例化了函数模板的一个实例。模板是一个蓝图, 它本身不是类或者函数, 编译器用模板产生指定的类或者函数的特定类型版本, 产生模板特定类型的过程称为函数模板实例化。

 1 template <class T>
 2 T Add(T left, T right) {
 3     return left + right;
 4 }
 5 int main() {
 6     cout << Add(20, 30) << endl;
 7     cout << Add(20.12, 30.54) << endl;
 8     cout << Add(20, (int)30.54) << endl;
 9     cout << Add<int>(20, '1') << endl;
10     getchar();
11     return 0;
12 }

注意: 模板被编译了两次:
实例化之前,检查模板代码本身,查看是否出现语法错误, 如:遗漏分号;
在实例化期间,检查模板代码,查看是否所有的调用都有效, 如:实例化类型不支持某些函数调用。
【 实参推演】从函数实参确定模板形参类型和值的过程称为模板实参推断多个类型形参的实参必须完全匹配。

【 类型形参转换】一般不会转换实参以 匹配已有的实例化, 相反会产生新的实例。 编译器只 会执行两种转换: 1、 const转换: 接收const引 用或者const指针的函数可以分别用非const对象的引 用或者指针来调用 2、 数组或函数到指针的转换: 如果模板形参不是引 用类型, 则对数组或函数类型的实参应用常规指 针转换。 数组实参将当做指向其第一个元素的指针, 函数实参当做指向函数类型的指针。

2、1 模板参数

函数模板有两种类型参数: 模板参数和调用参数

模板形参名字只能在模板形参之后到模板声明或定义的末尾之间使用 , 遵循名字屏蔽规则

 1 typedef int T;
 2 template<class T>
 3 void FunTest(T t)
 4 {
 5     cout << "t Type = " << typeid(t).name() << endl; 7 }
 8 T gloab;
 9 int main() {
10     FunTest(10);
11     cout << "gloab Type = " << typeid(gloab).name() << endl;
12     return 0;
13 }

模板形参的名字在同一模板形参列表中只能使用 一次

所有模板形参前面必须加上class或者typename关键字修饰

注意: 在函 数模板的内 部不能指定缺省的模板实参。
下面模板函数声明有问题吗?

1 template<class T, U, typename V>
2 void f1(T, U, V) ;
3 template<class T>
4 T f2(int &T) ;
5 template<class T> 6 T f3 (T, T) ;
7 typedef int TYPENAME; 8 template<typename TYPENAME> 9 TYPENAME f4(TYPENAME) ;

 2、2非模板类型参数

非模板类型形参是模板内 部定义的常量, 在需要常量表达式的时候, 可以使用非模板类型参数。例如数组长度:

 1 template<typename T,int N>
 2 void FunTest(T(&_array)[N])
 3 {
 4     for (int indx = 0; indx < N; ++indx) {
 5         _array[indx] = 0;
 6     }
 7 }
 8 int main() {
 9     int a[5];
10     float b[5];
11     FunTest(a);
12     FunTest(b);
13     getchar();
14     return 0;
15 }

1 //类型等价性
2 const int iByteCnt = 9;
3 int b[iByteCnt+1] ;
4 int a[10] ;
5 FunTest(a) ; // FunTest<int, 10> 两个数组等价
6 FunTest(b) ; // FunTest<int, 10> 编译器不会合成新的函数

模板形参说明:
1、 模板形参表使用<>括起来;
2、 和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同;
3、 定义模板函数时模板形参表不能为空;
4、 模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后;
5、 模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换;
6、 模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。

2、3模板函数重载

  template<typename T>
1
int Max(const int& left, const int & right) 2 { 3 return left>right? left: right; 4 } 5 template<typename T> 6 T Max(const T& left, const T& right) 7 { 8 return left>right? left: right; 9 } 10 template<typename T> 11 T Max(const T& a, const T& b, const T& c) 12 { 13 return Max(Max(a, b) , c) ; 14 } ; 15 int main() 16 { 17 Max(10, 20, 30) ; 18 Max<>(10, 20) ; 19 Max(10, 20) ; 20 Max(10, 20. 12) ; 21 Max<int>(10. 0, 20. 0) ; 22 Max(10. 0, 20. 0) ; 23 return 0; 24 }

注意: 函数的所有重载版本的声明都应该位于该函数被调用位置之前。
【 说明】
1、 一个非模板函数可以和一个同名 的函数模板同时存在, 而且该函数模板还可以被实例化为这个非模板函数。
2、 对于非模板函数和同名 函数模板, 如果其他条件都相同, 在调动时会优先调动非模板函数而不会从该模板产生出一个实例。 如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
3、 显式指定一个空的模板实参列表, 该语法告诉编译器只 有模板才能来匹配这个调用,而且所有的模板参数都应该根据实参演绎出来。
4、 模板函数不允许自 动类型转换, 但普通函数可以进行自 动类型转换。

2、4模板函数特化

有时候并不总是能够写出对所有可能被实例化的类型都最合适的模板,在某些情况下, 通用模板定义对于某个类型可能是完全错误的, 或者不能编译, 或者做一些错误的事情。

 1 template<typename T>
 2 int compare(T t1, T t2) {
 3     if (t1 < t2)
 4         return -1;
 5     if (t1 > t2)
 6         return 1;
 7     return 0;
 8 }
 9 int main() {
10     char *pStr1 = "abcd";
11     char *pStr2 = "1234";
12     cout << compare(pStr1, pStr2) << endl;
13     getchar();
14     return 0;
15 }

输出结果是-1;但是当我们定义如下:

1 char *pStr2 = "1234";
2 char *pStr1 = "abcd";

其他全部不变时,输出结果依然是-1。说明这之中存在问题。分析如下:

因为直接将两个指针变量的地址传递给函数模板,在比较时直接比较的是两个地址的大小,而没有比较指针内容,所以返回值一直是-1。显然这个函数满足不了我们的需求了,它支持常见int, float等类型的数据的比较,但是不支持char*(string)类型。所以我们必须对其进行特化,以让它支持两个字符串的比较。所谓特化,就是将泛型的东东搞得具体化一些,从字面上来解释,就是为已有的模板参数进行一些使其特殊化的指定,使得以前不受任何约束的模板参数,或受到特定的修饰(例如const或者摇身一变成为了指针之类的东东,甚至是经过别的模板类包装之后的模板类型)或完全被指定了下来。

我们可以下面这样来定义

1 template<>
2 int compare<const char*>(const char* const p1, const char* const p2)
3 {
4     return strcmp(p1, p2);
5 }

也可以这样定义:

1 template < >
2 int compare(const char* left, const char* right)
3 {
4     return strcmp(p1, p2);
5 }

模板函数特化形式如下:

1、 关键字template后面接一对空的尖括号<>;
2、 函数名 后接模板名 和一对尖括号, 尖括号中指定这个特化定义的模板形参;
3、 函数形参表;
4、 函数体;

template<>
返回值 函数名 <Type>(参数列表)
{
// 函数体
}
特化的声明必须与特定的模板相匹配, 否则

注意: 在模板特化版本的调用中 , 实参类型必须与特化版本函数的形参类型完全匹配,如果不匹配, 编译器将为实参模板定义中实例化一个实例。函数模版的特化,当函数调用发现有特化后的匹配函数时,会优先调用特化的函数,而不再通过函数模版来进行实例化。

 1 template<typename T>
 2 int compare(T t1, T t2) {
 3     cout << "in template<class T>..." << endl;
 4     if (t1 < t2)
 5         return -1;
 6     if (t1 > t2)
 7         return 1;
 8     return 0;
 9 }
10 //  这个是一个特化的函数模版
11 template<>
12 int compare<const char*>(const char* const p1, const char* const p2)
13 {
14     cout << "in special template< >..." << endl;
15     return strcmp(p1, p2);
16 }
17 //  特化的函数模版, 两个特化的模版本质相同, 因此编译器会报错
18 // error: redefinition of 'int compare(T, T) [with T = const char*]'|
19 //  这个其实本质是函数重载
20 /*template < >
21 int compare(const char* left, const char* right)
22 {
23     std::cout << "in special template< >..." << std::endl;
24 
25     return strcmp(left, right);
26 }*/
27 int main() {
28     const char *pStr1 = "abcd";
29     const char *pStr2 = "1234";
30     char *pStr3 = "abcd";
31     char *pStr4 = "1234";
32     cout << compare(pStr1, pStr2) << endl;
33     cout << compare(pStr3, pStr4) << endl;
34     getchar();
35     return 0;
36 }

输出结果:分析如下:

注意: 特化不能出 现在模板实例的调用 之后, 应该在头文件中 包含模板特化的声明 , 然后使用 该特化版本的每个源文件包含该头文件。

 

posted @ 2017-04-29 19:09  滴巴戈  阅读(1652)  评论(0编辑  收藏  举报