泛型编程
编写与类型无关的代码,是代码复用的一种手段,模板是泛型编程的基础。
模板分类
1.模板函数
代表了一个函数家族,该函数无关类型,在使用时才根据实参类型得到特定的函数。
#ifndef __TEST_H__
#define __TEST_H__
#include<iostream>
#include<stdlib.h>
using namespace std;
#endif
#define _CRT_SECURE_NO_WARNINGS 1
#include "test.h"
template<typename T>
T Add(T left, T right)
{
return left + right;
}
int main()
{
cout << Add(10, 20) << endl;
cout << Add(10.1, 20.0) << endl;
cout << Add(10, (int)20.0) << endl;
cout << Add<int>(10, 20.0) << endl;
system("pause");
return 0;
}
在主函数main()里面调用Add函数会发现编译器可以根据传参的具体类型合成需要的具体函数,这个过程具体解释叫做模板函数的实例化过程,
int Add(int left, int right)
{
return left + right;
}
但如果类似于这样把具体函数直接给出来那
cout << Add(10, 20) << endl;
这个调用将会直接用上面给出来的这个函数而不是用模板再去实例化一个函数出来。
关于上述代码的具体解释
第一个调用将会实例化出的函数是
int Add(int left, int right)
{
return left + right;
}
第二个实例化出的函数
double Add(double left, double right)
{
return left + right;
}
第三个
int Add(int left, int right)
{
return left + right;
}
这里编译器会提示数据从double转到int可能会有丢失,是因为将浮点类型数据强制类型转换成整型数据时小数部分会被直接舍弃。
第四个
int Add(int left, int right)
{
return left + right;
}
同样也是
需要注意的是:模板被编译了两次
实例化之前检查模板代码是否正确无误。
实例化期间检查模板代码看是否所有的调用都有效。
实参推演
从函数实参确定模板形参类型和值的过程叫做实参推演。
多个类型形参的实参必须完全匹配。
如果像这样传参,编译器将无法真正确定模板参数T的类型,也无法实例化出需要的函数。
template<typename T, typename T>
T Add(T left, T right)
{
return left + right;
}
这样属于重定义模板参数T,也就是说模板形参的名字在同一模板形参列表中只能用一次。
template<typename T V>
T Add(T left, T right)
{
return left + right;
}
所有的模板形参类型前面都必须加上关键字typename或者class,一般用typename比较直观。上面代码中的V前面没有关键字修饰,这种用法是错误的。
非模板类型参数
template<typename T,int N>
void FunTest(T(&arr)[N])
{
int i = 0;
for (i = 0; i < N; i++)
{
arr[i] = 0;
}
}
int main()
{
int _arr[5];
FunTest(int (&_arr)[5]);
system("pause");
return 0;
}
在模板内部定义常量,当需要常量表达式的时候就可以用非模板类型参数。在上面代码中数组的长度就是一个常量,可以在模板定义一个常量参数来表示这个数组的长度。
模板形参说明
1、模板形参表使用<>括起来。
2、和函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同。
3、定义模板函数时模板形参表不能为空。
4、模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typename后。
5、模板类型形参可作为类型说明符用在模板中的任何地方,与内置类型或自定义类型使用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换。
6、模板形参表中,class和typename具有相同的含义,可以互换,使用typename更加直观。但关键字typename是作为C++标准加入到C++中的,旧的编译器可能不支持。
模板函数重载
int Max(const int& left, const int & right)
{
return left>right? left:right;
}
template<typename T>
T Max(const T& left, const T& right)
{
return left>right? left:right;
}
template<typename T>
T Max(const T& a, const T& b, const T& c)
{
return Max(Max(a, b), c);
};
int main()
{
Max(10, 20, 30);
Max<>(10, 20);
Max(10, 20);
Max(10, 20.12);
Max<int>(10.0, 20.0);
return 0;
}
注意:函数的所有重载版本的声明都应该位于该函数被调用位置之前。
【说明】1、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数。
2、对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调动非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板。
3、显式指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,而且所有的模板参数都应该根据实参演绎出来。
4、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
模板函数的特化
有的时候实例化的代码并不一定是最正确的,也会有一些错误产生,
那就需要定义一个特化的模板函数来完成这些可能发生错误的函数调用;
特化必须在头文件中进行声明,而且使用该特化的源文件中都应该包含该头文件。
特化形式:
template<>
返回值 函数名(形参列表)
{
函数体;
}
特化必须与特定的模板相匹配,否则只是定义了一个普通函数。