C++模板
函数模板
我们看一段代码,我们要实现两个int的交换函数,可以这么实现
void Swap(int& left, int& right){
int temp = left;
left = right;
right = temp;
}
但是,我们想实现两个double或者char类型的加法,就得把这个函数再写两次
void Swap(int& left, int& right){
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right){
double temp = left;
left = right;
right = temp;
}
void Swap(char& left, char& right){
char temp = left;
left = right;
right = temp;
}
我们可以看出,这几个函数只是参数不同,内部的实现是完全相同的,这么写的话,我们的代码会很冗余,代码的复用性不高,为了解决这个问题,C++引入了函数模板来解决这个问题
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
函数模板的格式:
template<typename T1, typename T2,…,typename Tn>
返回值类型 函数名(参数列表){}
上面的交换函数就可以写为函数模板的形式,增强代码的复用性:
//其中typename也可以写成class,T就是被我们参数化的变量。
template<typename T>
void Swap(T& left, T& right) {
T tem = left;
left = right;
right = tem;
}
//上面的一块是一个整体统称函数模板
int main() {
int a1 = 88;
int b1 = 66;
char a2 = 'A';
char b2 = 'B';
//不同的变量参数经过函数模板的转化,就能生成不同的函数,实现不同类型的交换函数
Swap(a1, b1);
Swap(a2, b2);
return 0;
}
我们用函数模板编写一个不同类型的加法函数,我们可以把函数内所有可以用到类型的地方都用函数模板来传递参数。
template<typename T>
//返回值也可以用传入的参数T来代替
T add(T a, T b) {
return a + b;
}
int main() {
//自动推导参数类型并传参到T
add(55, 55);
return 0;
}
这种写法是隐式传递,T的类型是通过我们传入的55值推导出来的,我们也可以显式传递
template<typename T,typename T1, typename T2>
T add(T1 a,T2 b) {
return a + b;
}
int main() {
//显式传参的参数我们写到函数后面的<>内,参数可以传递多个,顺序对应上面的类型顺序
add<int, char, int>('A', 66);
add<int, double, int>(55.55, 66);
return 0;
}
这样的写法看似很简单,我们写起来也更加的方便,传入不同的参数,都是通过这个函数模板来运行,似乎效率也更高了。
但是其实和我们写多个函数并调用做的事情是差不多的
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。
所以说使用函数模板并不能提升代码的效率,只是让程序员写代码更加方便,后续的管理也更方便。
int add(int a, int b) {
return a + b;
}
template<typename T>
T add(T a, T b) {
return a + b;
}
int main() {
//调用具体函数
add(55, 33);
//只能调用函数模板
add<int>(66, 88);
return 0;
}
由于函数模板并没有提升效率,而且传入参数以后也要生成代码以后才能调用,所以我们函数模板和具体的函数同时存在时,会优先匹配具体的函数。
但是我们要是显式传递了int参数,就只能匹配函数模板生成代码后再调用。
类模板
类模板的用法和函数模板类似,也是在类的前面写上**template<class T1, class T2,…,class Tn>**把类实现为一个模板类。然后把需要的成员变量用参数传。
我们实现一个顺序表,其中的数据成员类型我们就可以用Type传入,实现由一个模板类完成不同数据成员的顺序表
template <class Type>
class SeqList {
public:
SeqList(Type sz)
:capacity(sz)
,base(new Type[capacity])
,size(0)
{}
void Push_Back(const Type& data) {
base[size++]=data;
}
void show() {
for (int i = 0; i < size; ++i) {
cout << base[i] << " ";
}
cout << endl;
}
~SeqList() {
delete[] base;
base = nullptr;
capacity = size = 0;
}
private:
Type* base;
size_t capacity;
size_t size;
};
/
int main() {
SeqList<int> lt1(10);
for (int i = 1; i <= 10; ++i) {
lt1.Push_Back(i);
}
lt1.show();
return 0;
}
要注意的是,要想把模板类的函数实现在类外,写法得像下面这样写
template <class Type>
class SeqList {
public:
SeqList(Type sz)
:capacity(sz)
,base(new Type[capacity])
,size(0)
{}
//类内声明
void Push_Back(const Type& data);
void show();
~SeqList() {
delete[] base;
base = nullptr;
capacity = size = 0;
}
private:
Type* base;
size_t capacity;
size_t size;
};
//
//类外实现的时候,要注意每个函数都要实现为模板函数,在写类作用域限定符的时候要写出完整的模板参数列表
template <class Type>
void SeqList<Type>::Push_Back(const Type& data){
base[size++] = data;
}
template <class Type>
void SeqList<Type>::show() {
for (int i = 0; i < size; ++i) {
cout << base[i] << " ";
}
cout << endl;
}
//
int main() {
SeqList<int> lt1(10);
for (int i = 1; i <= 10; ++i) {
lt1.Push_Back(i);
}
lt1.show();
return 0;
}
只要代码中有模板类或者模板函数,那么这份代码就不支持分离编译,也就是类和函数的声明和实现只能在一个文件中完成,不能头文件声明,源文件实现,如果分开编译,链接时就会发生链接错误。
|