模板的意义:对类型也可以参数化了;
template<typename T>
bool compare(T a, T b) {
cout << "template compare" << endl;
return a > b;
}
int main() {
//compare<int>(1, 2);
compare(1, 2);
//上面的compare<int>(1,2)中的函数模板实参可以省略,对于函数模板编译器可以进行类型推导
}
在函数调用点,编译器根据用户指定的类型,从原模板实例化一份函数代码出来: bool compare<int>(int a,int b){ return a>b; } 比如如果要比较double类型的,编译器在调用点实例化一份compare<double>函数代码; 函数模板调用时候不用给模板实参,因为对于函数模板,编译器可以进行模板的实参推导; 模板实参推导:编译器可以根据用户传入的实参类型推导出模板类型参数的具体类型; 模板的实例化:在函数模板的调用点上,编译器用推断出来的模板参数来实例化一个特定版本的函数; 编译器生成的特定类型的版本称为模板的实例; 模板类型参数:可以通过typename和class定义模板类型参数, 函数模板是不会被编译器编译的,因为类型不知道;而模板函数才是被编译器所编译的。 对于某些类型来说,依赖编译器默认实例化的模板代码,代码处理逻辑是有错误的(如下面的compare),这就需要模板的特例化了;compare("hello", "world"); //这样会生成下面的模板函数实例:
bool compare<const char*>(const char* a, const char* b) {
return a > b; //但是比较的是a和b的地址,这样是没有意义的,所以不对;
//比较字符串是想比较二个字符串的字典顺序,所以我们需要一个const char *的特例化版本
}
/*模板非类型参数:非类型参数表示一个值而非类型;当一个模板被实例化时候,非类型参数被一个用户提供的或编译器推断出来的值所替代;这些值必须是常量表达式;*/
template<unsigned M,unsigned N>
bool compare(const char(&p1)[M], const char(&p2)[N]) {
//编译器会使用字面常量的大小来代替N和M,从而实例化模板;
cout << "非类型模板参数" << endl;
return strcmp(p1, p2);
}
//非类型模板参数在C++11中的array中:
template <typename T,std::size_t N>
struct array;
/*使用array时候:std::array<int,3> a = {1,2,3};
模板特例化(专用化):模板特殊的实例化,不是编译器提供的,而是开发者提供的。
下面代码是特化版本;*/
template<>
bool compare(const char* a, const char* b) {
cout << "compare<const char*> 特例化版本" << endl;
return strcmp(a, b);
}
//编译器优先调用非模板的普通函数,然后是特例化版本,函数模板;
bool compare(const char *a,const char* b)
{
cout<<"normal compare"<<endl;
return strcmp(a,b)>0;
}
模板代码是不能在一个文件中定义,在另外一个文件中使用的;模板代码调用之前,一定要看到模板定义的地方,只有这样,模板才能够进行正常的实例化,产生能够被编译器编译的代码;所以,模板代码都是放在头文件中的,然后在源文件当中直接进行#include包含。
//告诉编译器进行指定类型的模板实例化;
template bool compare<int>(int,int);
template bool compare<double>(double,double);
类模板的使用以下面举例:
//用类模板实现可以扩容的顺序栈
template<typename T=int > //类模板的类型参数有默认模板实参
class SeqStack
{
private:
T* _pstack;
int _top;
int _size;
void expand() {
T* ptmp = new T[_size * 2];
int index = 0;
for (int i = 0; i < _top; i++) {
ptmp[i] = _pstack[i];
}
delete[] _pstack;
_pstack = ptmp;
_size *= 2;
}
public:
//模板名称+类型参数列表=类名称
//当我们使用一个类模板类型时必须提供模板实参
//,但有个例外,如果在类模板自己的作用域里面,可以直接使用模板名而不提供实参;
SeqStack(int size = 10) :_pstack(new T[size]),_top(0),_size(size){}
//SeqStack<T>(int size = 10);
//建议:构造和析构函数名不加<T>,其他出现模板的地方都加上类型参数列表
~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;
}
//存在异常安全性:可以使用copy and swap 的方式
delete[] _pstack;
_top = stack._top;
_size = stack._size;
_pstack = new T[_size];
for (int i = 0; i < _size; _top) {
_pstack[i] = stack._pstack[i];
}
return *this;
}
void push(const T& val); //现在放到类外怎么定义呢?
//void push(const T& val) {
//if (full()) {
//expand();
//}
//_pstack[_top++] = val;
//}
void pop() {
if (empty()) {
return;
}
--_top;
}
T top()const {
//top()前要判断,交给调用者去做,这里不做。
return _pstack[_top - 1];
}
bool full() const {
return _top == _size;
}
bool empty() const {
return _top == 0;
}
};
//在类模板外定义其成员时,因为我们并不在类的作用域,知道遇到类名才表示进入类的作用域
//由于返回类型位于类的作用域之外,所以必须指定类型参数
template<typename T>
inline void SeqStack<T>::push(const T& val) {
if (full()) {
expand();
}
//在函数体内,已经进入类的作用域了,因此可以直接使用SeqStack,不用加上<T>
_pstack[_top++] = val;
}
int main() {
//[类模板实例化后得到模板类]
//[一个类模板的成员函数只有当程序使用到它时才会进行实例化;如果一个成员函数没有被使用,它不会被实例化;]
SeqStack<> s1; //这里用类模板的默认类型参数int
//SeqStack<int> s1; 模板类SeqStack<int>中只有构造函数和析构函数被实例化,其他没有调用的函数不会被实例化
}
typedef SeqStack<string> stringStack; //可以定义一个typedef来引用实例化的类
新标准允许我们为类模板定义一个类型别名
template<typename T>
using twin = pair<T,T>;
twin<string > authors; //authoers是一个pair<string,string>
自定义一个简单的空间配置器:
template<typename T>
class Allocator //定义自己的空间配置器
{
public :
T* allocate(size_t size) //只负责内存开辟
{
return (T*)malloc(size * sizeof(T));
}
void deallocate(void* p)
{
free(p);
}
void construct(T* p, const T& val) //负责对象构造
{//这里是在已经开辟好的指定内存上构造对象,所以使用placement new定位new
new(p) T(val);
}
void destroy(T* p) //负责对象析构
{
p->~T(); //~T()代表了T类型的析构函数
}
};