在之前我们的编程思想中,我们所编写的每一个函数都是带有数据类型的。
例如compare函数,针对int数据类型的我们编写一个函数,针对double类型我们又编写一个一模一样的函数。想想这样是不是很麻烦呀,由此在c++中我们引出了模板。
模板的意义就是对类型进行参数化。
一、函数模板
1、相关概念
针对上述我们所说的compare函数我们对其模板化,写一个函数模板如下:
template<typename T>
bool compare(T a, T b)
{
cout << "template compare" << endl;
return a > b;
}
针对上述代码,我们有几个名词需要解释。
(1)模板类型参数:
template实现的就是定义一个模板参数列表(里面包含两个模板类型参数,用class也可,但是容易产生误会,一般都没有使用,都是使用的typename)
(2)函数模板:
compare是一个函数的模板,用T类型定义两个形参变量,不进行编译
在main主函数中我们可以这样来使用函数模板
int main()
{
compare<int>(10, 20);
compare<double>(10.1, 20.1);
compare(10, 20);
(3)模板函数
就像main函树里面所写的一样,在调用点进行实例化。他所生成的模板函数如下,这是要用编译器进行编译的:
template<typename int>
bool compare(int a, int b)
{
cout << "template compare" << endl;
return a > b;
}
在函数调用点,编译器用用户指定的类型,从原模板实例化一份函数代码出来
模板函数。从编译器来说代码没有减少,但是从用户角度代码确实变少了
(4)模板的实参推演:
就像main函树里面所写第三句调用那样,没有给定模板的参数类型。但是因为有函数模板的实参推演。在这只需要调用第一次实例化的函数即可
更进一步,如果我们想实现字符串的比较,我们编写如下:
compare("aaa","bbb");
推演出来的类型是const char*.但是这样是在比较两个字符串的地址大小,实际上应该调用字符串操作函数strcmp
针对compare函数模板,提供const char*类型的特例化版本,实现如下:
template<>
bool compare<const char*>(const char* a, const char* b)
{
cout << "compare<const char*>" << endl;
return strcmp(a, b) > 0;
}
对于某些类型来说,依赖编译器默认实例化的模板代码,代码处理逻辑是错误的。
所以我们引出模板的特例化(专用化)的概念。
(5)模板的特例化:
对于某些类型来说,依赖编译器默认实例化的模板代码,代码处理逻辑是错误的。不是编译器提供的,而是用户提供的。
也可以直接写一个普通函数来调用
bool compare(const char* a, const char* b)
{
cout << "normal compare" << endl;
return strcmp(a, b) > 0;
}
编译器优先把compare处理成函数名字,没有的话,才去找compare模板
2、函数模板的编写规则
因为函数模板不进行编译,模板代码不能在一个文件定义另一个文件使用,模板代码调用之前,一定要看大模板定义的地方,这样的话模板才能够正常实例化,产生能够被编译器编译器的代码,所以模板代码都是放在头文件中,在源文件中#include包含
3、模板的非类型参数
必须是整数类型(整数或者地址/引用都可以) 都是常量,只能使用,不能修改。
例如下面一个实例,使用冒泡排序时需要传入数组大小SIZE,具体实现如下:
template<typename T,int SIZE>
void sort(T* arr)
{
for (int i = 0; i < SIZE - 1; ++i)
{
for (int j = 0; j < SIZE - 1 - i; ++j)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main()
{
int arr[] = { 12,5,7,89,32,21,35 };
const int size = sizeof(arr) / sizeof(arr[0]);
sort<int,size>(arr);
for (int val : arr)
{
cout << val << " ";
}
cout << endl;
}
在函数的调用点,就应该传入模板的非类型参数SIZE
二、类模板
在讲解类模板的时候,为了讲解更加形象化,我们来模拟实现顺序栈
template<typename T>
class SeqStack
{
public:
SeqStack(int size = 10)
:_pstack(new T[size])
,_top(0)
,_size(size)
{}
~SeqStack()
{
delete[] _pstack;
_pstack = nullptr;
}
SeqStack(const SeqStack<T>& stack)
:_top(stack._top)
,_size(stack._size)
{
_pstack = new T[_size];
//不要用memcopy进行拷贝(做的是浅拷贝)
for (int i = 0; i < _top; ++i)
{
_pstack[i] = stack._pstack[i];
}
}
SeqStack<T>& operator = (const SeqStack<T>& stack)
{
if (this == &stack)
return *this;
delete[] _pstack;
_top = stack._top;
_size = stack._size;
_pstack = new T[_size];
for (int i = 0; i < _top; ++i)
{
_pstack[i] = stack._pstack[i];
}
return *this;
}
void push(const T& val)
{
if (full())
expand();
_pstack[_top++] = val;
}
void pop()
{
if (empty())
return;
--_top;
}
T top()const
{
if (empty())
throw "stack is empty!";//抛异常也代表函数逻辑结束
return _pstack[_top - 1];
}
bool full()const { return _top == _size; }
bool empty()const { return _top == 0; }
private:
T* _pstack;
int _top;
int _size;
//顺序栈底层数组按2倍的方式扩容
void expand()
{
T* ptmp = new T[_size * 2];
for (int i = 0; i < _top; ++i)
{
ptmp[i] = _pstack[i];
}
delete[]_pstack;
_pstack = ptmp;
_size *= 2;
}
};
int main()
{
SeqStack<int> s1;
s1.push(20);
s1.push(89);
s1.push(21);
s1.push(12);
s1.pop();
return 0;
}
注意!
(1)class后面的SeqStack叫模板名称,不是类名。模板名称 + 类型参数列表 = 类名称
(2)构造和析构函数名不用加,其他出现模板的地方都加上类型参数列表
(3)类模板的选择性实例化:当我们类模板实例化过后的模板类到底使用哪个方法编译器看代码中都调用过哪些方法