一、函数模板
定义一个函数模版:
template<typename T> // 定义函数模板
T funadd(T a, T b) {
T addhe = a + b;
return addhe;
}
尖括号里面是模板参数列表,如果这里的模板参数有多个,则用逗号分开,尖括号里至少要有一个模板参数。模板参数列表里面表示在函教定义中用到的“类型”或者“值”,使用的时候有时得指定模板实参,指定的时候也得用<>
把模板实参包起来,有时又不需要指定模板实参,系统能够根据一些信息推断出来。函数模版调用的时候,先不用看函数模板定义中template<>
这里有多少个模板参数,看的还是函数模板定义里面的函数名后面的参数数量。
int main() {
{
int he = funadd(3, 1);
}
{
float he = funadd(3.1f, 1.2f);
}
{
// 下面这行编译出错,不知道模板参数类型应该推断为int类型还是float类型
// float he = funadd(3, 1.2f);
}
return 0;
}
看一看上述案例的模板参数列表template<typename T>
,这里的T
,因为前面是用typename
来修饰,所以T
代表一个类型,是类型参数。在这个模板参数列表里,还可以定义非类型参数。非类型参数表示的是一个值,不能用typename/class
来修饰,例如非类型参数s是一个整型,那就写成int s
。
template<int a, int b> // 定义函数模板
int funcaddv2() {
int addhe = a + b;
return addhe;
}
这里没有类型模板参数,只有非类型模板参数。调用这个函数模版:
int main() {
{
// 要通过<>来传递参数,就得看模板函数的<>里有几个参数
// 这种<>写法就是显式指定模板参数,在尖括号中提供额外信息
int result = funcaddv2<12, 13>();
cout << result << endl; // 25
}
{
int a = 12;
// 这不可以,非类型模板参数必须是常量表达式,值必须是在编译的时候就能确定
// 因为实例化模板是在编译的时候做的事
// int result = funcaddv2<a, 14>();
}
return 0;
}
再定义一个函数模版:
template<typename T, int a, int b>
int funcaddv3(T c) {
int addhe = (int)c + a + b;
return addhe;
}
int main() {
{
// 类型参数为int,实参为13
int result = funcaddv3<int, 11, 12>(13);
cout << result << endl; // 36
}
{
// 类型参数为double,实参为13,系统会以"<>"传递进去的类型为准
// 而不是以13推断出来的类型为准
int result = funcaddv3<double, 11, 12>(13);
cout << result << endl; // 36
}
return 0;
}
再定义一个函数模版:
template<unsigned L1, unsigned L2> // 本例依旧没有类型参数
int charscomp(const char(&p1)[L1], const char(&p2)[L2]) {
return strcmp(p1, p2);
}
int main() {
{
// 根据test2能推断出大小是6个(算末尾的\0)取代L1,L2同理,推断出大小是5个
int result = charscomp("test2", "test");
cout << result << endl; // 1
}
return 0;
}
上面针对charscomp()
的调用,编译器实例化出来的版本是:
int charscomp(const char(&p1)[6], const char(&p2)[5]) {...}
再次提醒,非类型模板参数必须是一个常量表达式,否则编译会出错。函数模版也可以写成inline
,inline
的位置放在模版参数列表之后:
template<unsigned L1, unsigned L2>
inline int charscomp(const char(&p1)[L1], const char(&p2)[L2]) {
return strcmp(p1, p2);
}
函数模板的定义并不会导致编译器生成相关代码,只有调用这个函数模板时,编译器才会实例化一个特定版本的函数并生成函数相关代码。总结一下模板参数:
二、类模板
编译器不能为类模板推断模板参数,所以使用类模板必须在模板名后面用尖括号<>
提供额外信息,这些信息其实就是对应着模板参数列表里的参数。例如vector<int>
这里面的vector
是类模板,尖括号里的int
就理解成模板参数,通过这个模板参数指出容器vector
中所保存的元素类型。类模版(也称模板类)定义的一般形式如下:
template<typename 形参名1, typename 形参名2, ..., typename 形参名n>
class 类名 {
// ......
};
对于类模板,因为实例化具体类的时候必须有类模板的全部信息,包括类模板中成员函数的函数体具体内容等,所以类模板的所有信息,不管是声明还是实现等内容,都必须写到一个.h文件中去,其他的要用到类模板的源程序文件(如. cpp文件)只要#include
这个类模板的.h文件即可,例如在文件myvector.h中定义:
#ifndef __MYVECTOR__
#define __MYVECTOR__
// 自己的容器类模板
template<typename T> // 名字为T的模板参数,用来表示myvector这个容器所保存的元素类型
class myvector {
public:
typedef T* myiterator; // 迭代器
public:
// 构造函数
myvector();
// 赋值运算符重载,在类模板内部使用模板名myvector并不需要提供模板参数,当然提供也行,可以写成myvector<T>
myvector& operator=(const myvector&);
public:
// 迭代器接口
myiterator mybegin(); // 迭代器起始位置
myiterator myend(); // 迭代器结束位置
};
#endif
实例化这个类模板:
#include "myvector.h"
int main() {
{
myvector<int> tmpvec; // T被替换成了int
myvector<double> tmpvec2; // T被替换成了double
myvector<string> tmpvec3; // T被替换成了string
}
return 0;
}
myvector
是类模板名,myvector<int>
等才是真正的类型名(实例化了的类模板)。在上述类模板中,增加成员函数(定义和声明写在一起):
public:
void myfunc() {};
类模板一旦被实例化之后,这个类模板的每个实例都会有自己版本的成员函数。所以,类模板的成员函数具有和这个类模板相同的模板参数(这句话的核心意思是:类模板的成员函数是有模板参数的)。如果这个类模板的成员函数定义在类模板里面,那么这个成员函数的模板参数体现不出来,但假如把类模板的成员函数的实现写在类模板定义的外面,那么这个成员函数的模板参数就体现出来了。也就是说,定义在类模板之外的成员函数必须以关健字template
开始,后面接类模板参数列表。同时在类名后面要用尖括号<>
把模板参数列表里面的所有模版参数名列出来,如果是多个模板参数则用,
分隔。
public:
void myfunc(); // 函数声明
在类模板外部定义:
template<typename T>
void myvector<T>::myfunc() {}
再写一下构造函数的实现:
template<typename T>
myvector<T>::myvector() {}
一个类模板虽然里面可能有很多成员函数,但是当实例化模板之后,如果后续没有使用到某个成员函数,则这个成员函数是不会被实例化的。再看一下类模板名字的使用,在上述类模板中有一个赋值运算符的重载代码:
myvector& operator=(const myvector&);
赋值运算符重载返回一个myvector
的引用,在类模板内部可以直接使用类模板名,并不需要在类模板名后跟模板参数,当然非要在类模板名后面跟模板参数也可以:
myvector<T>& operator=(const myvector<T>&); // 赋值运算符重载
如果在类模板定义之外实现:
template<typename T>
// 第一个<T>表示返回的是一个实例化了的myvector,第三个<T>不是必加
myvector<T>& myvector<T>::operator=(const myvector<T>&) {
//......
return *this;
}
模板参数并不局限于类型,普通的值也能作为模板参数,也就是非类型模板参数。前面讲函数模板有非类型模板参数,而myvector
类模板中是一个类型模板参数。创建一个新的类模板来演示非类型模板参数,创建一个新的文件myarray.h:
#ifndef __MYARRAY__
#define __MYARRAY__
template<typename T, int size = 10>
class myarray {
private:
T arr[size];
};
#endif
存在非类型模板参数size
,而且还有默认值。
#include "myarray.h"
int main() {
{
myarray<int, 100> tmparr;
}
{
myarray<int> tmparr;
}
return 0;
}
在类模板增加一个成员函数的声明:
public:
void myfunc();
在类模板外面实现:
template<typename T, int size>
void myarray<T, size>::myfunc() {
std::cout << size << std::endl;
return;
}
在main函数中调用:
myarray<int> tmparr;
tmparr.myfunc(); // 10
myarray<int, 50> tmparr2;
tmparr2.myfunc(); // 50
注意,非类型的模板参数,参数的类型有一定的限制:
(1)浮点型一般不能作为非类型模板参数:
template<typename T, double size>
class myarray {...};
(2)类类型也不能作为非类型模板参数:
class a {};
template<typename T, a size>
class myarray {...};