2.模板
模板:
模板的意义:对类型进行参数化
函数模板
template<typename T>
bool compare(T a,T b)
{
cout<<"Template compare"<<endl;
return a>b;
}
int main()
{
compare<int>(10,20);
//在函数的调用点,编译器用用户指定的类型,从原模版中实例化一份函数代码出来
//compare(int a,int b)
compare<double>(10.3,30.1);
}
模板的实参推演:
可以更具用户传入的实参类型,来推导出模板类型的参数
template<typename T>
bool compare(T a,T b)
{
cout<<"Template compare"<<endl;
return a>b;
}
int main()
{
compare(10,20); //未传入类型参数,编译器会进行实参推演,得到具体类型
compare(10.3,30.1);
compare("alsdjflaf","asodf");
//自动类型推导为 const char* 这是没有意义的,因为两个指针比较大小实际是两个字符串的地址比大小
}
模板不参与编译:
模板不参与编译,因为没有办法编译,不知道具体类型,无法开辟正确的空间
但是,模板一旦实例化或推演,就会在符号表中产生相应的符号,对应的函数也就生成了
问题情况:
template<typename T>
bool compare(T a,T b)
{
cout<<"Template compare"<<endl;
return a>b;
}
int main()
{
compare(10,20.1);
}
这样就会编译错误:因为,在进行类型推导的时候,第一次将T推导为int类型,第二次将T推到为double类型,引发二义性,导致错误
模板的特例化:
template<typename T>
bool compare(T a,T b)
{
cout<<"Template compare"<<endl;
return a>b;
}
int main()
{
compare("alsdjflaf","asodf");
//自动类型推导为 const char* 这是没有意义的,因为两个指针比较大小实际是两个字符串的地址比大小
}
对于某些类型,依赖编译器默认实例化的模板代码逻辑是错误的(如上代码)。这时就要为模板提供特例化了。(不是编译器提供而是开发者提供)
template<typename T,typename E>
bool compare(T a,E b)
{
cout<<"Template compare"<<endl;
return a>b;
}
template<> //一定要加,代表是模板函数
bool compare(const char* a,const char* b)
{
cout<<"特例化"<<endl;
return srtcmp(a,b);
}
int main()
{
compare("abcd","abcd");
}
特例化实际就是自实现
template<typename T,typename E>
bool compare(T a,E b)
{
cout<<"Template compare"<<endl;
return a>b;
}
template<>
bool compare(const char* a,const char* b) //本质还是模板函数
{
cout<<"特例化"<<endl;
return strcmp(a,b);
}
bool compare(const char* a,const char* b) //就是一个普通函数
{
cout<<"普通函数"<<endl;
return strcmp(a,b);
}
int main()
{
compare("abcd","abcd"); //调用普通函数
compare<const char*>("abc","def"); //调用模板函数
}
当特例化与函数的自实现同时存在时,优先调用普通函数
特例化与普通函数之间并不是重载的关系,因为不满足重载的定义(函数名相同),此例中模板函数的函数名为compare<const char*> 普通函数的函数名为compare
模板的非类型参数:
模板的非类型参数都是常量,即含有const属性,不可更改
template<typename T,const int SIZE>
void sort(T* arr)
{
for(int i=0;i<SIZE;i++)
{
for(int j=0;j<SIZE-i-1;j++)
{
if(arr[j]<arr[j+1])
{
T tem = arr[j];
arr[j] = arr[j+1];
arr[j+1] = tem;
}
}
}
}
int main()
{
int arr[] = {12,6,9,3,4,7,56};
const int num = sizeof (arr)/sizeof (arr[0]);
//传入模板的非类型参数,必须是 const类型
sort<int,num>(arr);
for(int i:arr){cout<<i<<" ";}
}
非类型模板参数是有类型限制的。一般而言,它可以是常整数(包括enum枚举类型)或者指向外部链接对象的指针或引用。
浮点数和类对象(class-type)不允许作为非类型模板参数
注意:
模板代码,不可以在一个文件中定义,在另一个文件中使用
因为,模板本身是不参加编译的,只有在实例化后才会编译。由于.cpp文件是分别进行编译之后在进行连接,但由于模板本身是不参与编译的使用也就不会产生对应的符号表,就导致链接错误
解决方法
-
将模板代码放在头文件中,在源文件中 include 包含 常用
-
可以将模板单独定义在一个文件中,但是要告诉编译器,进行模板指定类型的实例化,从而参与编译,生成对应的函数,即在模板文件中加上
template bool compare<int>(int,int);
在使用改模板的文件中,加上模板函数的声明
template<typename T> bool compare(T a,T b);
这种办法显然不好,因为不可能将所有可能的类型都枚举一编
类模板:
顺序栈的自实现
#include <iostream>
#include "string.h"
using namespace std;
template<typename T>
class SeqStack //此时SeqStack是模板名,只有加上<T>才是类名
{
SeqStack<T>(int size = 10)
//模板是没有构造函数的,只有类才有构造函数,所以写成SeqStack<T>(),类的构造函数
//由于这样看起来比较麻烦,所以c++规定,模板类的构造和析构函数可以不加<T>
:top_(0)
,size_(size)
,pstack_(new T[size])
{}
~SeqStack<T>()
{
delete[] pstack_;
pstack_ = nullptr;
}
SeqStack(const SeqStack<T>&another)
:top_(another.top_)
,size_(another.size_)
{
this->pstack_ = new T[size_];
for(int i=0;i<top_;i++)
{
pstack_[i] = another.pstack_[i];
}
}
SeqStack& operator=(const SeqStack<T>&another)
{
if(this == &another) return *this; //防止自赋值
top_ = another.top_;
}
void push(T val)
{
if(full()) expand();
else
{
pstack_[top_] = val;
++top_;
}
}
T pop()
{
top_--;
return pstack_[top_];
}
bool full()
{
if(size_ == top_)
return true;
else return false;
}
bool empty()
{
if(top_ == 0)
return true;
else return false;
}
private:
T* pstack_; //内存可增长
int top_;
int size_;
void expand() //顺序栈底层数组2倍的方式扩容
{
T* tem = new T[size_ * 2];
for(int i=0;i<top_;i++)
{
tem[i] = pstack_[i];
}
pstack_ = tem;
delete[] tem;
}
};
stl::vector的自实现
class vector
{
public:
vector<T>(int size = 10)
{
first_ = new T[size];
last_ = first_;
end_ = first_ + size + 1;
}
~vector<T>()
{
delete[] first_;
first_ = nullptr;
last_ = nullptr;
end_ = nullptr;
}
vector<T>(const vector<T>&another)
{
first_ = new T[end_ - first_];
int len = another.last_ - another.first_;
//len是有效元素的长度
for(int i=0;i<len;i++)
{
first_[i] = another.first_[i];
}
last_ = first_ = len;
end_ = first_ + (end - first_);
}
vector&operator=(const vector<T>&another)
{
if(this == &another)
return *this;
first_ = new T[end_ - first_];
int len = another.last_ - another.first_;
//len是有效元素的长度
for(int i=0;i<len;i++)
{
first_[i] = another.first_[i];
}
last_ = first_ + len;
end_ = first_ + (end - first_);
}
bool full() const
{
if(last_ == end_)
return true;
else
return false;
}
bool empty() const
{
return !full();
}
void push_back(T val)
{
if(full())
expand();
*last_++ = val; //++的优先级大于*解引用
}
void pop_back()
{
if (empty())
return;
last_--;
}
T back() //返回容器末尾的元素值
{
return *(last_-1);
}
private:
T* first_; //指向有效元素的第一个
T* last_; //指向最后一个有效元素的后继位置
T* end_; //指向数组空间的后继位置
void expand()
{
int size = end_ - first_;
T* tem = new T[size * 2];
for(int i=0;i<size;i++)
{
tem[i] = first_[i];
}
first_ = tem;
last_ = first_ + size;
end_ = first_ + size * 2;
}
};
这并不是STL中的实现,在STL中还有容器空间适配器allocator
理解容器空间适配器allovator
在之前自实现的vector有个很大的问题,就是内存的开辟与对象的构造时同步的,这极大的浪费了资源
需要把内存开辟与对象的构造分离开。需要把内存的释放和对象的析构分离开
template<typename T>
class Allocator
{
public:
T* allocate(size_t size) //负责内存开辟
{
return (T*) malloc(sizeof(T) * size);
}
void deallocate(void *p) //负责内存释放
{
free(p);
}
void construct(T*p,const T&val) //负责对象构造
{
new(p) T(val); //定位new,在p的内存上调用拷贝构造
}
void destroy(T* p)
{
p->~T();
}
};
template<typename T>
class vector
{
public:
vector<T>(int size = 10)
{
first_ = allocator_.allocate(size);
last_ = first_;
end_ = first_ + size + 1;
}
~vector<T>()
{
for(T* p=first_;p!=last_;p++)
{
allocator_.destroy(p);
}
allocator_.deallocate(first_);
first_ = last_ = end_ = nullptr;
}
vector<T>(const vector<T>&another)
{
int len = another.last_ - another.first_;
//len是有效元素的长度
first_ = allocator_.allocate(len);
for(int i=0;i<len;i++)
{
allocator_.construct(first_+i,another.first_[i]);
}
last_ = first_ = len;
end_ = first_ + (end_ - first_);
}
vector&operator=(const vector<T>&another)
{
if(this == &another)
return *this;
for(T* p=first_;p!=last_;p++)
{
allocator_.destroy(p);
}
allocator_.deallocate(first_);
int len = another.last_ - another.first_;
//len是有效元素的长度
first_ = allocator_.allocate(len);
for(int i=0;i<len;i++)
{
allocator_.construct(first_+i,another.first_[i]);
}
last_ = first_ = len;
end_ = first_ + (end_ - first_);
}
bool full() const
{
if(last_ == end_)
return true;
else
return false;
}
bool empty() const
{
return !full();
}
void push_back(T val)
{
if(full())
expand();
allocator_.construct(last_,val);
}
void pop_back()
{
if (empty())
return;
else
allocator_.destroy(--last_);
}
T back() //返回容器末尾的元素值
{
return *(last_-1);
}
private:
T* first_; //指向有效元素的第一个
T* last_; //指向最后一个有效元素的后继位置
T* end_; //指向数组空间的后继位置
Allocator<T> allocator_;
void expand()
{
int size = end_ - first_;
T* tem = allocator_.allocate(size * 2);
for(int i=0;i<size;i++)
{
// tem[i] = first_[i];
allocator_.construct(tem+i, first_[i]);
}
for(int i=0;i<size;i++)
{
allocator_.destroy(first_ + i);
}
allocator_.deallocate(first_);
first_ = tem;
last_ = first_ + size;
end_ = first_ + size * 2;
}
};