1. 模板的引入
我们来看下面这几个函数:
void swap(int& left, int& right)
{
int tmp = left;
left = right;
right = tmp;
}
void swap(char& left, char& right)
{
char tmp = left;
left = right;
right = tmp;
}
void swap(double& left, double& right)
{
double tmp = left;
left = right;
right = tmp;
}
- C++中引入了函数重载,简化了我们对上述功能相同,细节不同函数的调用方式。
- 可是,从定义的角度出发,这写有着大量重复性逻辑代码,编写起来无疑是十分繁琐与冗余的。
- 上述几个函数,除了参数的类型之外没有任何的区别,因此,我们是否可以进行无关类型只针对逻辑的编程呢,由是,C++提出了泛型编程的概念引入了无关类型的编程方式,模板。
2. 函数模板与类模板
2.1 函数模板
- 正如其名,这种方式只是提供了一个模板,其并不是真正的函数,只有在被调用时,其类型才会确定下来,生成对应类型的函数。
- 模板参数的定义方式如下:(可定义多个模板参数)
template<typename T1, typename T2,.....>
返回值 函数名(参数列表)
{
//实现
}
- 函数模板生成的函数不是同一份,生成的函数都是被调用时,根据模板参数生成出来的。因此,对模板的使用只是我们将原本自己去做的事情交给了编译器去做,减少了我们的繁复不必要的操作。
template<typename T>
void swap(T& left, T& right)
{
T tmp = left;
left = right;
right = tmp;
}
4, 多参数类型模板参数的定义
template<typename T1, typename T2>
void Print(T1 num1, T2 num2)
{
cout << num1 << ' ' << num2 << endl;
}
2.2 模板调用方式
- 推演实例化调用:根据所给出的参数类型进行模板参数类型的推演
int num1 = 10;
int num2 = 20;
swap(num1, num2);
char c1 = 'a';
char c2 = 'b';
swap(c1, c2);
double num3 = 11.1;
double num4 = 22.2;
swap(num3, num4);
- 显示实例化调用的使用场景:
<1> 当模板无法根据参数类型推演出应该实例化的类型
<2> 当函数参数的类型,没有包含模板参数,无法进行推演
template <typename T>
T Add(T left, T right)
{
return left + right;
}
//无法推导出应该使用哪种类型,进行哪种类型的转换
cout << Add(10, 12.3) << endl;
//没有模板参数类型的函数参数,没有推导的依据
template <typename T>
T* f(int n)
{
T* data = new T[n];
}
- 显示实例化的调用方式:
//定义方式:
[函数]<类型>(参数列表)
//模板参数类型为int,向int做类型转换
Add<int>(10, 12.3);
//模板参数类型为double,向double做类型转换
Add<double>(10, 12.3);
//返回int类型的指针,指向一段大小为10的动态开辟的空间
int* pa = f<int>(10);
2.3 函数模板与普通函数的调用优先性
- 当同时存在类型符合的普通函数,与函数模板时,编译器会去调用哪一个,是否会发生冲突。
<1> 当存在类型符合存在的普通函数时,优先调用符合的普通函数(可以直接使用,不再生成)
<2> 当普通函数与调用的参数类型不同,则回调用函数模板
<3> 当不存在函数模板时,普通函数的类型与函数调用参数的类型不同时,会将参数转换为函数参数类型,然后再对函数进行调用
//场景1:
//优先
int Add(int num1, int num2)
{
return num1 + num2;
}
template<typename T>
T Add(T num1, T num2)
{
return num1 + num2;
}
cout << Add(10, 20) << endl;
//场景2:
double Add(double num1, double num2)
{
return num1 + num2;
}
//优先
template<typename T>
T Add(T num1, T num2)
{
return num1 + num2;
}
cout << Add(10, 20) << endl;
//场景3:
//类型转换为double后进行调用
double Add(double num1, double num2)
{
return num1 + num2;
}
cout << Add(10, 20) << endl;
2.4 类模板
- 函数之外,C++引入的类(自定义类型),也有着自己需要模板的应用场景
#define STDataType int;
class Stack
{
public:
Stack(int n = 4)
{
data = new STDataType[n];
top = 0;
capacity = n;
}
~Stack()
{
delete[] data;
capacity = 0;
top = 0;
}
private:
STDataType* data;
int top;
int capacity;
};
int main()
{
//宏的方式可以提高代码复用率
//存放int类型的数据
Stack st1;
//存放double类型的数据
Stack st2;
return 0;
}
宏的定义方式虽然可以提高代码的复用率,但以上的场景其无法解决,即同时存在两种存储不同类型的栈。
- 类模板的定义方式
//typename ,class关键字皆可,现阶段没有区别
//template <typename T>
template <class T1, class T2,......>
class [类名]
{
//实现
}
2.5 类模板的构造函数,类模板声明与定义分离
- 我们在之前类与对象的学习中,学习过类的默认成员函数,其中构造函数的函数名为类的名称,那么类模板的构造函数的函数名应该是什么呢。
- 构造函数的名称为类名,而类模板
[类名]<实例化调用类型>
为类型名,因此,构造函数名与普通类相同。
- 类模板的成员函数声明与定义分离方式:
<1> 必须在同一文件下,否则报错
<2> 分离出的定义部分,必须也要加模板参数的声明,声明类域时要使用类型名,具体如下
template<class T>
class [类名]
{
public:
//函数声明
[返回值] [函数名]();
}
//定义分离:
template<class T>
[返回值] [类名<T>]::[函数名]()
{
//实现
}
示例:
//定义在头文件中,头文件被多次展开会重复定义
template<class T>
class Stack
{
public:
Stack(int n = 4);
~Stack()
{
delete[] data;
capacity = 0;
top = 0;
}
private:
T* data;
int top;
int capacity;
};
template<class T>
Stack<T>::Stack(int n)
{
data = new T[n];
top = 0;
capacity = n;
}