模板函数
概念:一种与类型无关的代码.
作用:复用.
下面通过一个实例来实现:
template<class T> //类比函数参数,但是模板参数传递的是类型,也可以使用typename
T Add(T a,T b)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
cout<<Add(a,b)<<endl;
//Add<int>(a,b); //显示实例化,当add函数的参数类型不同时,就会在推演类型时出错,所以此时就用到了显示实例化,明确T的类型
float c = 1.1223;
float d = 2.123;
cout<<Add(c,d)<<endl;
return 0;
}
那么是怎么实现的呢?只要想探索底层的实现原理,有一个简便的方法就是:查看汇编.
此时,我们查看汇编:
所以:编译器在编译时,会进行模板类型的推演,然后模板函数在执行时会生成不同的函数名,进而实现函数的复用.
注意:模板函数只有在调用的时候才会进行检查.如果只写模板函数是不会检查函数体是否有错误
模板函数重载
template<class T> //类比函数参数,但是模板参数传递的是类型
T Add(T a,T b)
{
return a + b;
}
int Add(int a,int b)
{
return a + b;
}
int main()
{
int a = 10;
int b = 20;
Add(a,b); //调用非模板函数
float c = 1.1223;
float d = 2.123;
Add(c,d); //调用的是模板函数
return 0;
}
函数名相同,且在同一作用域,所以上面两个函数构成了重载.
模板类
如果要看Vector的模板实现的代码,请戳这里
模板类与模板函数相似,在这儿,我们可以通过顺序表Vector来实现模板类.
template <class T>
class Vector
{
public:
Vector();
Vector(const Vector<T>& v);
Vector<T>& operator=(const Vector<T>& v);
void PushBack(const T& value);
void PopBack();
void Insert(size_t pos,const T& value);
void Erase(size_t pos);
size_t Size();
size_t Capacity();
bool Empty();
~Vector();
protected:
T* _first;
T* _finish;
T* _endofstorage;
};
敲黑板:在模板中.类名就是上例中的Vector;类型而是Vector;而在普通的类中,类名和类型相同.
当在类外面进行实现时,与我们以往写的声明是不同的.
template<class T>
Vector<T>::Vector() //注意作用域
:_first(NULL)
,_finish(NULL)
,_endofstorage(NULL)
{}
template<class T> //必须写
Vector<T>::~Vector()
{
delete[] _first;
_first = _finish = _endofstorage = NULL;
}
当在创建对象使用时,它的声明是:
Vector<int> v;
经常使用的数据结构还有带头双向链表.
带头双向链表代码的实现
template<class T>
struct LinkNode
{
T data;
LinkNode<T>* prev;
LinkNode<T>* next;
LinkNode(const T& value)
:data(value)
,prev(NULL)
,next(NULL)
{}
};
template<class T>
class LinkList
{
public:
typedef LinkNode<T> Node;
LinkList();
LinkList(const LinkList<T>& l);
LinkList<T>& operator=(const LinkList<T>& l);
void PushFront(const T& value);
void PopFront();
void PushBack(const T& value);
void PopBack();
void Insert(Node* pos,const T& value);
void Erase(Node* pos);
void Clear();
~LinkNdoe();
bool Empty();
protected:
Node* head;
};
模板参数——>容器适配器
在生活中,我们笔记本电脑的充电器,也叫适配器(实现电压的转化).
同样的,在我们的程序中,也可以通过某种代码去实现其它的功能.比如:用Vector去模拟实现栈.
template<class T,class Container>
//template<class T,class Container = Vector<T> > //缺省参数
class Stack
{
public:
void Push(const T& value)
{
con.PushBack(value);
}
void Pop()
{
con.PopBack()
}
T& Top()
{
con.Front();
}
bool Empty()
{
con.Empty();
}
protected:
Container con;
};
int main()
{
Stack<int,class<int> > s;
return 0;
}
模板的模板参数
与上面的适配器的实现类比:
template<class T,template<class T> class Container >
//template<class T,template<class T> class Container = Vector<T>>
class Stack
{
public:
void Push(const T& value)
{
con.PushBack(value);
}
void Pop()
{
con.PopBack()
}
T& Top()
{
con.Front();
}
bool Empty()
{
con.Empty();
}
protected:
Container<T> con;
};
int main()
{
Stack<int,Vector> s;
return 0;
}
template<class T,template<class T> class Container>
其中:template<class T>表示Container是一个模板类类型的模板参数.
非类型的类模板参数
模板类的模板参数,也可以飞类型的模板参数.
比如:
template<class T,size_t N>
利用这个可以实现静态的顺序表.
注意:double是不可以做非类型的模板参数.
非类型的函数模板参数
template<class T,int value>
T Add(const T& a)
{
return a + value;
}
int main()
{
cout<<Add<int,3>(10)<<endl;
return 0;
}
类模板的特化
使用日期类来举例学习:
template<typename T1,typename T2>
class Date
{
public:
Date()
{
cout<<"Date()"<<endl;
}
private:
T1 _d;
T2 _d1;
};
template<typename T1,typename T2>
class Date<T1& ,T2&>
{
public:
Date(T1 a,T2 b)
{
cout<<"Date<T1&,T2&>"<<endl;
}
private:
T1& _d1;
T2& _d2;
};
template<typename T1,typename T2>
class Date<T1*,T2*>
{
public:
Date()
{
cout<<"Date<T1*,T2*>"<<endl;
}
private:
T1* _d1;
T2* _d2;
};
//第二个参数偏特化
template<typename T1>
class Date<T1,char>
{
public:
Date()
{
cout<<"Date<T1,char>"<<endl;
}
private:
T1 _d1;
char _d2;
};
//第一个参数偏特化
template<typename T2>
class Date<char,T2>
{
public:
Date()
{
cout<<"Date<char,T2>"<<endl;
}
private:
char _d1;
T2 _d2;
};
template<> //全特化
class Date<int,int>
{
public:
Date()
{
cout<<"Date<int,int>"<<endl;
}
private:
int _d1;
int _d2;
};
int main()
{
Date<int,int> d;
Date<char,int> d1;
Date<double&,double&> d2(1.11,2.22);
Date<int*,int*> d3;
return 0;
}
总结:当该类型的模板函数实现了时,在调用时就会调用(偏特化模板函数)实现了的对应的函数.当该函数没有实现时,就会调用模板函数.注意:当类的成员变量为引用类型时,在初始化类时对于成员变量也要初始化.(引用必须初始化)
注意:模板的全特化和半特化都是在已定义的模板的基础之上,不能单独存在.
模板的分离编译
当模板的定义在.h文件中,而声明在.c文件中,而测试函数又在另外一个文件中时,在编译时可以通过吗?
总结:模板不支持分离编译,最好的做法就是,直接在模板的定义后面进行模板的实现.
如果定义和声明不在同一个文件中,那么在执行函数时就会直接编译不通过,链接失败.因为模板函数只是在执行的时候才会去推演它的类型,才会去找实现的函数,当它们不在同一个文件时,就会因为找不到而报错.
模板总结:
优点:
1. 提高了代码的复用性,节省资源.
2. 增强代码的灵活性.
缺点:
1. 不易维护,让代码变得复杂.
2. 当模板使用错误产生错误信息时,不容易找到错误信息的根源.
下面是一道拓展思维的题:
不使用乘除,for,while,等循环和递归来计算1+2+3+4+….+n的值;
方法1:
class Add_1_to_n
{
public:
Add_1_to_n()
{
ret = ret + i;
++i;
}
static int sum;
static int i;
};
int Add_1_to_n::sum = 0;
int Add_1_to_n::i = 1;
void Test()
{
Add_1_to_n* p = new Add_1_to_n[10];
cout<<Add_1_to_n::sum<<endl;
delete[] p;
}
方法2:在编译期间就已经解决问题.
缺点;当n太大时,就会因为递归的层数太深而程序崩溃,无法计算出来
tempalate<size_t N>
class Add_1_to_n
{
public:
enum
{
sum = Add_1_to_n(N - 1)::sum + N;
};
};
template<>
class Add_1_to_n<1>
{
public:
enum
{
sum = 1;
};
};
void test
{
cout<<Add_1_to_n<10>::sum<<endl;
}
方法3:鬼畜方法(反正我是想不到~~~)
缺点:因为栈比较小,所以当N太大时,就会导致栈溢出
char arr[N][N + 1] = {0};
cout<<(sizeof(arr))>>1)<<endl;
若读者有更好的方法:请指教