###引言:写一个交换的函数Swap,形参的类型会因为传入的实参的不同而需要改变,这样在频繁使用交换函数并且传入的实参的类型不同时会很麻烦,要写很多重载函数。而重载函数较多时会出现代码复用率较低和可维护性较低的问题;
在C++里面引入了模板的概念,模板可以用来实例化函数,让函数的形参类型根据实参的不同来生成,这样,利用一个函数模板就可以频繁操作不同类型的实参了。
模板是泛型编程的基础(泛型编程是与类型无关的通用代码,是代码复用的一种手段)
###模板分为函数模板和类模板,这里简单介绍:
函数模板
1、函数模板的引入:
以交换函数Swap为例子:
void Swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
这是基本的交换函数,只能接收int类型的实参才能正常使用;这里引入函数模板,让Swap函数可以正常操作不同类型的实参:
template<class T>
void Swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 1;
int b = 20;
Swap(a, b);
cout << "a = " << a << endl << "b = " << b << endl;
double c = 1.2;
double d = 2.22;
Swap(c, d);
cout << "c = " << c << endl << "d = " << d << endl;
return 0;
}

上面代码就是使用了函数模板,让Swap能够接收不同类型的实参;
函数模板的语法规则:
template<class T1, class T2,class T3........ class T3>
函数返回类型+函数名+参数列表
在声明一个class是来定义模板参数的关键字,也可以使用 typename (即 template<typename T1>)
不能使用struct来定义模板参数;
函数模板时可以class和typename都使用;
2、函数模板的原理:

函数模板是一个蓝图,它本身不是函数,而是编译器根据它来生成有具体函数参数的模板,使用模板省去了我们的重复工作。
3、函数模板参数的实例化:
1、隐式实例化:
隐式实例化:让编译器根据实参来推到函数参数的实际类型;
利用上文中Swap的函数模板来举例,在编译阶段,函数根据你传过去的实参的具体类型来确定T到底是什么,让后生成一个函数,这个函数的参数类型确定了,这就是实例化;
传入的是两个double类型的实参,那么T在函数模板生成的函数中就是double,若是其他类型,实例化之后,T就是那个传入的类型;
代码示例:
template<typename T1,typename T2>
T1 Add(T1& x, T2& y)
{
return x + y;
}
int main()
{
int a = 1;
double b = 2.22;
cout << Add(a, b) << endl;
return 0;
}
a是int类型的,对应着T1,那么模板生成的函数里面的T1就是int,同理,T2是double类型的;
但是要注意:有时候推断会出问题:


在这个例子中函数模板只有一个定义模板参数的关键字,函数模板的形参部分都是相同的参数T,在传入时,一个类型是int,一个是double,那么编译器就不知道要把T推断成哪个类型的参数,所以就会出错;
为了解决这种问题,有两个办法:强转类型或者显示实例化;
2、显示实例化:
显示实例化:在函数名后面加个<>,<>里面写上类型,对应着函数模板里面的参数,这个参数的类型就是<>里面类型;
<>里面可以写多个要显示转换的类型;
template<class T>
T Add(T& x, T& y)
{
return x + y;
}
int main()
{
int a = 1;
double b = 2.22;
//显示实例化:
Add<int>(a, b);
return 0;
}
若是类型不一样,编译器会将类型都隐式类型转换为<>里面的类型;若是转换不成功,会报错;
4、模板参数匹配规则:
如果一个函数和一个能实例化成这个函数的函数模板同时存在,那么在调用的时候,会先直接调用函数,这是因为模板函数多了生成的一个过程,直接调用现存函数效率更高;
但是当函数模板生成的函数要优于这个函数时,会调用函数模板生成的函数;
代码示例:
int Add(int& x, int& y)
{
return x + y;
}
template<class T>
T Add(T& x,T& y)
{
return x+y
}
这个就是先调用函数,因为函数模板并不优于函数(示例化出来的参数不匹配)
int Add(int& x, int& y)
{
return x + y;
}
template<class T1,class T2>
T1 Add(T1& x, T2& y)
{
return x + y;
}
这个就会先调用函数模板,因为函数模板更优,可以生成两个不同类型的参数。
类模板:
类模板格式:
template<class T>
class +类名
{
//类中成员的定义
}
写一个栈的类模板,让这个栈里面存储的数据可以是多种类型的数据;
#include<iostream>
using namespace std;
//类模板
template<class T>
class Stack
{
public:
Stack(int n=4)
:_array(new T[n])
,_size(0)
,_capacity(n)
{}
~Stack()
{
delete[] _array;
_array = nullptr;
_size = _capacity = 0;
}
void Push(const T& x)
{
if (_size == _capacity)
{
T* tmp = new T[2 * _capacity];
memcpy(tmp, _array, sizeof(T) * _size);
delete[] _array;
_array = tmp;
_capacity *= 2;
}
_array[_size++] = x;
}
void Print()
{
for (int i = 0; i < _size; i++)
{
cout << _array[i] << " ";
}
cout << endl;
}
private:
T* _array;
int _size;
int _capacity;
};
int main()
{
Stack<int> st1;//类模板实例化
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
st1.Push(5);
st1.Print();
Stack<double> st2;//类模板实例化
st2.Push(1.1);
st2.Push(2.1);
st2.Push(3.1);
st2.Push(4.1);
st2.Push(5.1);
st2.Print();
return 0;
}
上述代码中Stack就是一个类模板,这个类模板中的数据是T类型的,在我们实例化时指定的数据类型后,T就是这个指定的数据类型;
类模板必须显示实例化,并且类模板的声明和定义不建议分离到头文件和源文件中,这样会有链接错误;
类模板不是真正的类,利用类模板实例化出来的类才是真正的类。
除此之外,类模板中的成员函数可以定义在类外面:但是要在定义的前面声明模板,并且作用域的范围是类模板Stack,Stack后面还要加上<T>,这个T实际就是类模板里面的T,但是还可以起其他名字,表示的意思还是不变的:
#include<iostream>
using namespace std;
template<class T>
class Stack
{
public:
Stack(int n=4)
:_array(new T[n])
,_size(0)
,_capacity(n)
{}
~Stack()
{
delete[] _array;
_array = nullptr;
_size = _capacity = 0;
}
void Push(const T& x);
void Print()
{
for (int i = 0; i < _size; i++)
{
cout << _array[i] << " ";
}
cout << endl;
}
private:
T* _array;
int _size;
int _capacity;
};
template<class T>//还要声明,T可以换成其他字母,代表的意思相同
void Stack<T>:: Push(const T& x)//Stack<T>::指定在哪个类模板里面
{
if (_size == _capacity)
{
T* tmp = new T[2 * _capacity];
memcpy(tmp, _array, sizeof(T) * _size);
delete[] _array;
_array = tmp;
_capacity *= 2;
}
_array[_size++] = x;
}
536

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



