一、概述
- 所谓泛型编程是以独立于任何特定类型的方式编写代码。使用泛型编程时,我们需要提供具体程序实例所操作的类习惯或者值
- 模板是泛型编程的基础。模板是创建类或者函数的蓝图或者公式。我们给这些蓝图或者公式提供足够的信息,让这些蓝图或者公式真正的转变具体的类或者函数,这种转变发生在编译时。
- 模板支撑将类型作为参数的程序设计方式,从而实现了对泛型程序设计的支持。也就是说,C++模板机制允许在定义类、函数时将类型作为参数。
模板分为函数模板和类模板,这里介绍函数模板。
二、函数模板的定义
看如下代码
int funcadd(int i1, int i2)
{
int addhe = i1 + i2;
return addhe;
}
int funcadd(double d1, double d2)
{
double addhe = d1 + d2;
return addhe;
}
功能其实是一样只是类型不同,使用函数模板如下。
template<typename T>
T funcadd(T a, T b)
{
T addhe = a + b;
return addhe;
}
1、模板的定义是用template关键字开头,后边跟<>,<>里边叫“模板参数列表(模板实参)”,如果模板参数列表里有多个参数,则用逗号分开。<>里必须至少得有一个模板参数,模板参数前面有个 typename/class关键字(两个其中一个,class在这里不是用来定义类的,只是模板前的一个参数,所以建议用typename)。如果模板列表参数里边有多个模板参数,那你就要用多个typename/class:<typename T, typename Q>
2、模板参数列表里边表示函数定义中用到的“类型”或者“值”,也和函数参数列表类似(以上T是表示类型)。用的时候有时需要指定模板实参给它,指定的时候我们要用<>把模板实参包起来。有时又不需要我们指定模板实参,系统能根据一些信息推断出来。
3、funcadd这个函数声明了一个名字为T的类型参数。T实际是类型,编译器在编译的时候会根据funcadd的调用来确定。
三、函数模板的使用
函数模板调用和函数调用区别不大,调用的时候,编译器会根据你调用这个函数模板时的实参去推断模板参数列表里的参数(形参)的类型。->模板参数是推断出来的(有时需要提供),推断的依据调用函数的实参。有时光凭借函数实参是推断不出模板参数,这个时候就得用<>来主动提供模板参数。
#include <iostream>
using namespace std;
template<typename T>
T funcadd(T a, T b)
{
T addhe = a + b;
return addhe;
}
int main()
{
int he = funcadd(1, 3);//3和1系统认为int
cout << he << endl;
return 0;
}
4
编译器在推断出来这个模板形参类型后,就为我们实例化了一个特定版本的函数如上面就实例为如下
int funcadd(int a, int b)
{
int addhe = a + b;
return addhe;
}
当然double也可以推断出来但是double和int一起就不会推断出来
int main()
{
int he = funcadd(1, 3);//3和1系统认为int
cout << he << endl;
double he2 = funcadd(1.2, 3.1);
cout << he2 << endl;
double he3 = funcadd(3, 1.2f);//报错
return 0;
}
四、非类型模板参数
template<typename T>
因为T前面有一个typename/class,这表示T代表一个类型,是一个类型参数。
那么在模板参数列表里边,还可以定义非类型参数。 非类型参数代表的是一个值。如非类型参数s是一个整型
template<typename T, int s>
当模板被实例化时,这种非类型模板参数的值或者是用户提供的,或者是编译器推断的,都有可能,但是这些值必须都得是常量表达式,因为实例化这些模板是编译器是在编译时实例化的。
#include <iostream>
using namespace std;
template<int a, int b>
int funcadd()
{
int addhe = a + b;
return addhe;
}
int main()
{
int result = funcadd<12, 1>();//显示的指定模板参数--在尖括号中提供额外的信息
cout << result << endl;
return 0;
}
13
非类型模板参数值必须在编译的时候就能够确定,因为实例化函数模板是在编译时干的事。
看如下代码
int main()
{
int a = 12;
int result = funcadd<a, 12>();//报错
return 0;
}
再来看一下其它的使用方法
#include <iostream>
using namespace std;
template<typename T,int a, int b>
int funcadd(T c)
{
int addhe = (int)c + a + b;
return addhe;
}
int main()
{
int a = 12;
int result = funcadd<int ,12, 12>(12);
int result2 = funcadd<double, 11, 12>(13);//系统会以我们用<>传递进去的类型为准,而不是以13来推断什么类型
return 0;
}
36
vs2017调试发现并不是传入13的值推断成整型,而是以<>类型为准
再来看一个例子
#include <iostream>
using namespace std;
template<unsigned L1, unsigned L2>//本例中仍然没有类型参数。只有非类型模板参数
int charscomp(const char(&p1)[L1], const char(&p2)[L2])
{
return strcmp(p1, p2);
}
int main()
{
int result = charscomp("test2", "test");//没有提供非类型模板参数,系统会根据test2的长度(6),test长度(5),取代L1,L2
cout << result << endl;
return 0;
}
结果
1
系统会实例化一个如下代码(实际上是调用这段代码)
int charscomp(const char(&p1)[6], const char(&p2)[5])
{
return strcmp(p1, p2);
}
除了让系统推断还可以自己给定参数,不过系统可以自己推断就不用多此一举
//int result = charscomp<7,5>("test2", "test");//报错
int result = charscomp<6,5>("test2", "test");//没有提供非类型模板参数,系统会根据test2的长度(6),test长度(5),取代L1,L2
模板函数可以用inline函数,放在模板参数列之后
template<unsigned L1, unsigned L2> inline//本例中仍然没有类型参数。只有非类型模板参数
int charscomp(const char(&p1)[L1], const char(&p2)[L2])
{
return strcmp(p1, p2);
}
模板定义并不会导致编译器生成代码,只有在我们调用这个函数模板时,使编译器为我们实例化一个特定版本的函数之后,编译器才会生成代码。编译器生成代码的时候,需要能够找到函数的函数体,所以,函数模板的定义通常都是在.h文件中。多个文件引入.h头文件模板不会出现像函数一样的重定义问题。