目录
- 1.函数模板
- 1.1函数模板概念
- 1.2函数模板格式
- 1.3函数模板的原理
- 1.4函数模板实例化
- 隐式实例化
- 显示实例化
- 1.5模板参数适配原则
- 2.类模板
- 2.1类模板的定义格式
- 2.2类模板实例化
- 总结
1.函数模板
如何实现一个通用的交换函数呢?
#include<iostream>
using namespace std;
void Swap(int& p1, int& p2)
{
int tmp = p1;
p1 = p2;
p2 = tmp;
}
void Swap(double& p1, double& p2)
{
double tmp = p1;
p1 = p2;
p2 = tmp;
}
void Swap(char& p1, char& p2)
{
char tmp = p1;
p1 = p2;
p2 = tmp;
}
int main()
{
int a = 10, b = 20;
cout << "a=" << a << " " << "b=" << b << endl;
Swap(a, b);
cout << "a=" << a << " " << "b=" << b << endl;
double c = 10.1, d = 20.2;
cout << "c=" << c << " " << "d=" << d << endl;
Swap(c, d);
cout << "c=" << c << " " << "d=" << d << endl;
char x = 'a', y = 'b';
cout << "x=" << x << " " << "y=" << y << endl;
Swap(x, y);
cout << "x=" << x << " " << "y=" << y << endl;
return 0;
}
代码运行的结果为:
使用函数重载虽然可以实现,但是有一下几个不好的地方:
- 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
- 代码的可维护性比较低,一个出错可能所有的重载均出错
我们可以使用以下代码实现:
template<class T>
void Swap(T& p1, T& p2)
{
T tmp = p1;
p1 = p2;
p2 = tmp;
}
int main()
{
int a = 10, b = 20;
cout << "a=" << a << " " << "b=" << b << endl;
Swap(a, b);
cout << "a=" << a << " " << "b=" << b << endl;
double c = 10.1, d = 20.2;
cout << "c=" << c << " " << "d=" << d << endl;
Swap(c, d);
cout << "c=" << c << " " << "d=" << d << endl;
char x = 'a', y = 'b';
cout << "x=" << x << " " << "y=" << y << endl;
Swap(x, y);
cout << "x=" << x << " " << "y=" << y << endl;
return 0;
}
代码运行的结果为:
上面的代码也可以实现不同类型的交换,其中涉及了模板的知识点,接下来我们一起学习一下。
1.1函数模板概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。
1.2函数模板格式
template<typename T1, typename T2,......,typename Tn>
返回值类型 函数名(参数列表){}
template<typename T>//模板参数--类型
void Swap( T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
其中,typename
是用来定义模板的关键字,也可以使用class
,不能使用struct
代替class
。
1.3函数模板的原理
在上面的代码中,我们使用函数模板调用不同类型的数据进行交换,调用的是同一个函数吗?
汇编代码:
int a = 10, b = 20;
001227FF mov dword ptr [a],0Ah
00122806 mov dword ptr [b],14h
//cout << "a=" << a << " " << "b=" << b << endl;
Swap(a, b);
0012280D lea eax,[b]
00122810 push eax
00122811 lea ecx,[a]
00122814 push ecx
00122815 call std::operator<<<std::char_traits<char> > (0121460h)
0012281A add esp,8
double c = 10.1, d = 20.2;
0012281D movsd xmm0,mmword ptr [__real@4024333333333333 (0129B48h)]
00122825 movsd mmword ptr [c],xmm0
0012282A movsd xmm0,mmword ptr [__real@4034333333333333 (0129B58h)]
00122832 movsd mmword ptr [d],xmm0
//cout << "c=" << c << " " << "d=" << d << endl;
Swap(c, d);
00122837 lea eax,[d]
0012283A push eax
0012283B lea ecx,[c]
0012283E push ecx
0012283F call Swap<char> (012145Bh)
00122844 add esp,8
在编译器编译阶段,编译器根据传入的参数,推导出对应的模板参数类型,从而实例化出具体的函数!
函数模板是一个蓝图,它本身并不是函数,是编译器用使用方式产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
1.4函数模板实例化
实现Add()
函数不同类型的数据相加
eg1:
template <class T1,class T2>
T1 Add(T1& x1, T2& x2)
{
return x1 + x2;
}
int main()
{
int a = 10;
double d = 20.1;
cout << Add(a, d) << endl;
return 0;
}
代码运行的结果为:
eg2:
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a = 10;
double d = 20.1;
cout << Add(a, d) << endl;
return 0;
}
代码编译运行的结果:
当函数模板只有一个参数时,而传递不同类型的实参,编译器因调用不明确而无法推导实例化出具体函数而报错,这就涉及函数模板实例化问题。
函数模板实例化概念:
用不同类型的参数使用函数模板时,称为函数模板的实例化。模板参数实例化分为:隐式实例化和显式实例化
隐式实例化
隐式实例化:编译器根据实参推演模板参数的实际类型。
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a = 10;
double d = 20.1;
cout << Add(a, (int)d) << endl;//强制类型转换
return 0;
}
通过强制类型转换,是传递的数据为同一类型,编译器便可以实例化出具体的函数,需要注意的是强制类型转换的临时变量具有常性,函数参数需要加const修饰,以上便是隐式实例化的过程。
显示实例化
显示实例化:在函数名后的
<>
中指定模板参数的实际类型
eg1:
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
void Test()
{
int a = 10;
double d = 20.1;
cout << Add<int>(a, d) << endl;
}
代码编译运行的结果为:
如果类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功编译将会报错。
eg2:
template<class T>
T* Alloc(int n)
{
return new T[n];
}
int main()
{
int* p = Alloc(10);
return 0;
}
以上的代码就不能通过隐式实例化调用模板函数,需要通过函数实例化才能调用。
正确的调用方式:
T* Alloc(int n)
{
return new T[n];
}
int main()
{
int* p = Alloc<int>(10);
return 0;
}
1.5模板参数适配原则
1. 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非模板函数匹配,编译器不需要特化
Add<int>(1, 2); // 调用编译器特化的Add版本
}
2. 对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么将选择模板
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
// 通用加法函数
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
return left + right;
}
void Test()
{
Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
}
3. 模板函数不允许自动类型转换,但普通函数可以进行自动类型转换
// 专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
void Test()
{
Add(1, 2.0); // 调用普通函数,编译器会自动转换类型
}
//同类型加法函数
template<class T>
T Add(const T& left, const T& right)
{
return left + right;
}
void Test()
{
Add(1, 2.0);
//调用模板函数,编译器不会自动强转而报错,需要自己手动强转或显示实例化
}
2.类模板
2.1类模板的定义格式
eg1:
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
实现不同类型的栈:
typedef int DataType;
class StackInt
{
public:
StackInt(size_t capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 其他方法...
~StackInt()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
typedef double DataType;
class StackDouble
{
public:
StackDouble(size_t capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 其他方法...
~StackDouble()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
StackInt s1; // int
StackDouble s2; // double
return 0;
}
平时我们实现要使用栈存储不同类型的数据的时候,需要不同的栈的才能实现,需要对已有其他类型的栈进行相应的修改,即类名以及函数名都要修改;当我们需要添加不同功能函数时,不同的栈都要实现一遍,我们可以使用类模板解决这个问题!
eg2:
template<class T>
class Stack
{
public:
Stack(size_t capacity = 3)
{
_array = (T*)malloc(sizeof(T) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(T data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 其他方法...
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
T* _array;
int _capacity;
int _size;
};
int main()
{
Stack<int> s1; // int
Stack<double> s2; // double
return 0;
}
使用类模板实现不同类型的栈,需要显示实例化。
eg3:
template<class T>
class Stack
{
public:
//声明
Stack(size_t capacity = 3);
void Push(T data);
// 其他方法...
~Stack();
private:
T* _array;
int _capacity;
int _size;
};
//定义
template<class T>
Stack<T>::Stack(size_t capacity)
{
_array = (T*)malloc(sizeof(T) * capacity);
if (NULL == _array)
{
perror("malloc申请空间失败!!!");
return;
}
_capacity = capacity;
_size = 0;
}
template<class T>
void Stack<T>::Push(T data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
template<class T>
Stack<T>::~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
int main()
{
Stack<int> s1; // int
Stack<double> s2; // double
return 0;
}
使用类模板,要实现类成员函数声明和定义分离:①要指定函数的类模板域,如上代码需要在函数名前面加上
Stack<int>::
②类模板的作用域在最近的{}
域里面,在类外面实现成员函数,需要结合类模板一起实现。
2.2类模板实例化
类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>
,然后将实例化的类型放在<>
中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
//类模板与普通类的区别
//普通类:类名和类型一样,如用普通类实现栈,类名和类型都为Stack
//类模板:类名和类型不同,如用类模板实现栈,类名为Stack,类型为Stack<T>
//类模板实例化
Stack<int> s1; // int
Stack<double> s2; // double
总结
本章我们一起初步学习了C++模板的相关知识,希望对大家了解C++模板有些许帮助,感谢大家阅读,如有不对欢迎纠正!🎠🎠🎠