因为我们经常使用向量类,所以在这里我们创建一个类似于向量的类加深我们对如何设计和实现一个类的理解。
我们决定写一个模板类,这样可以使用户用Vec类存储几种不同类型的数据成员。
模板类的写法:
template <class T> class Vec
{
public:
//接口
private :
//实现
};
因为这个类中要实现begin,end,size等函数的功能。所以Vec中要保存首元素地址,末元素地址和元素个数。我们可以只保存首元素地址和末元素地址,然后计算元素的个数。
Vec类改进为:
template <class T> class Vec
{
public:
//接口
private :
T* data;//Vec中的首元素
T* limit;//Vec中的末元素
};
根据标准的向量类,我们需要3个构造函数,分别为默认构造函数/带一个大小参数的构造函数/带大小和默认值的构造函数。
加了构造函数的Vec类如下:
template <class T> class Vec
{
public:
Vec() {create();} //默认构造函数,create()函数后期实现
explicit Vec(size_type n,const T& Val=T()) {create(n,Val);} //explicit关键字只在定义带一个参数的构造函数时才有意义
private :
T* data;//Vec中的首元素
T* limit;//Vec中的末元素
};
关于类型的定义。我们要提供用户能使用的类型名称,这样可以隐藏实现该类的具体细节。
特别是我们要为常量非常量迭代器和Vec大小的类型提供类型定义。还需要一个value_type类型,这是容器中存储对象的类型的另一个同义词。因为我们用一个数组来封装Vec的元素,所以可以使用普通指针作为Vec迭代器的类型,所有的指针都指向内部的data数组。
template <class T> class Vec
{
public:
typedef T* iterator; //迭代器,指示每一个元素。
typedef const T* const_iterator;
typedef size_t size_type;
typedef T value_type; //每个元素的类型
Vec() {create();} //默认构造函数,create()函数后期实现
explicit Vec(size_type n,const T& Val=T()) {create(n,Val);}
private :
iterator data;//更改部分
iterator limit;//更改部分
};
索引与大小。用户可以使用size函数获取Vec有多少元素,Vec[i]根据索引的方式得到每个元素的值。
Size()函数不带参数,返回Vec::size_type类型。定义重载运算符时在关键字operator后面加上符号。
template <class T> class Vec
{
public:
typedef T* iterator; //迭代器,指示每一个元素。
typedef const T* const_iterator;
typedef size_t size_type;
typedef T value_type; //每个元素的类型
Vec() {create();} //默认构造函数,create()函数后期实现
explicit Vec(size_type n,const T& Val=T()) {create(n,Val);}
size_type size() const{return limit-data;}
T& operator[](size_type i){return data[i];}
const T& operator[](size_type i) const {return data[i];}
private :
iterator data;
iterator limit;
};
返回迭代器的操作。我们要实现begin和end,分别返回Vec的首末数据.
template <class T> class Vec
{
public:
typedef T* iterator; //迭代器,指示每一个元素。
typedef const T* const_iterator;
typedef size_t size_type;
typedef T value_type; //每个元素的类型
Vec() {create();} //默认构造函数,create()函数后期实现
explicit Vec(size_type n,const T& Val=T()) {create(n,Val);}
size_type size() const{return limit-data;}
T& operator[](size_type i){return data[i];}
const T& operator[](size_type i) const {return data[i];}
iterator begin(){return data;} //新增
const_iterator begin() const{return data;} //新增
iterator end(){return limit;} //新增
const_iterator end() const{return limit;} //新增
private :
iterator data;
iterator limit;
};
对象的复制。有复制构造函数和复制运算符两部分。
template <class T> class Vec
{
public:
Vec(const Vec& v){create(v.begin(),v.end());}//复制构造函数
Vec& operator=(const Vec&);//复制运算符
//其他同上
}
复制运算符的实现:
template <class T>
Vec<T>& Vec<T>::operator =(const Vec& rhs) //此处为Vec<T>,不是Vec.
{
if(&rhs!=this)
{
uncreate(); //抹去原Vec中的数据。
create(rhs.begin(),rhs.end());
}
return *this;
}
赋值不是初始化。赋值会删除旧值,而初始化没有这一步。初始化会创建一个新对象,并给初始值。赋值和初始化连得比较紧密。要注意:(1)构造函数始终只控制初始化操作;(1)operator=始终只控制赋值操作。
析构函数。在public 中增加~Vec(){uncreate();}
复制构造函数,析构函数,赋值运算符函数之间关系相当密切,他们之间构成了一个“三位一体规则”。
如果类需要一个析构函数,那么他同时可能也需要一个复制构造函数和一个赋值运算符函数。
动态的Vec类型对象
每次添加一个对象,就重新分配内存,并把原数据复制到新的地址,系统开销是相当大的。
标准向量库采用了一种经典的方式。每次分配比实际多的空间,直到用完后再分配多一半的空间。
所以我们就需要两个末元素的地址,一个指示实际拥有对象的结尾,一个表示空间的结尾。
public:
void push_back(const T& val)
{
if(avail==limit)
{
grow(); //获得需要的空间
}
unchecked_append(val);//将新增元素加入末端
}
private :
iterator data;
iterator limit;
iterator avail;//指针指向构造元素末尾后面的一个元素。
灵活的内存管理
C++的标准库中提供了内存管理的功能,为内存管理者们提供了一个统一的内存管理接口。内存管理功能同输入/输出功能一样,都是标准库的一部分而不是语言的特性,这种特性为我们管理各种各样的内存提供了较大的弹性。
<memory>头文件中提供了allocator<T>类,它可以分配一块预备用来储存T类型对象但是尚未被初始化的内存块,并返回一个指向此内存块头元素的指针。在allocator类中我们目前感兴趣的只有4个成员函数和2个相关的非成员函数。
template <class T> class allocator
{
public:
T* allocate(size_t);
void deallocate(T*,size_t);//释放未被初始化的内存(allocate函数返回的指针,该指针指向的内存块大小)
void construct(T*,T);//在allocate中开辟的尚未被初始化的区域中,进行初始化生成单给对象
void destory(T*);//删除上面生成的对象
};
void uninitialized_fill(T*,T*,const T&);//向内存块中初始化一个值。前两参数间的区域被第个参数的值初始化。
T* uninitialized_copy(T*,T*,T*);//将前两个指针参数区间中的值,复制到第个参数指定的区域处。
综上,我们得到的较完整的Vec定义如下:
template <class T> class Vec { public: typedef T* iterator; //迭代器,指示每一个元素。 typedef const T* const_iterator; typedef size_t size_type; typedef T value_type; //每个元素的类型 Vec() {create();} //默认构造函数,create()函数后期实现 explicit Vec(size_type n,const T& Val=T()) {create(n,Val);} size_type size() const{return limit-data;} T& operator[](size_type i){return data[i];} const T& operator[](size_type i) const {return data[i];} iterator begin(){return data;} //新增 const_iterator begin() const{return data;} //新增 iterator end(){return limit;} //新增 const_iterator end() const{return limit;} //新增 Vec(const Vec& v){create(v.begin(),v.end());}//复制构造函数 Vec& operator=(const Vec&);//复制运算符 ~Vec(){uncreate();} void push_back(const T& val) { if(avail==limit) { grow(); //获得需要的空间 } unchecked_append(val);//将新增元素加入末端 } private : iterator data; iterator limit; iterator avail;//指针指向构造元素末尾后面的一个元素。 allocator<T> alloc;//控制内存分配。 //为底层的输注分配空间并对其进行初始化 void create(); void create(size_type,const T&); void create(const_iterator,const_iterator); //删除数组中的元素并释放其内存 void uncreate(); //支持Push_back的函数 void grow(); void unchecked_append(const T&); }; template <class T> Vec<T>& Vec<T>::operator =(const Vec& rhs) //此处为Vec<T>,不是Vec. { if(&rhs!=this) { uncreate(); //抹去原Vec中的数据。 create(rhs.begin(),rhs.end()); } return *this; }
template <class T> void Vec<t>::create() { data=avail=limit=0; } template <class T> void Vec<T>::create(Vec<T>::size_type n, const T& val) { data=alloc.allocate(n); limit=avail=data+n; uninitialized_fill(data,limit,val); } template <class T> void Vec<T>::create(Vec<T>::const_iterator i, Vec<T>::const_iterator j) { data=alloc.allocate(j-i); limit=avail=uninitialized_copy(i,j,data); } template <class T> void Vec<T>::uncreate() { if(data) { //以相反的顺序删除构造函数生成的元素 iterator it=avail; while(it!=data) { alloc.destroy(--it); } //返回占用的所有内存空间 alloc.deallocate(data,limit-data); } //重置指针表明Vec为空 data=limit=avail=0; }
template <class T> void Vec<T>::grow() { //在扩展对象的大小时,分配现在空间两倍的大小。 size_type new_size=max(2*(limit-data),ptrdiff_t(1));//有可能Vec对象是空的,所以ptrdiff_t(1) //分配新的内存空间,并将目前的对象内容复制到新的内存空间中 iterator new_data=alloc.allocate(new_size); iterator new_avail=uninitialized_copy(data,avail,new_data); //返回原来的内存空间 uncreate(); //重置指针,使其指向新的内存空间 data=new_data; avail=new_avail; limit=data+new_size; } //假定avail新分配,但尚未初始化。 template <class T> void Vec<T>::unchecked_append(const T & val) { alloc.construct(avail++,val); }