在c语言中,我们能够通过预处理宏替换来逃避编译器的类型检查,处理进行一些对类型要求不强的操作的,比如返回同类型的两个数中较大值就可以使用宏定义,从而可以实现我们对不同内置类型的数据大小比较。
当然这我们知道这是不安全的,我们知道宏是在预编译时期进行的简单替换,而不是函数那样通过参数传递调用。
例如:
#include <iostream>
using namespace std;
#define min(a,b) (((a)>(b))?(b):(a))
int main()
{
int arr[10]={2,3,4,5,7,8,9,10,11,13};
int size=10;
int *p=&arr[0];
int count=0;
//统计数组的数据个数
while(min(p++,&arr[size]) != &arr[size])
++count;
cout<<count<<endl;
return 0;
}
使用宏替换返回两个数的较小值,这样不管是int类型的数组还是double类型的数组都可以使用,但是上述的运行的结果是不正确的,在宏替换之后,我们使用了三目运算符,p++操作在一次min()计算中被多重复执行了一次,导致程序运行结果不正确。
那么我们知道c++可以进行函数重载,我们可以通过函数重载解决这个问题吗?肯定是可以的,我们将每一个内置类型的min函数显示定义出来:
int min(int a,int b)
{
return a>b?b:a;
}
short min(short a,short b)
{
return a>b?b:a;
}
char min(char a,char b)
{
return a>b?b:a;
}
double min(double a,double b)
{
return a>b?b:a;
}
这样我们就可以调用不同类型参数的函数,但是我们发现函数只有参数类型不一样,除此之外,函数形参数目及函数中的定义及函数调用均相同,那么我们能否将参数类型提炼出来,根据调用需求再实例化具体的函数 ,有的!那就是函数模板,函数模板就是通过函数在调用时,根据调用的参数类型来确定函数在定义时未确定的参数,这个过程叫函数实例化,通过调用给定参数从而实例化出一个确定类型的参数函数。
template<tempname T>//T是未知的类型,具体什么类型在函数调用时确定
//template 和class作用均相同,这里的class不是类定义的class
T Min(T a,T b)
{
return a>b?b:a;
}
上面是返回最小值的函数模板,有了这个模板,我们就可以直接调用Min函数处理任意类型的数据,也包括自定义类型的数据,只要该自定义类型重载了 ‘>’ 就可以;
函数模版只是一个说明,并不能直接执行,只有在实例化之后它才成为一个普通可执行的函数。那么该模板在调用时是如何进行实例化的呢?比如我们Min(1,2),那么编译系统会生成以下函数
int Min(int a,int b)
{
return a>b?b:a;
}
注意:
- 函数调用过程中首先调用类型匹配度高的函数,优先调用普通函数;比如模板函数和实例化的函数同时被定义,当调用Max(1,2)时,直接调用int Min(int,int)函数,而不再通过模板实例化。
- 函数模板在传参过程中不存在隐式类型转换
非类型模板参数
模板类型的非类型参由有一个普通的参数声明构成,非类型模板参数表示该参数代表了一个潜在的值,而该值被用一个常量值,可以出现在定义的余下部分。该参数一般是常整数(包括枚举类型)或者指向外部链接对象的指针。浮点数和类对象不能作为非类型模板参数。
示例:
//代码来自c++Prime 676页
template <int size>
Buf{ ... };
template <int *ptr>
class BufPtr { ... };
int size_val = 1024;
const int c_size_val = 1024;
Buf< 1024 > buf0; // 常量 ok
Buf< c_size_val > buf1; // const修饰的常变量 ok
Buf< sizeof(size_val) > buf2; //sizeof计算的常量表达式sizeof(int) ok:
BufPtr< &size_val > bp0; //全局空间域中的变量地址为常量表达值 ok
// 错误: 不能在编译时刻被计算出来
Buf< size_val > buf3;//编译时size_val的值不确定
下面一个例子作为练习,模拟实习stack栈,以数组作为内部结构:
#include <iostream>
using namespace std;
//非类型模板参数的练习,栈实现
template<class T,int MAXSIZE>//MAXSIZWE为非模板类型参数,在编译时期已知
class Stack
{
private:
int m_count;
T elems[MAXSIZE];
public:
Stack():m_count(0)
{}
bool empty()
{
return m_count==0;
}
bool full()
{
return m_count==MAXSIZE;
}
void push(const T& val);
void pop();
T& top();
const T& top()const;
};
template<typename T,int MAXSIZE>
void Stack<T, MAXSIZE>::push(const T& val)
{
if(full())
{
cout<<"Error! Satck full stack"<<endl;
}
elems[m_count]=val;
m_count++;
}
template<class T,int MAXSIZE>
void Stack<T,MAXSIZE>::pop()
{
if(empty())
{
cout<<"Error! Stack is empty"<<endl;
}
m_count--;
}
template<class T,int MAXSIZE>
T& Stack<T,MAXSIZE>::top()
{
if(empty())
{
cout<<"Error! Stack is empty"<<endl;
return elems[0];
}
return elems[m_count-1];
}
template<class T,int MAXSIZE>
const T& Stack<T,MAXSIZE>::top() const
{
if(empty())
{
cout<<"Error! Stack is empty"<<endl;
return elems[0];
}
return elems[m_count-1];
}
void testStack()
{
Stack<int,10> s;
s.push(3);
s.push(4);
s.push(5);
cout<<"empty? :"<<s.empty()<<endl;
cout<<"full? :"<<s.full()<<endl;
cout<<s.top()<<endl;
for(int i=0;i<3;i++)
{
s.pop();
}
cout<<"empty? :"<<s.empty()<<endl;
cout<<"full? :"<<s.full()<<endl;
cout<<s.top()<<endl;
}
int main()
{
testStack();
return 0;
}
在上面的Stack实例中,我们如果定义了Stack<int,10> s1和Stack<int,20> s2这两个对象,那么这s1和s2是两个不同的类型,虽然两个对象处理的类型相同,但是实例化出来对象内部的数组初始化大小不同,因此是两个不同的对象,两者之间不能进行类型转化。