1.定义
- 当我们在写代码的时候可能会经常用到函数重载,但是函数重载的代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数。代码的可维护性比较低,一个出错可能所有的重载均出错。所以模板的出现可以有效解决上面问题。
- 模板是泛型程序的设计基础(泛型 generic type)
2.模板的分类
- c++的模板总共分为两类:
- 函数模板
- 类模板
- 函数模板的格式
格式:template<typename T1, typename T2.........typename Tn>
返回值类型 函数名(参数列表) {}
template<typename T>
void swap( T& left T& right)
{
T temp=left;
left=right;
right=left;
}
- 类模板的格式
格式:template<typename T1, typename T2...........typename Tn>
class 类模板名{};
3.函数模板
1.原理
- 函数模板是一个蓝图,它本身并不是函数。是编译器产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。
#include <iostream>
using namespace std;
template<typename T>
void Swap(T& left, T& right)
{
T temp = left;
left = right;
right = temp;
}
int main()
{
int x = 2, y = 3;
cout << x << " " << y << endl << endl;
Swap(x, y);
cout << x << " " << y << endl << endl;
char a = 'a', b = 'b';
cout << a << " " << b << endl << endl;
Swap(a, b);
cout << a << " " << b << endl << endl;
double c = 2.1, d = 3.1;
cout << c << " " << d << endl << endl;
Swap(c, d);
cout << c << " " << d << endl << endl;
return 0;
}
通过上面代码我们可以看到,不同的类型在使用模板后依旧能进行转换,但是从反汇编中可以看到,实际上这三个调用的不是同一个函数。
在编译器编译阶段,对于模板函数的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数以供调用。比如:当用double类型使用函数模板时,编译器通过对实参类型的推演,将T确定为double类型,然后产生一份专门处理double类型的代码,对于字符类型也是如此。
2.函数模板的实例化
- 用不同类型的参数使用函数模板时,称为函数的实例化,函数的实例化分为:
- 隐式实例化
- 显式实例化
- 显示实例化:对于函数模板而言,不管是否发生函数调用,都可以通过显示实例化声明将函数模板实例化。
template [函数返回类型] [函数模板名]<实际类型列表>(函数参数列表)
template void func<int>(const int&);
- 当函数模板里面的参数不是同一个类型的时候,编译器无法确定这里的T是int还是double,编译会报错。
#include <iostream>
using namespace std;
template<typename T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
int a1 = 10, a2 = 20;
int a3=Add(a1, a2);
cout << a3 << endl;
//两个int类型,可以运行
double b1 = 10.5, b2 = 20.5;
double b3 = Add(b1, b2);
cout << b3 << endl;
//两个double类型,可以运行
cout << Add(a1, b1) << endl;
//一个int和一个double,不可以运行
return 0;
}
解决这个方式的办法有两种:
1.强制类型转换
int main()
{
int a1 = 10, a2 = 20;
int a3=Add(a1, a2);
cout << a3 << endl;
//两个int类型,可以运行
double b1 = 10.5, b2 = 20.5;
double b3 = Add(b1, b2);
cout << b3 << endl;
//两个double类型,可以运行
cout << Add(a1, (int)b1) << endl;//把b1或者a1转换为同一类型的变量
return 0;
}
2.显示类型转换
int main()
{
int a1 = 10, a2 = 20;
int a3=Add(a1, a2);
cout << a3 << endl;
//两个int类型,可以运行
double b1 = 10.5, b2 = 20.5;
double b3 = Add(b1, b2);
cout << b3 << endl;
//两个double类型,可以运行
cout << Add<int>(a1,b1) << endl;//在Add后面加上<int>或者<double>
return 0;
3.函数模板支持多个参数模板
template<class K, class V> //两个模板参数
void Func(const K& key, const V& value)
{
cout << key << ":" << value << endl;
}
int main()
{
Func(1, 1); //K和V均int
Func(1, 1.1);//K是int,V是double
Func<int, char>(1, 'A'); //多个模板参数也可指定显示实例化不同类型
}
4.函数模板的匹配
- 原则1: 一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数
//专门处理int的加法函数
int Add(int left, int right)
{
return left + right;
}
//通用加法函数模板
template<class T>
T Add(T left, T right)
{
return left + right;
}
int main()
{
Add(1, 2); //当有两个Add函数的时候,系统会优先调用非模板的函数
//用显示定义来让系统强行调用函数模板
Add<double>(1.1 , 2.2);
}
- 原则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;
}
int main()
{
Add(1, 2); // 与非函数模板类型完全匹配,不需要函数模板实例化
Add(1, 2.0); // 模板函数可以生成更加匹配的版本,编译器根据实参生成更加匹配的Add函数
return 0;
}
4.类模板
1.原理
- 类模板是对成员数据类型不同的类的抽象,它说明了类的定义规则,一个类模板可以生成多种具体的类。与函数模板的定义形式类似, 类模板也是使用template关键字和尖括号“<>”中的模板形参进行说明,类的定义形式与普通类相同。
在没学习过模板的时候,如果要建立多个不同类型的栈的话,你就需要多个不同类型的栈。
typedef int DataType; //如果你还想要一个其他类型的栈的话,就需要重新再写一个栈
class Stack
{
public:
// 构造函数
Stack(int capacity = 3) //初始化列表
:_array(new DataType[capacity]) // 开辟一个DateType的动态数组,并进行初始化
, _capacity(capacity)
,_size(0)
{}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
// 其他方法...
~Stack()
{
delete[]_array;
_array = nullptr;
_size = _capacity = 0;
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack s1;
return 0;
}
但是用模板的话,就可以只用一个模板能生成多个不同类型的栈。
template<class T1>
class Stack
{
public:
// 构造函数
Stack(int capacity = 4)
:_a(new T1[capacity])
,_capacity(capacity)
,_size(0)
{}
void Push(T1 data)
{
_a[_size] = data;
_size++;
}
// ...其他方法
// 析构函数
~Stack()
{
delete[]_a;
_a = nullptr;
_capacity = _size = 0;
}
private:
T1* _a;
int _capacity;
int _size;
};
int main()
{
Stack<int> s1;
Stack<double> s2; //一个函数就可以同时创造多个不同类型的栈
return 0;
}
2.类模板的实例化
- 类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<> 中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。
// Vector类名,Vector<int>才是类型
Vector<int> s1;
Vector<double> s2;
3.类模板的定义与声明
- c++类的声明与定义与声明与c++的类和对象不同,我们要用到函数模板里的显式实例化。
//这是stack.h 文件。里面存放了声明
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
using namespace std;
template<class T1>
class Stack
{
public:
// 构造函数
Stack(int capacity = 4);
//插入函数
void Push(T1 data);
// 其他方法....
// 析构函数
~Stack();
private:
T1* _a;
int _capacity;
int _size;
};
//此时是在Stack.cpp里,我们要使用声明的话要用显式实例化
#include "Stack.h"
template<class T1>
// 构造函数
Stack<T1>::Stack(int capacity)
:_a(new T1[capacity])
,_capacity(capacity)
,_size(0)
{}
template<class T1>
// 插入函数
void Stack<T1>::Push(T1 data) //显式实例化
{
_a[_size] = data;
_size++;
}
// 析构函数
template<class T1>
Stack<T1>::~Stack()
{
delete[]_a;
_a = nullptr;
_capacity = _size = 0;
}