一.什么是函数模板
在介绍函数模板前先看段代码:
对,就是函数重载,我们知道函数重载基于不同数据类型实现类似操作,那么如果数据类型比较多的时候怎么办呢?只要有新类型出现,就要重新添加对应函数,而且代码函数体都相同,但代码复用率却不高。那么可不可以使用预处理指令,可以,但是安全性却不高。所以我们引入函数模板。那什么是函数模板?
函数模板:
代表了一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
二.函数模板的语法:
函数模板的定义格式:
template<typename Param1, typename Param2,...,class Paramn>
返回值类型 函数名(参数列表)
{
...
}
其中template是声明函数模板的关键字。<typename Param1, typename Param2,...,class Paramn>为模板参数表 typename定义模板参数的关键字当然也可以是class(但不可以使用struct),
模板函数也可定义成内联函数:
template<typename T>
inline T Add(const T _left, const T _right)
{
return (_left + _right);
}
注意:inline关键字必须放在模板形参表之后,返回值之前,不能放在template之前。
**模板是一个蓝图,它本身不是类或者函数,编译器用模板产生指定的类或者函数的特定类型版本,产
生模板特定类型的过程称为函数模板实例化。
函数模板实例化:
template <typename T>
void swap(T &x,T &y)
{
T temp;
temp = x;
x = y;
y = temp;
}
void main()
{
int x = 8;
int y = 6;
cout <<"交换位置前x,y的值x =" << x <<" " << "y = "<< y << endl;
swap(x,y);
cout <<"交换位置后x,y的值x =" << x <<" " << "y = "<< y << endl;
float a = 2.6f;
float b = 5.8f;
cout <<"交换位置前a,b的值a =" << a <<" " << "b = "<< b << endl;
swap(a,b);
cout <<"交换位置后a,b的值a =" << a <<" " << "b = "<< b << endl;
char ch1,ch2;
ch1 = 'a';
ch2 = 'b';
cout <<"交换位置前ch1,ch2的值ch1 =" << ch1 <<" " << "ch2 = "<< ch2 << endl;
swap(ch1, ch2);
cout <<"交换位置前ch1,ch2的值ch1 =" << ch1 <<" " << "ch2 = "<< ch2 << endl;
system("pause");
}
如这个程序当编译器遇到swap(x,y)时,会根据模板的函数声明,生成实例函数。这样可以使得一段程序用于处理多种不同类型的对象,从而实现真正的代码可重性。
三.使用函数模板需要注意的问题
(1).函数模板中的每一个类型参数在函数参数表中至少使用一次。如下声明即错误声明:template <class T1, class T2>
Void func1(T1 paral)
{
}
(2).全局域中声明的与模板参数同名的对象、函数、类型,在函数模板中将被隐藏。如:
int paral;//全局变量
template <class T1>
Void func2(T1 paral)
{
//函数体,在函数体中访问的是T1类型中的paral而不是全局整型变量paral;
}
(3).函数模板定义中声明的对象或类型不能与模板参数同名。如:
template <class T>
T max(T x,T y)
{
typedef float T; //定义的类型与模板参数同名
}
(4)模板参数名可以用来指定函数模板的返回类型
(5)模板参数名在同一模板参数表中只能使用一次,但可在多个函数模板声明或定义之间重复使用。如:
template <class T,class T> // 模板参数名重复使用
T max (T x,T y)
template <class T>
T max(T x,T y)
template <class T> //模板参数名T在不同模板之间重复使用
T min(T x,T y)
(6).一个模板的定义和多处声明所使用的模板参数名无须相同。如:
template <class T> //模板的前向声明
T max(T x, T y);
template <class Type> //定义和多处使用的模板参数名无须相同
Type max (Type x,Type y)
{
//函数体
}
(7).函数模板如果有多个模板类型参数,则每个模板类型参数前面都必须用关键字class或typename 修饰,且可以混用。
(8)模板参数在函数参数表中出现的次数没有限制。
(9)在模板形参内部不能指定缺省的模板实参
模板函数重载:
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);
Max(10.0, 20.0);
return 0;
}
模板函数特化:
在说模板函数特化的时候我们先来看看这个程序
template <typename T>
int compare(T t1,T t2)
{
if (t1 < t2)
return -1;
if(t1>t2)
return 1;
return 0;
}
int main()
{
char *pStr1 = "1234";
char *pStr2 = "abcd";
cout <<compare(pStr1,pStr2)<<endl;
return 0;
system("pause");
}
程序正常情况下应该返回-1,可是你可以试试运行一下这个程序
是不是和你想想中的不一样?那么到底是为什么呢?其实导致这个结果的原因是因为:函数在调用的时候将俩个指针变量的地址传递给模板函数,在比较时比较的是俩个地址,不是俩个值。怎么来解决这个问题呢?
这样就可以了。
模板函数特化形式如下:
1、关键字template后面接一对空的尖括号<>
2、再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参
3、函数形参表
4、函数体
template<>
返回值 函数名<Type>(参数列表)
{
// 函数体
}
**在模板特化版本的调用中,实参类型必须与特化版本函数的形参类型完全匹配,
如果不匹配,编译器将为实参模板定义中实例化一个实例。
四.类模板
类模板主要表示像数组、链表、矩阵等这类数据结构或包含有通用得逻辑算法,类模板就变得特别有用。
类模板的定义语法格式:
template <class T1,class T2,......>
Class 类名
{
类成员声明
}
除此之外,类模板成员函数的定义在类模板以外,形式如下:
template <class T1,class T2.....>
类型名 类名<T1,T2,....>::函数名(参数表)
其中template 是声明类模板的关键字。< , , ..>是模板参数表,模板参数分为模板类型参数和模板非类型参数
下面看个例子:
普通顺序链表的实现:typedef int DataType;
//typedef char DataType;
class SeqList
{
private :
DataType* _data ;
int _size ;
int _capacity ;
};
以类模板实现动态链表:template<typename T>
class SeqList
{
public :
SeqList();
~ SeqList();
private :
int _size ;
int _capacity ;
T* _data ;
};
template <typename T>
SeqList <T>:: SeqList()
: _size(0)
, _capacity(10)
, _data(new T[ _capacity])
{}
template <typename T>
SeqList <T>::~ SeqList()
{
delete [] _data ;
}
void test1 ()
{
SeqList<int > sl1;
SeqList<double > sl2;
}
看出来什么了吗?类模板的定义几乎和普通类的定义相同,只不过在类模板的定义中要加上模板定义的关键字和模板参数列表。
下面看个成员函数实例化的例子:
struct student
{
int id;
int score;
};
template <class T>
class buffer
{
private:
T a;
int empty;
public:
buffer(void);
T get(void);
void put(T x);
};
template <class T>
buffer <T>::buffer(void):empty(0){} //成员函数初始化
template <class T>
T buffer <T>::get(void)
{
if(empty == 0)
{
cout <<"the buffer is empty!"<< endl;
exit(1);
}
return a;
}
template <class T>
void buffer <T>::put(T x)
{
empty++;
a = x;
}
void main(void)
{
student s = {1022,78};
buffer <int>i1,i2;
buffer <student> stu1;
buffer <double> d;
i1.put(13);
i2.put(-101);
cout << i1.get() <<" " <<i2.get()<<endl;
stu1.put(s);
cout <<"the student's id is "<<stu1.get().id<<endl;
cout <<"the student's score is "<<stu1.get().score<<endl;
cout <<d.get() <<endl;
system("pause");
}
执行结果如下:
类模板实例化的特点:
1.只有当类模板实例真正使用时,编译器才会实例化类模板。
2.实例化类模板时,除了构造函数和析构函数外,不会自动实例化类模板的其他成员函数。
3.对于模板非类型参数,实例化时赋给的实参必须是编译时常量,否则会出错。
类模板与模板类:
1类模板是抽象带有相同功能的类,不能定义对象,因为这些类的成员函数返回值、成员函数形式参数以及数据成员的数据类型不同。而模板类是类模板的实例
2类模板是程序员写出来的,而模板类是编译器自动生成的。
3.类模板是模板的定义,不是一个实实在在的类,而模板类具有程序代码,占用内存空间,可以实际执行,模板类是实实在在的类。
下面看一个基类和派生类使用不同数据类型的简单例子:
template <typename T>
class A
{
public :
void f(T a)
{
cout <<a <<endl;
}
};
template <typename x, typename y>
class B:public A<y>
{
public :
void g(x b)
{
cout <<b <<endl;
}
};
void main()
{
B <char, int >bb;
bb.f(10);
bb.g('A');
B<int, int>bb1;
bb1.f(10);
bb1.g(25);
system("pause");
}
执行结果:
五.派生类和类模板
派生类和类模板一般有三种组合
1.从类模板派生类模板:建立类模板的层次结构
2.从类模板派生非类模板:从类模板创建具体的类,而不是编译器自动创建不满足要求的类。
3.从非类模板派生出类模板:从现存类中创建类模板。
使用模板的优缺点分析:
优点:
模板复用了代码,节省资源,更快的迭代开发,C++的标准模板库(STL)因此而产生。
增强了代码的灵活性。
缺点:
模板让代码变得凌乱复杂,不易维护,编译代码时间变长。
出现模板编译错误时,错误信息非常凌乱,不易定位错误。