1.什么是模板?
在我们学C++的模板之前,从我们平常的想法来看,模板就是一种固定的格式,是一种模具,根据这个模具就可以做出多类似甚至相同的物品。我们在编写代码时,有一些功能类似的函数等,比如像交换函数,在不同情况下我们需要写不同的交换函数,但它们的功能是类似的,都是交换两个(多个)变量,那么在C++中是否存在这样一个模具,让我们仅用一个函数来实现不同类型变量的交换呢?这就需要用到我们的模板了,模板也是泛型编程的基础,它被分为函数模板和类模板两种。
2.函数模板
2.1 概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
2.2 格式
template<typename T1,typename T2.... ...,typename Tn>
返回值 函数名(参数列表){}
//注:typename是定义模板参数的关键字,也可以用class,
//我们这里暂时认为它们两个作用相同(但不能用struct替换class)
如:
template<typename T>
void my_Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
int main()
{
int a = 1;
int b = 2;
my_Swap(a, b);
cout << "a:" << a << " " << "b:" << b << endl;
double x = 3;
double y = 4;
my_Swap(x, y);
cout << "x:" << x <<" " << "y:" << y << endl;
return 0;
}
运行结果如下:
我们发现,通过该函数模板可以实现不同类型但俩同种类型变量的交换。
但是如果两种要交换的变量类型不同,仅用一个模板参数是不够的,比如我们去交换x 和 a(double 和 int):
这时候我们就需要用到多个模板参数:
template<typename T1,typename T2>//两个模板参数
void my_Swap(T1& left, T2& right)
{
T1 temp = left;
left = right;
right = temp;
}
int main()
{
int a = 1;
double x = 3.3;
my_Swap(x, a);
cout << "x:" << x <<" " << "a:" << a << endl;
return 0;
}
//以上仅做示例用
注意这里交换只会交换值,而不会交换类型,因此3.3会整形化给a:
2.3 函数模板的原理
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型类推演生成对应类型的函数以供调用:
2.4 函数模板的实例化
用不同类型的半数使用函数模板被称为函数模板的实例化,模板参数实例化分为:隐式实例化和显示实例化
-
隐式实例化:就是让编译器根据实参自己推断模板参数的类型:
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
double d1 = 10.0, d2 = 20.0;
Add(a1, a2);//自动推导模板参数类型为int
Add(d1, d2);//自动推断模板参数类型为double
return 0;
}
但是如果我们将两个不同类型的参数相加,会出现编译错误:
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10;
double d1 = 10.0;
Add(a1, d1);//两种不同类型的参数相加
return 0;
}
这是因为当编译器看到该实例化时,需要推演其实参类型通过实参a1将T推演为int,通过实参d1将T推演为double类型,但模板参数列表中只有一个T,编译器无法确定此处到底该将T确定为int 或者 double类型而报错。那么我们怎么解决这个问题呢?有两种方法:
a.我们通过自己强转使得两种参数一样:
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10;
double d1 = 10.0;
Add(a1, (int)d1);//手动强转
return 0;
}
b.通过显示实例化,下面我们就来讲讲显示实例化
2. 显示实例化:在函数名后<>中指定模板参数的类型:
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10;
double d1 = 10.0;
Add<int>(a1, d1);//<类型>显示实例化模板参数为int
return 0;
}
//注:如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译器将会报错。
2.5 模板参数的匹配原则
1.非模板函数可以与一个同名的函数模板同时存在,而且该函数模板可以被实例化成该非模板函数:
//非模板函数(处理int + int)
int Add(int left, int right)
{
return left + right;
}
//非模板函数(处理double + double)
double Add(double left, double right)
{
return left + right;
}
// 模板函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
int main()
{
//与非模板函数匹配,优先使用非模板函数
Add(1, 2);
Add(1.0, 2.0);
//使用函数模板特化版本
Add<int>(1, 2);
Add<double>(1.0, 2.0);
return 0;
}
//可以自行调试查看各自调用的哪个函数
2.对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数, 那么将选择模板:
//非模板函数(处理int + int)
int Add(int left, int right)
{
return left + right;
}
// 模板函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
return left + right;
}
int main()
{
Add(1, 2); // 与非函数模板类型完全匹配,直接调用非函数模板函数
Add(1, 2.0); // 模板函数可以生成更加匹配的版本即int+double,编译器根据实参生成更加匹配的Add(int ,double)
return 0;
}
//可以自行调试查看各自调用的哪个函数
3.模板函数不允许自动类型转换,普通函数可以:
//非模板函数(处理int + int)
int Add(int left, int right)
{
return left + right;
}
int main()
{
Add(1, 2.0);//非模板函数支持自动类型转化
return 0;
}
//模板函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
int main()
{
Add(1, 2.0);//模板函数不支持自动类型转化
return 0;
}
//报错如下:
3. 类模板
3.1 定义格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类成员定义(成员变量、成员函数)
};
示例:
//动态顺序表类模板
template<class T>
//注意 这里的Vector是类模板 而非一个具体的类
class Vector {
public:
Vector(size_t capacity = 10)
: _pData(new T[capacity])
, _size(0)
, _capacity(capacity) {}
~Vector();
// ...其他成员函数
private:
T* _pData;
size_t _size;
size_t _capacity;
};
注意:类模板中的函数放在类外定义时,需要添加模板参数列表:
//这里以析构函数为例子,类外定义添加模板参数列表
template <class T>
Vector<T>::~Vector()
{
if (_pData)
{
delete[] _pData;
}
_size = _capacity = 0;
}
3.2 类模板的实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
// Vector只是类名,Vector<int>才是类型
Vector<int> v1;
Vector<double> v2;
注意区分 类模板 和 模板类,通过类模板实例化出来的结果称为模板类