一.引子:
问题:
交换下面的数据
1.两个int型
2.两个double型
3.两个char型
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(double& left, double& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(char& left, char& right)
{
int temp = left;
left = right;
right = temp;
}
我们发现要想交换这三组数,由于他们的数据类型不同,我们必须写三个不同的交换函数,
虽然C++引用(别名)传参解决了指针的一些弊端
以及函数重载让我们不用费心费力给相同功能的函数起名字
但是这样做依旧得写三个函数来实现两个变量的交换,这样很麻烦,怎么办呢?
C++创建者提出模板来解决这个问题:
//template<typename T>//typename<==>class
template<class T>
void Swap(T& left, T& right)
{
T tmp = left;
left = right;
right = tmp;
}
int main()
{
int a = 10, b = 20;
double x = 1.3, y = 2.4;
char m = 'a', n = 'b';
Swap(a, b);
Swap(x, y);
Swap(m, n);
return 0;
}
那么我们使用了模板之后,Swap用的是同一个函数吗?显然不是,为什么呢,请看下面.
二.模板的原理
模板的本质实际上是把 本来人要做的事(写三个函数) 交给 编译器 去做,让编译器代替我们写出这三个不同的函数.
当然,你展开标准命名空间后可以不用写swap函数,直接调用C++库里面的即可,这里只是举个例子方便我们理解模板,如果你想了解更多C++库里面的知识可以查阅帮助文档
帮助文档链接:https://cplusplus.com/reference/
三.模板使用过程中存在的问题
(一).问题1
1.推演实例化错误
请看下面这两段代码
int Add(const int& left, const int& right)
{
return left + right;
}
int main()
{
Add(1, 2);
Add(1.1, 2);
//由于传的是引用,所以并不是直接传参,而是先会有一份临时拷贝,这个临时拷贝对象具有常性
//double->const double->const int隐式类型转换会报警告,但是不会报错
return 0;
}
template<typename T>
T Add(const T& left, const T& right)
{
return left + right;
}
int main()
{
cout << Add(1, 2) << endl;
cout << Add(1.1, 2) << endl;//推演实例化报错
return 0;
}
第一段代码会报警告,隐式类型转换从couble->const int
第二段代码推演实例化错误, 1.1是double型, 2是int型
那T究竟是double还是int呢?编译器无法确定,因此会报错
2.三种解决方法
隐式实例化
cout << Add((int)1.1, 2) << endl;
cout << Add(1.1,(double)2) << endl;
显式实例化
cout << Add<int>(1.1, 2) << endl;
cout << Add<double>(1.1,2) << endl;
给模板设定两个参数(不推荐)
template<typename T1, typename T2>
T1 Add(const T1& left, const T2& right)
{
return left + right;
//1+2.2会发生整型提升
//整型提升:不够int的提到int
//超过int的提升为较大的那个
//这里被提升为了double
}
int main()
{
cout << Add(1, 2) << endl;
cout << Add(1.1, 2) << endl;
cout << Add(1, 2.2) << endl;
//这里会报警告,把参数传递过去后
return 0;
}
3.显式实例化的应用场景
template<class T>
T* Func(int n)
{
T* a = new T[n];//new n个T类型的对象
return a;
}
int main()
{
Func<int>(10);
}
Func函数参数里面没有T,编译器无法自行推演,因此需要显式实例化
(二).问题2
下面代码只是样例,不推荐写出这样的代码
//专门处理int型的加法函数
int Add(int left, int right)
{
return left + right;
}
//通用加法函数
template<class T>
T Add(T left, T right)
{
return left + right;
}
int main()
{
cout << Add(1, 2) << endl;//调第一个
cout << Add(1.1, 2.2) << endl;//调第二个
return 0;
}
//通用加法函数1
template<class T>
T Add(T left, T right)
{
return left + right;
}
//通用加法函数2
template<class T1, class T2>
T1 Add(T1 left, T2 right)
{
return left + right;
}
int main()
{
cout << Add(1, 2) << endl;//调第一个
cout << Add(1.1, 2.2) << endl;//调第一个
cout << Add(1, 2.2) << endl;//调第二个
return 0;
}
得出结论:编译器会调用现成的,尽量匹配的.
四.模板的具体应用-栈
typedef char STDataType;
class Stack
{
private:
STDataType* _a;
int top;
int capacity;
};
int main()
{
Stack st1;//int
Stack st2;//char
return 0;
}
要创建一个存放char,存放一个int的栈如果不用模板的化就得写两个栈
template<typename T>
class Stack
{
public:
//构造函数
Stack(size_t capacity = 0)
: _a(nullptr)
, _capacity(0)
, _top(0)
{
if (capacity > 0)
{
_a = new T[capacity];
_capacity = capacity;
_top = 0;
}
}
~Stack()
{
delete[] _a;
_a = nullptr;
_capacity = _top = 0;
}
void Push(const T& x)
{
if (_top == _capacity)
//栈满了或者栈是空的
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
//1.开新空间
//2.拷贝数据
//3.释放旧空间
T* tmp = new T[newCapacity];
//C++中尽量不要使用malloc,因为如果栈中存自定义类型的话,malloc无法访问类中的
//private变量,而new在创建空间的同时已经调用构造函数把创造的空间初始化了
if (_a)
{
memcpy(tmp, _a, sizeof(T)*_top);
delete[] _a;
}
_a = tmp;
_capacity = newCapacity;
}
_a[_top] = x;
++_top;
}
void Pop()
{
assert(_top > 0);
--_top;
}
bool Empty()
{
return _top == 0;
}
const T& Top()
{
assert(_top > 0);
return _a[_top - 1];
//1.为什么加const
//避免栈中数据被修改
/*
Stack<int> st1;
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Top()++;
st1.Top() *= 2;
*/
//2.为什么加引用
//因为栈中数据全存放在堆空间.函数结束,不会被销毁,可以用引用作为别名
}
private:
T* _a;
size_t _top;
size_t _capacity;
//下面实际上也是用初始化列表
/*T* _a = nullptr;
size_t _top = 0;
size_t _capacity = 0*/;
};
int main()
{
try
{
Stack<int> st1;
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
st1.Push(5);
st1.Push(6);
while (!st1.Empty())
{
cout << st1.Top() << " ";
st1.Pop();
}
cout << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
另外,模板不支持分离编译(声明放在.h,定义放在.cpp)
那么类模板想要声明定义分离怎么办?
例如上面栈中的Push函数太长了,想在类外面定义,写在本文件中
虽然不可以写在其他文件,但是可以写在同一个文件中,例如:
template<typename T>
class Stack
{};
//..............
template<class T>
void Stack<T>::Push(const T& x)
//现在T在类外面,必须指定类域,函数才能访问类中的私有变量
{
if (_top == _capacity)
//栈满了或者栈是空的
{
size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
//1.开新空间
//2.拷贝数据
//3.释放旧空间
T* tmp = new T[newCapacity];
if (_a)
{
memcpy(tmp, _a, sizeof(T)*_top);
delete[] _a;
}
_a = tmp;
_capacity = newCapacity;
}
_a[_top] = x;
++_top;
}
int main()
{
try
{
Stack<int> st1;
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4);
st1.Push(5);
st1.Push(6);
while (!st1.Empty())
{
cout << st1.Top() << " ";
st1.Pop();
}
cout << endl;
}
catch (const exception& e)
{
cout << e.what() << endl;
}
return 0;
}
像这样函数直接在.h文件中定义,一般把文件后缀名写为.hpp