在C++语言中,函数声明和定义需要明确指定函数名及其参数类型,这是理解函数重载的重要概念。
函数模板
C++函数模板是一种强大的泛型编程工具,它允许我们对数据类型进行参数化,从而实现代码的高度复用。模板的核心意义在于将算法与数据类型解耦,使同一段代码可以适用于不同的数据类型。
template<typename T>//定义一个模板参数列表
bool compare(T a,T b) //compare是一个函数模板
{
return a>b;
}
上述定义的这段代码就可以支持如int、float、double类型的数据比较。
函数模板的实例化
在函数调用点进行实例化是指在使用函数模板时,编译器会根据传入的实参在调用点自动生成对应的具体函数代码。
具体过程可以总结为:
- 编译器在编译时遇到函数模板调用语句
- 根据实参类型推导模板参数
- 并实例化对应类型的模板函数
- 编译生成的目标代码中会包含这个模板函数
/*
实例化的int型模板函数
bool compare<int>(int a,int b)
{
return a>b;
}
实例化的double型模板函数
bool compare<double>(double a,double b)
{
return a>b;
}
*/
int main()
{
//在函数调用点,编译器根据用户指定的类型,从原模版实例化一份代码出来
//叫做模板函数
compare<int> (10,20);
compare<double>(10.5,20.5);
}
如上述代码,由于compare是函数模板,并且调用的时候分别传入了int类型的实参和double类型的实参,于是从原模板中分别实例化了int和double的模板函数。
函数模板的特例化
特例化:模板本来是一组通用逻辑的实现,但是对于某些特定的参数类型来说,通用的逻辑实现可能不能满足要求,这时就需要针对这些特殊的类型去实现一个特例模板,即模板的特例化。
函数模板特例化,是将模板参数全部指定成特定的类型(并对这个类型的逻辑进行修改),但不等价于函数重载,两者不要混淆。
/*针对compare这个模板函数,能适用于大部分数据类型,如int ,double,以及简单对象,但是也有特例
如compare("aaa","bbb"),根据函数推演,其应该是
bool <const char *>(const char *,const char *)
{
return a<b;
}
这样比较的是a和b两个地址的大小,而非字典序大小,
针对这种情况,提供const char *这个类型的特例化版本
*/
template<>
bool compare(const char *a,const char *b)
{
return strcmp(a,b)>0;
}
非模板函数-普通函数
//template<> //没有这个template定义的就是普通函数
bool compare(const char *a,const char *b)
{
return a<b;
}
注意:
模板代码是不能在一个文件中定义,在另外一个文件中使用的。模板代码调用之前,一定要看到模板定义的地方,这样模板才能够正常的实例化,产生能够被编译器编译的代码。所以,一般模板代码都是放在头文件中的。
不过如果是跨文件调用的话,在该函数模板定义的文件中,告诉编译器进行指定类型的模板实例化也是可以的(这种一般不推荐,要手动初始化多个类型的模板函数)。
类模板
类模板与函数模板的定义和使用类似,存在多个类,其功能是相同的,仅仅是数据类型不同,就可以通过类模板复用代码:
template <类型形式参数表>
类声明
以下是基于类模板模拟实现了一个栈,支持入栈、出栈、判断栈满、判断栈空、查看栈顶原始,扩容;实现了支持深拷贝的拷贝构造函数和赋值构造函数。
template <typename T>
class SeqStack //模板名称+类型参数列表=类名称
{
public:
//构造和析构函数不用加<T>(在类里面,就是类模板)
//但是其他出现模板的地方都加上类型参数列表
SeqStack(int size=10)
:_pstack(new T[size])
,_top(0)
,_size(size)
{}
~SeqStack()
{
delete []_stack;
_pstack=nullptr;
}
SeqStack(const SeqStack<T> &stack) //重载拷贝构造运算符
:_top(stack._top)
,_size(stack._size)
{
_pstack=new T[_size];
//memcopy是浅拷贝,一般仅用于一些内置类型,对于对象等一般for循环赋值
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;
}
}