函数模板提供了一种函数行为,该函数行为可以用多种不同的类型进行调用;也就是说,函数模板代表一个函数族、它表示看起来和普通的函数很相似,唯一的区别就是有些函数元素是未确定的:这些元素将在使用时被参数化
定义模板:
template
inline T const& max(T const&a, T const& b)
{
return a < b ? b:a;
}
实参推导:
当我们为某些实参调用一个诸如max()的模板时,模板参数可以由我们所传递的实参来决定。如果我们传递了两个int给参数类型T const&,那么C++编译器能够得出结论:T必须是int。这里不允许进行自动类型转换,每个T都必须正确地匹配。
template
inline T const& max(T const& a,T const& b);
max(4,7); //OK:两个实参的类型都是int
max(4,4,2); //ERROR:第一个T是int,而第2个T是double
有3种方法可以用来处理上面这个错误:
(1)对实参进行强制类型转换,使它们可以相互匹配:
max( static_cast(4), 4.2); //OK
(2)显示指定(或限定)T的类型
max(4,4,2); //OK
(3)指定两个参数可以具有不同的类型
类模板Stack的实现:
#include
#include
template
class Stack{
private:
std::vector elems; //存储元素的容器
public:
void push(T const&);
void pop();
T top() const;
bool empty() const{
return elems.empty();
}
};
template
void Stack::push(T const& elem)
{
elems.push_back(elem);
}
template
void Stack::pop()
{
if(elems.empty()){
throw std::out_of_range("Stack<>::pop():empty stack");
}
elems.pop_back();
}
template
T Stack::top() const
{
if(elems.empty()){
throw std::out_of_range("Stack<>::pop():empty stack");
}
elems.back();
}
类模板的特化:
为了特化一个类模板,必须在起始处声明一个template<>,接下来声明用来特化类模板的类型。这个类型被用作模板实参,且必须在类名的后面直接指定:
template<>
class Stack{
...
}
进行类模板的特化时,每个成员函数都必须重新定义为普通函数,原来模板函数中的每个T也相应地被进行特化的类型取代:
void Stack::push(std::string const& elem)
{
elems.push_back(elem);
}
局部特化:
类模板可以被局部特化。可以在特定的环境下指定类模板的特定实现,并且要求某些模板参数仍然必须由用户来定义。如类模板:
template
class Myclass{
...
};
可以有下面几种局部特化:
//局部特化:两个模板参数具有相同的类型
template
class MyClass{
...
};
//局部特化:第2个模板参数的类型是int
template
class MyClass{
...
};
//局部特化:两个模板参数都是指针类型
template
class MyClass{
...
};
如果有多个局部特化同等程度地匹配某个声明,那么就称该声明具有二义性:
MyClass m; //ERROR:同等程度地匹配MyClass和MyClass
MyClass m; //ERROR:同等程度地匹配MyClass和MyClass
为了解决第2中二义性,可以另外提供一个指向相同类型指针的特化:
template
class MyClass{
...
};
缺省模板实参:
如在类Stack<>中,可以把用于管理元素的容器定义为第2个模板参数,并且使用std::vector<>作为它的缺省值
template >
class Stack{
private:
CONT elems; //包含元素的容器
public:
void push(T const&);
void pop();
T top() const;
bool empty() const{
return elems.empty();
}
};
template
void Stack::push(T const& elem)
{
elems.push_back(elem);
}
如果你只传递第一个类型实参给这个类模板,那么将会利用vector来管理stack的元素
对于函数模板和类模板,模板参数并不局限于类型,普通纸也可以作为模板参数。在基于类型参数的模板中,你定义了一些具体未加确定的代码,直到代码被调用时这些细节才被真正确定。然而,这里我们面对的这些细节是值value,而不是类型。当要使用基于值的模板时,你必须显式地指定这些值,才能够对模板进行实例化,并获得最终代码。
可以使用元素数目固定的数组来实现stack,优点是:无论是由你来亲自管理内存,还是由标准容器来管理内存,都可以避免内存管理开销。然而,决定一个栈的最佳容量是很困难的。一个好的解决办法就是:让栈的用户亲自制定数组的大小,并把它作为所需要的栈元素的最大个数。
#include
#define MAXSIZE 1024
template
class Stack{
private:
T elmes[MAXSIZE]; //包含元素的数组
int numElems; //元素的当前总个数
public:
Stack();
void push(T const&);
void pop();
T top() const;
};
template
Stack::Stack():numElems(0)
{
//do nothing
}
MAXSIZE是新加入的第2个模板参数,类型为int;它指定了数组最多可包含的栈元素的个数。
为了使用这个模板,你需要同时指定元素的类型和个数(即栈的最大容量):
Stack
同样,我们可以为模板参数指定缺省值:
template
class Stack{
...
};
1.关键字typename
通常而言,当某个依赖模板参数的名称是一个类型时,就应该使用typename
考虑一个typename的典型应用,即在模板代码中 访问STL容器的迭代器
//打印STL容器的元素
template
void printColl(T const& coll)
{
typename T::const_iterator pos; //用于迭代coll的迭代器
typename T::const_iterator end(coll.end()); //结束位置
for(pos==coll.begin();pos!=end;++pos){
std::cout<<*pos<<" ";
}
std::cout<
为了访问模板类型为T的const_iterator类型,需要在声明开始处使用关键字typename来加以限定,如:
typename T::const_iterator pos;
2.成员模板
对于元素类型不同的栈,你不能对它们进行相互赋值,即使这两种(元素的)类型之间存在隐式类型转换。
缺省赋值运算符要求两边具有相同的类型,当元素类型不同时,两个栈的类型显然不同,不能符合缺省赋值运算符的要求。
然而,通过定义一个身为模板的赋值运算符,针对元素类型可以转换的两个栈就可以进行相互赋值。
template
class Stack{
private:
std::deque elems; //存储元素的容器
public:
void push(T const&);
void pop();
//使用元素类型为T2的栈进行赋值
template
Stack& operator= (Stack const&);
};
3.使用字符串作为函数模板的实参
有时,把字符串传递给函数模板的引用参数会导致出人意料的运行结果
#include
//注意:引用参数
template
inline T const& max1(T const& a,T const& b)
{
return a < b ? b:a;
}
int main()
{
std::string s;
max1("apple","peach"); //OK:相同类型的实参
max1("apple","tomato"); //ERROR:不同类型的实参
max1("apple",s); //ERROR:不同类型的实参
return 0;
}
问题在于:由于长度的区别,这些字符串属于不同的数组类型。也就是说,‘apple’和‘peach’具有相同的类型char const[6];然而‘tomato’的类型则是:char const[7].
模板实例化是一个通过使用具体值替换模板实参,从模板产生出普通类、函数或者成员函数的过程。这个过程最后获得的实体(比如类、函数或者成员函数)就是我们通常所说的特化(specialization)。对于仍然具有模板参数的特化,我们称之为局部特化(partial specialization)
template
class MyClass{
...
};
template //局部特化
class MyClass{
...
};
通过template关键字来声明使用模板,typename关键字来定义模板类型
template //声明使用模板,并定义T是一个模板类型
void swap(T& a,T& b)
{
T c = a;
a = b;
b = c;
}
当我们使用int类型参数来调用swap,则T就会自动转换为int类型
函数模板的使用:分为自动调用和显示调用
int a = 0;
int b = 1;
swap(a,b); //自动调用,编译器根据a和b的类型来推导
float c = 0;
float d = 1;
swap(c,d); //显示调用,告诉编译器,调用的参数是float类型
初探函数模板:
写两个函数模板,一个用来排序数组,一个用来打印数组
#include
#include
using namespace std;
template
void Sort(T a[],int len)
{
for(int i = 1;i
void Println(T a[],int len)
{
for(int i = 0;i(a,5); // 显示调用,告诉编译器,调用的参数是int类型
string s[5] = {"Java","C++","Pascal","Ruby","Basic"};
Sort(s,5);
Println(s,5);
return 0;
}
深入理解函数模板:
为什么函数模板能够执行不同的类型参数?
试验函数模板是否生成真正的函数:通过两个不同类型的函数指针指向函数模板,然后打印指针地址是否一致
#include
using namespace std;
template
void Swap(T& a,T& b)
{
T c = a;
a = b;
b = c;
}
int main()
{
void (*FPii)(int&,int&);
FPii = Swap; //函数指针FPii指向Swap函数
void (*FPff)(float&,float&);
FPff = Swap; //函数指针FPff指向Swap函数
cout<(FPii)<(FPff)<(Swap)<
多参数函数模板
函数模板可以定义任意多个不同的类型参数
template
T1 Add(T2 a,T3 b)
{
return static_cast(a+b);
}
注意:
工程中一般都将返回值参数作为第一个模板类型
如果返回值类型作为模板类型,则必须指定返回值模板类型。因为编译器无法推导出返回值类型
可以从左向右部分指定类型参数
//T1 = int,T2 = double,T3 = double
int r1 = Add(0.5,0.8);
//T1 = int,T2 = float,T3 = double
int r2 =Add(0.5,0.8);
//T1 = int,T2 = float,T3 = double
int r3 =Add(0.5,0.8);
开始试验多参数函数模板:
#include
using namespace std;
template
T1 Add(T2 a,T3 b)
{
return static_cast(a+b);
}
int main()
{
int a = Add(1,1.5); //该行编译出错,没有指定返回值类型
int a = Add(1,1.5);
cout<(1,1.5);
cout<
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8xT5Cr7D-1647411362275)(F:\C++\泛型编程\image-20220316103523833.png)]
#include
using namespace std;
template
T Max(T a,T b)
{
cout<<"T Max(T a,T b)"< b ? a : b;
}
template
T Max(T* a,T* b) //重载函数模板
{
cout<<"T Max(T* a,T* b)"< *b ? *a : *b;
}
int Max(int a,int b) //重载普通函数
{
cout<<"int Max(int a,int b)"< b ? a : b;
}
int main()
{
int a=0;
int b=1;
cout<<"a:b="<(a,b)<
和函数模板一样,将泛型思想应用于类。编译器对类模板处理方式和函数模板相同,都进行两次编译。
类模板通常应用于数据结构方面,使得类的实现不再关注数据元素的具体类型,而只关注需要实现的功能。
使用方法:通过template关键字来声明,然后通过typename关键字来定义模板类型
template
class operator
{
public:
T op(T a,T b);
};
类模板的使用:
operator op1;
operator op2;
int i = op1.op(1,2);
string s = op2.op("D.T.","Software");
初探类模板:
//实现不同类型的加减乘除
#include
#include
using namespace std;
template
class Operator
{
public:
T add(T a,T b)
{
return a+b;
}
T minus(T a, T b)
{
return a - b;
}
T multiply(T a, T b)
{
return a * b;
}
T divide(T a, T b)
{
return a / b;
}
};
string operator-(string& l,string& r)
{
return "Minus";
}
int main()
{
Operator op1; //定义对象时,需要指定类模板类型
cout< op2;
cout <
类模板的工程应用
#ifndef _OPERATOR_H
#define _OPERATOR_H
template < typename T >
class Operator
{
public:
T add(T a, T b);
T minus(T a, T b);
T multiply(T a, T b);
T divide(T a, T b);
};
template < typename T > //外部定义的成员函数,都需要加上模板声明
T Operator :: add(T a, T b) //同时加上结构体声明
{
return a+b;
}
template < typename T >
T Operator :: minus(T a, T b)
{
return a-b;
}
template < typename T >
T Operator :: multiply(T a, T b)
{
return a*b;
}
template < typename T >
T Operator :: divide(T a, T b)
{
return a/b;
}
#endif
多参数类模板:
类模板可以定义任意多个不同的类型参数,同时还要必须指定每个模板参数
template
class Operator
{
public:
void add(T1 a,T2 b);
};
template
void Operator::add(T1 a,T2 b)
{
cout<<(a+b)< op1; //定义op1对象时,必须指定类模板类型
op1.add(2,2.1);
return 0;
}
类模板也可以像函数重载一样,类模板通过特化的方式实现特殊情况。
类模板特化:
template < typename T1,typename T2 > //声明的模板参数个数为2个
class Operator //正常的类模板
{
public:
void add(T1 a, T2 b)
{
cout< //不需要指定模板类型,因为是完全特化的类模板
class Operator< int , int> //指定类型参数,必须为2个参数,和正常类模板参数个数一致
{
public:
void add(int a, int b)
{
cout< Op1; //匹配完全特化类模板:class Operator< int,int>
Operator Op2; //匹配正常的类模板
return 0;
}
template < typename T1,typename T2 > //声明的模板参数个数为2个
class Operator //正常的类模板
{
public:
void add(T1 a, T2 b)
{
cout< //有指定模板类型以及指定参数,所以是部分特化的类模板
class Operator< T* ,T*> //指定类型参数,必须为2个参数,和正常类模板参数个数一致
{
public:
void add(T* a, T* b)
{
cout<<*a+*b< Op1; //匹配部分特化: class Operator< T* ,T*>
Operator Op2; //匹配正常的类模板: class Operator
return 0;
}
编译时,会根据对象定义的类模板类型,首先去匹配完全特化,再来匹配部分特化,最后匹配正常的类模板。
初探类模板特化:
#include
using namespace std;
template < typename T1,typename T2 >
class Operator //正常的类模板
{
public:
void add(T1 a, T2 b)
{
cout<<"add(T1 a, T2 b)"<
class Operator //部分特化的类模板,当两个参数都一样,调用这个
{
public:
void add(T a, T b)
{
cout<<"add(T a, T b)"<
class Operator //部分特化的类模板,当两个参数都是指针,调用这个
{
public:
void add(T1* a, T2* b)
{
cout<<"add(T1* a, T2* b)"<
class Operator //完全特化的类模板,当两个参数都是void*,调用这个
{
public:
void add(void* a, void* b)
{
cout<<"add(void* a, void* b)"< Op1; //匹配正常的类模板:class Operator
Op1.add(1,1.5);
Operator Op2; //匹配部分特化的类模板:class Operator
Op2.add(1,4);
Operator Op3; //匹配部分特化的类模板:class Operator
Op3.add(p1,p2);
Operator Op4; //匹配完全特化的类模板:class Operator
Op4.add(NULL,NULL);
delete p1;
delete p2;
return 0;
}
数值型模板参数:
之前,我们学习的模板参数都是带泛型的(表示不同类型),其实模板参数也可以是数值型参数。
template
void func()
{
T a[N]; //使用模板参数定义局部数组
}
func();
数值型模板参数必须在编译时被唯一确定
变量在运行期间是可变的,所以不能作为模板参数。类对象(可变)
通过数值参数的类模板来求 1+2+3+…+N的值:
#include
using namespace std;
template
class Sum
{
public:
static const int VALUE = Sum::VALUE + N;
};
template < >
class Sum < 1 >
{
public:
static const int VALUE = 1;
};
int main()
{
cout<<"1+2+3+...+10= "<::VALUE<::VALUE<
,T2>
Op3.add(p1,p2);
Operator
Op4.add(NULL,NULL);
delete p1;
delete p2;
return 0;
}
数值型模板参数:
之前,我们学习的模板参数都是带泛型的(表示不同类型),其实模板参数也可以是**数值型参数**。
```c++
template
void func()
{
T a[N]; //使用模板参数定义局部数组
}
func();
数值型模板参数必须在编译时被唯一确定
变量在运行期间是可变的,所以不能作为模板参数。类对象(可变)
通过数值参数的类模板来求 1+2+3+…+N的值:
#include
using namespace std;
template
class Sum
{
public:
static const int VALUE = Sum::VALUE + N;
};
template < >
class Sum < 1 >
{
public:
static const int VALUE = 1;
};
int main()
{
cout<<"1+2+3+...+10= "<::VALUE<::VALUE<