🤓泛型编程
- 假设像以前交换两个函数需要,函数写很多个或者要重载很多个;那么有什么办法实现一个通用的函数呢?
void Swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
void Swap(double& x, double& y)
{
double tmp = x;
x = y;
y = tmp;
}
void Swap(char& x, char& y)
{
char tmp = x;
x = y;
y = tmp;
}
- 虽然函数重载可以实现,但是总归不太好
- 可以发现函数重载虽然可以使用同一个函数名,避免不了复用率比较低,假如有新的类型 还是需要再写对应类型函数
- 函数多了可维护性就会变差,一个出错可能其他的重载都会有问题
- 所以下面就用到了一个函数模板的,解决这个问题
泛型编程:编写与类型无关通用的代码,是代码复用的一种手段,模板是泛型编程的基础
🤓函数模板
概念:函数模板代表一个函数家族,该函数与类型无关,在使用时被参数化,根据实参类型产生函数的特定版本类型
☝️ 函数模板格式
template<typename T1,typename T2,...,typename Tn>
返回值类型 函数名(参数列表){}
注意:typename是用来定义模板参数的关键字,还可以写成class(但是不可以写成struct,不能使用struct来代替)
☝️ 函数模板的原理
- 函数模板是一个蓝图,本身就不是一个函数,编译器使用了某种方式产生特定具体类型函数的模具,所以本来需要写很多次函数重载的活或者其他重复的事情都交给了编译器
- 在编译器编译阶段,对于模板的使用,编译器需要根据传过来的实参类型来推导生成对应函数类型,提供调用;如int类型的实参使用函数模板时,编译器通过对应类型推演,推导出T类型是int类型,从而产生一份专门处理int类型的函数,对于double类型也是这样的;
虽然编译器麻烦一点,但是我们就可以少写一些了,岂不美哉
☝️ 函数模板的实例化
- 不同的类型使用函数模板时,这个过程称之为函数模板的实例化,函数模板的实例化分为两种:隐式实例化和显式实例化
- 隐式模板实例化:通过传递的实参类型推导出对应函数的实际类型
template <class T>
void Swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}
template <typename Type1,typename Type2>
void fun(Type1& x,Type2& y)
{
}
int main()
{
int x1 = 10,x2 = 20;
double y1 = 10.1,y2 = 20.1;
Swap(x1, x2);
Swap(y1, y2);
//Swap(x1, y1); 这样就不行了,函数模板只写了一个T的关键字,必须是同一个类型
cout << y1 << " " << y2 << endl;
//C++自带的swap函数
swap(x1, x2);
swap(y1, y2);
//如果两个参数不同,就需要有两个关键字
fun(x1, y1);
return 0;
}
- 显示实例化:
#include <iostream>
using namespace std;
template<class T>
T Add(const T& x, const T& y)//这里需要写const 不然不能使用强制转换
{
return x + y;
}
//可以写多个函数模板;如果不想强制转换和显示实例化可以写两个关键字的
template<class T1,class T2>
T1 Add(T1& num1, T2& num2)
{
return num1 + num2;
}
template<class N>
N* apply(int x)//没有传入的实参类型,意思是的位置没有用到 N 推导不了
{
N* tmp = new N[x];//N 用做了类型参数
return tmp;
}
int main()
{
int su1 = 10, su2 = 20;
double xu1 = 10.2, xu2 = 20.2;
Add(su1, su2);
Add(xu1, xu2);
//隐式实例化; 强制类型转换
cout << Add(su1,(int)xu1) << endl;
cout << Add((double)su1,xu1) << endl;
//显示实例化,直接告诉 T 的类型,编译器不需要推导了
cout << Add<double>(su1,xu1)<< endl;//这里的su1 会有隐式类型转换
cout << Add<int>(su1,xu1) << endl;
cout << Add(su2, xu2) << endl;
//这里必须显示写实例化
double* space = apply<double>(20);
//隐式类型转换不是想转就能转的,比如:指针,一般都是和int相关的类型
char a = 'b';
int ia = 2100;
cout << (int)a << endl;
cout << (char)ia << endl;
return 0;
}
- 编译器一般不会自己进行隐式类型转换,怕出事
- 函数模板实例化,如果其他条件相同的情况下,会优先调用非模板函数;如果模板函数实例化出更好的,那么会选择模板;所以说什么好选择什么
- 函数模板对自定义类型不能隐式类型转换,对于普通的类型会自动隐式类型转换
#include <iostream>
using namespace std;
int Add(const int& x,const int& y) //这里优先调用
{
return 20 * (x + y);
}
template<class T>
T Add(const T& x, const T& y)//这里需要写const 不然不能使用强制转换
{
return x + y;
}
int main()
{
int su1 = 10, su2 = 20;
Add(su1, su2);
}
🤓类模板
☝️ 类模板的定义格式
template<class T1, class T2, ..., class Tn>
class 类模板名
{
// 类内成员定义
};
#include <iostream>
using namespace std;
template<typename T>
class Stack
{
public:
Stack(int n = 4)
:_array(new T[n])// T 就是代表的是模板实例化的类型
, _size(0)
, _capacity(n)
{
}
void Push(const T& p);
~Stack()
{
delete[] _array;
_array = nullptr;
_size = _capacity = 0;
}
private:
T* _array;
size_t _size;
size_t _capacity;
};
template<typename T> //如果是声明和定义分离 就需要再写一个类模板
void Stack<T>::Push(const T& p)//告诉类中模板类型是什么
{
if (_size == _capacity)
{
T* tmp = new Stack[_capacity * 2];
memcpy(tmp, _array, sizeof(T) * _size);//拷贝内容
delete[] _array;//释放旧空间
_array = tmp;
_capacity = _capacity * 2;
}
_array[_size++] = p;
}
int main()
{
Stack<int> a1;
return 0;
}
- 模板的定义和声明不建议放到两个不同的文件 .h 和 .cpp;会出现链接错误
☝️ 类模板的实例化
- 模板类是需要显示实例化的
- 和函数模板实例化不同,类模板的名字不是真正的类,而实例化的结果才是真正的类
int main()
{
// Stack 是类名,Stack<int> 是类型
Stack<int> a1;
Stack<double> a2;
return 0;
}
頑張ろ