首先来谈谈什么是模板?
形象的来说,模板就是一个创建类或函数的蓝图或者公式。
首先我们来看一个例子:
比如说我们要实现一个函数来比较两个数的值,按照我们以前的习惯,我们会选择重载函数:
//如果相等则返回0,如果v1小返回-1,如果v2小返回1
int compare(const string &v1, conststring &v2)
{
if(v1<v2) return -1;
if(v2<v1) return -1;
return0;
}
int compare(const double &v1, constdouble &v2)
{
if(v1<v2) return -1;
if(v2<v1) return -1;
return0;
}这两个函数几乎完全相同,唯一的差异就是函数类型,函数体完全一样。而且我们做比较函数可能还要处理更多的数据类型,这时候工程量就显得特别大。
我们可以定义一个通用的函数模板
template<typename T>
int compare(const T &v1, const T&v2)
{
if(v1<v2) return -1;
if(v2<v1) return -1;
return0;
}
模板定义可以以关键字temeplate开始,后跟一个模板参数列表,这是一个逗号分隔的一个或多个模板参数的列表,用小于号和大于号包围起来。(模板参数列表不能为空)
函数模板:代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参产生函数的特定类型版本。
基本格式:
template<typename Param1, typename Param2,...,class Paramn>
返回类型函数名(参数列表
{
..
}typename是用来定义模板参数关键字,也可以使用class。尽量使用typename
注意:不能使用struct代替typename
模板函数可以定义为inline函数
template<typename T>
inline T Add(const T _left, const T _right)
{
return(_left + _right);
}注意:inline关键字必须放在模板形参表之后,返回值之前,不能放在template之前
产生模板特定类型的过程叫做函数模板实例化。
实例化过程中模板被编译两次:
1、实例化之前,检查模板代码本身,查看是否出现语法错误,如:遗漏分号
2、在实例化期间,检查模板代码,查看是否所有的调用都有效,如:实例化不支持某些函数调用
#include<iostream>
using namespace std;
template < class T>
T Add(T left, T right)
{
returnleft + right;
}
int main()
{
cout<< Add(10, 20) << endl;
cout<< Add(2.5, 32.3) << endl;
cout<< Add(10, (int)2.5) << endl;
cout<< Add<int>((int)'c',(int) 'a') << endl;
system("pause");
return0;
}输出结果如图所示:
在上例中第一个函数相当于:
int Add(int left, int right)
{
return left+right;
}
第二个相当于:
double Add(double left, double right)
{
return left+right;
}第三个和第四个使用了强制类型转换,实际上和第一个一样,都调用的是int类型的函数。
【实参推演】:从函数实参确定模板类型和值的过程称为模板实参推断
多个类型形参的实参必须完全匹配。
【类型形参转换】
一般不会转换实参以匹配已有的实例化,相反会产生新的实例。
编译器只会执行两种转换:
1、const转换:接受const引用或者const指针的函数可以分别用非const对象的引用来调用
2、数组或函数到指针的转换:如果模板形参不是引用类型,则对数组或函数类型的实参应用常规指针转换。数组实参将当做指向其第一个元素的指针。
【模板参数】
函数模板有两种类型参数:模板参数和调用参数
模板形参名字只能在模板形参之后到模板声明或者定义的末尾之间使用,遵循名字屏蔽规则
模板形参的名字在同一模板形参列表中只能使用一次,举个例子:
#include<iostream>
using namespace std;
typedef int T;
template <class T,class T>
int main()
{
system("pause");
return0;
}
所有模板形参前面必须加上class或者typename关键字修饰,例如:
#include<iostream>
using namespace std;
typedef int T;
template <class T,U>
int main()
{
system("pause");
return0;
}
在该例中模板形参U前没有加关键字,出现错误
注意:在函数模板的内部不能指定缺省的模板形参。
【非模板类型参数】
非模板类型形参是模板内部定义的常量,在需要常量表达式的时候,可以使用非模板类型参数。
模板形参说明:
1、模板形参表用<>括起来
2、核函数参数表一样,跟多个参数时必须用逗号隔开,类型可以相同也可以不相同
3、定义模板参数时模板形参表不能空
4、模板形参可以是类型形参,也可以是非类型新参,类型形参跟在class和typedef后
5、模板类型参数可作为类型说明符用在模板的各个地方,与内置类型或者自定义类型实用方法完全相同,可用于指定函数形参类型、返回值、局部变量和强制类型转换
6、模板形参表中,class和typedef具有相同的含义可以互换,单typedef更加直观。但关键字typedef是作为c++标准加入到c++中的,旧的编译器可能不支持。
【函数模板重载】
#include<iostream>
using namespace std;
int Max(const int& left, const int&right)
{
returnleft > right ? left : right;
}
template<class T>
T Max(const T& left, const T&right)
{
returnleft > right ? left : right;
}
template <class T>
T Max(const T& a, const T& b, constT& c)
{
returnMax(Max(a, b), c);
};
int main()
{
cout<< Max(10, 20, 30) << endl;
cout<< Max<>(10, 20) << endl;
cout<< Max(10, 20) << endl;
cout<< Max<int>(10.0, 20.0) << endl;
cout<< Max(10.0, 20.0) << endl;
system("pause");
return0;
}
注意:函数的所有重载版本的声明都应该位于该函数被调用位置之前。
【说明】
1、一个非模板函数可以和一个同名的函数模板函数同时存在,而且该函数模板还可以被实例化为这个非模板函数。
2、对于非模板函数和同名函数模板,如果其他条件都相同,在调用时会优先调用非模板函数而不会从模板中产生一个实例。如果模板可以产生一个更好匹配的函数,那么选择模板。
3、显示指定一个空的模板实参列表,该语法告诉编译器只有模板才能来匹配这个调用,而且所有的模板参数都应该根据实参演绎出来。
4、模板函数不允许自动类型转换,但普通函数可以进行自动类型转换。
【函数模板特例化】
有时候并不总是能够写出所有可能被实例化的类型都是合适的模板,在有些情况下,通用模板定义对于某个类型可能是完全错误的,或者不能编译,或者做一些错误的事。
#include<iostream>
using namespace std;
template <class T>
int compare(T t1, T t2)
{
if(t1<t2)
{
return-1;
}
if(t1>t2)
{
return1;
}
return0;
}
int main()
{
char*pStr1 = "1234";
char*pStr2 = "abcd";
cout<< compare(pStr1, pStr2) << endl;
system("pause");
return0;
}
在函数调用时,直接将两个指针变量的地址传递给模板函数,在比较时比较的时地址并没有比较指针的内容。
可以这样定义:
template<>
int compare(const char* const p1,constchar* const p2)
{
return strcmp(p1,p2);
}
模板函数特化形式如下:
1、关键字template后加<>
2、函数名后接模板名和一对尖括号,尖括号中指定这个特化定义的模板参数
3、函数形参表
4、函数体
格式:
template<>
返回值函数名 <Type>(参数列表)
{
//函数体
}
特化的声明必须与特定的模板相匹配
假如少了模板参数,该函数就是一个普通函数
注意:在模板特化版本调用中,实参类型必须与特化版函数的形参类型完全匹配,如果不匹配,编译器将为实参模板定义中实例化一个实例。
特化不能出现在模板实例的调用之后应该在头文件中包含模板特化的声明,然后使用该特化版本的每个源文件包含该头文件。
【模板类】
模板类格式:
template<class形参名1,class 形参名2,...class 形参名n>
class 类名
{...};
#include<iostream>
using namespace std;
template <class T>
class SeqList
{
public:
SeqList();
~SeqList();
private:
int_size;
int_capacity;
T*_data;
};
template<class T>
SeqList<T>::SeqList()
:_size(0)
, _capacity(10)
, _data(new T[_capacity])
{}
template<class T>
SeqList <T>::~SeqList()
{
delete[]_data;
}
void test1()
{
SeqList<int>s11;
SeqList<double>s12;
}
int main()
{
test1();
return0;
}【模板类的实例化】
只要有一种不同的类型,编译器就会实例化出一个对应的类。
SeqList<int > s11;
SeqList<double> s12;
当定义上述两种类型的顺序表时,编译器会用int和double分别代替模板形参,重新编写SeqList类,最后创建SeqList<int>和SeqList<double>的类。
模板特化:
全特化、
#include<iostream>
using namespace std;
template <class T>
class SeqList
{
public:
SeqList();
~SeqList();
private:
int_size;
int_capacity;
T*_data;
};
template<class T>
SeqList<T>::SeqList()
:_size(0)
, _capacity(10)
, _data(new T[_capacity])
{
cout<< "SeqList()" << endl;
}
template<class T>
SeqList <T>::~SeqList()
{
delete[]_data;
}
SeqList<int>::SeqList()
:_size(0)
, _capacity(10)
, _data(new T[_capacity])
{
cout<< "SeqList<int>" << endl;
}
SeqList <int>::~SeqList()
{
delete[]_data;
}
void test1()
{
SeqList<int>s11;
SeqList<double>s12;
}
int main()
{
test1();
return0;
}
偏特化(局部特化)
#include<iostream>
using namespace std;
template<class T1,class T2>
class Data
{
public:
Data();
private:
T1_d1;
T2_d2;
};
template<class T1,class T2>
Data<T1, T2>::Data()
{
cout<< "Data<T1, T2>" << endl;
}
//局部化第二个参数
template<class T1>
class Data<T1, int>
{
public:
Data();
private:
T1_d1;
int_d2;
};
template<class T1>
Data<T1, int>::Data()
{
cout<< "Data<T1, int>" << endl;
}
//局部特化两个参数为指针
#include<iostream>
using namespace std;
template<class T1,class T2>
class Data
{
public:
Data();
private:
T1_d1;
T2_d2;
};
template<class T1,class T2>
Data<T1, T2>::Data()
{
cout<< "Data<T1, T2>" << endl;
}
template<class T1, class T2>
class Data<T1*, T2*>
{
public:
Data();
private:
T1_d1;
T2_d2;
T1*_d3;
T2*_d4;
};
template<class T1, class T2>
Data<T1*, T2*>::Data()
{
cout<< "Data<T1*, T2*>" << endl;
}
//局部特化两个参数为引用
#include<iostream>
using namespace std;
template<class T1,class T2>
class Data
{
public:
Data();
private:
T1_d1;
T2_d2;
};
template<class T1,class T2>
Data<T1, T2>::Data()
{
cout<< "Data<T1, T2>" << endl;
}
template<class T1, class T2>
class Data<T1&, T2&>
{
public:
Data(constT1& d1, const T2& d2);
private:
constT1 &_d1;
constT2 &_d2;
T1*_d3;
T2*_d4;
};
template<class T1, class T2>
Data<T1&, T2&>::Data(constT1& d1, const T2& d2)
:_d1(d1)
, _d2(d2)
{
cout<< "Data<T1&, T2&>" << endl;
}
void test()
{
Data<double,int> d1;
Data<int,double> d2;
Data<int*,int*> d3;
Data<int&,int&> d4(1,2);
}
int main()
{
test();
system("pause");
return0;
}
输出结果:
从上述代码可知,偏特化不仅仅指特化部分参数,而是对模板参数进一步的条件限制设计出来的一种特化版本
模板的全特化都是在已定义的模板基础上,不能单独存在
模板的分离编译
将代码放在三个文件中如图所示:
编译时可以通过编译,但是运行时会出错:
分析出现这种错误的原因:
编译时SeqList<T>没有实例化出SeqList<int>实例,所以链接时出错。
解决办法:
1.在模板头文件xxx.h 里面显示实例化->模板类的定义后面添加templateclass SeqList<int>;一般不推荐这种方法,一方面,老编译器不支持,另一方面,实例化依赖调用者。
2.将声明和定义放在一个文件“xxx.hpp”里面,推荐使用这种方法
模板总结:
【优点】
模板复用了代码,节省资源,更快的迭代开发,c++标准库模板(STL)因此而产生
增强了代码的灵活性
【缺点】
模板让代码变得凌乱复杂,不易维护,编译代码时间变长。出现模板编译错误时,错误信息非常凌乱,不易定位错误。
本文详细介绍了C++中的模板概念,包括函数模板和类模板的定义、使用及特化方法,并通过实例展示了模板如何帮助代码复用。

850

被折叠的 条评论
为什么被折叠?



