一、STL向量容器vector简单实现
1、容器的定义
首先,我们来了解一下什么是容器,他的书面解释各位可以百度,在这儿我们就用一种通俗的方式来解释他,容器就是保存其他对象的对象。而且这种对象还有处理其他对象的方法。
C++采用基于模版的方式处理容器,STL中的容器提供了多种数据结构。
2、vector容器
(1)定义
是一个线性顺序结构。相当于数组,但其大小可以不预先指定,并且自动扩展。它可以像数组一样被操作,由于它的特性我们完全可以将vector 看作动态数组。
(2)特点
- 可以直接访问任何元素。
- 线性顺序结构。可以指定一块连续的空间,也可以不预先指定大小,空间可自动扩展,也可以像数组一样被操作,即支持[ ]操作符和vector.at(),因此可看做动态数组,通常体现在追加数据push_back()和删除末尾数据pop_back()。
- 当分配空间不够时,vector会申请一块更大的内存块(以2的倍数增长),然后将原来的数据拷贝到新内存块中并将原内存块中的对象销毁,最后释放原来的内存空间。因此如果vector保存的数据量很大时会很消耗性能,因此在预先知道它大小时性能最优。
- 节省空间。因为它是连续存储,在存储数据的区域是没有浪费的,但实际上大多数时候是存不满的,因此实际上未存储的区域是浪费的
- .在内部进行插入和删除的操作效率低。由于vector内部按顺序表结构设计,因此这样的操作基本上是被禁止的,它被设计成只能在后端进行追加和删除操作。
他的布局如下:
下面我们来看看他的具体代码实现:
template<typename T>
class vector
{
public:
vector(int size = 10)
{
_first = new T[size];
_last = _first;
_end = _first + size;
}
~vector()
{
delete[] _first;
_first = _last = _end = nullptr;
}
vector(const vector<T>& rhs)
{
int size = rhs._end - rhs._first;
_first = new T[size];
//拷贝有效元素
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
_first[i] = rhs._first[i];
}
_last = _first + len;
_end = _first + size;
}
vector<T>& operator = (const vector<T>& rhs)
{
if (this == &rhs)
return *this;
delete[] _first;
int size = rhs._end - rhs._first;
_first = new T[size];
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
_first[i] = rhs._first[i];
}
_last = _first + len;
_end = _first + size;
return *this;
}
void push_back(const T &val)//向容器末尾添加元素
{
if (full())
expand();
*_last++ = val;
}
void pop_back()//从容器末尾删除元素
{
if (empty())
return;
--_last;
}
T back() const//返回容器末尾的元素的值
{
return *(_last - 1);
}
bool full()const { return _last == _end; }
bool empty()const { return _first == _last; }
int size()const { return _last - _first; }
private:
T* _first;//指向数组起始的位置
T* _last;//指向数组中有效元素的后继位置
T* _end;//指向数组空间的后继位置
void expand()//容器的二倍扩容
{
int size = _end - _first;
T* ptmp = new T[2 * size];
for (int i = 0; i < size; ++i)
{
ptmp[i] = _first[i];
}
delete[]_first;
_first = ptmp;
_last = _first + size;
_end = _first + 2 * size;
}
};
int main()
{
vector<int> vec;
for (int i = 0; i < 20; ++i)
{
vec.push_back(rand() % 100);
}
//vec.pop_back();
while (!vec.empty())
{
cout << vec.back() << " ";
vec.pop_back();
}
cout << endl;
return 0;
}
运行结果如下:
二、容器空间适配器allocator简单实现
上述vector容器的实现,还缺少一个叫空间配置器allocator的东西。
首先,我们来看一看c++标准库里面定义的vector
template<class _Ty,
class _Alloc = allocator<_Ty> >
class vector
: public _Vector_alloc<!is_empty<_Alloc>::value,
_Vec_base_types<_Ty, _Alloc> >
然后我们再来想一想,上述vector容器存在什么问题。
问题一:
当我们用Test对象来实例化时,运行结果如何?
Test实现如下:
class Test
{
public:
Test(){cout << "Test()" << endl;}
~Test(){cout << "~Test" << endl;}
};
运行结果如下图:
运行结果含义:
因为我们在写构造函数的时候size大小就为10,在这个地方,他是一个空容器,但是却构造了10个Test对象,又析构了10次。因为构造时用了new,它不仅仅会开辟空间,还会去构造对象,因此构造了10个对象
错误原因:
- 定义容器对象时,底层只是进行空间开辟,而不能去构造对象。但是如果使用new的话,它会同时完成这两件事。
- 析构时用了delete,将_first指针指向的数组每一个元素都当作有效的test对象析构了一遍。数组可能会很长,但是里面有效的元素可能只有几个,我们析构时只析构有效的元素,再将整个数组内存释放。
再仔细想想,上述代码还存在些许问题
问题二:
执行如下代码:
int main()
{
Test t1, t2, t3;
cout << "-------------------------" << endl;
vector<Test> vec;
vec.push_back(t1);
vec.push_back(t2);
vec.push_back(t3);
cout << "-------------------------" << endl;
vec.pop_back();
cout << "-------------------------" << endl;
return 0;
}
运行结果如下:
运行结果含义:
底层使用了new,每一个位置其实已经放了test对象,push_back时,相当于给已经存在的test对象赋值,就出现了问题。
但也不能使用delete析构,delete不仅仅调用析构,还做了free操作,我们应该只析构,不释放数组堆内存。
解决方法:
1.把内存开辟与对象构造分开处理
2.析构容器有效的元素,然后释放_first指针指向的堆内存
3.只需要析构对象,将对象的析构和内存分离开
容器的空间配置器allocator:空间配置器的核心功能就是把对象的内存开辟和对象构造的过程分解开,对象析构和内存释放的过程分解开。 容器底层内存开辟,内存释放,对象构造和析构,都通过allocator空间适配器来实现。
具体代码实现如下:
template<typename T>
struct Allocator
{
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
}
void destory(T* p)//负责对象析构
{
p->~T();//~T()代表了T类型的析构函数
}
};
/*
容器低沉内存开辟,内存释放,对象构造析构,都通过allocator空间配置器来实现
*/
template<typename T,typename Alloc = Allocator<T>>
class vector
{
public:
vector(int size = 10)
{
//需要把内存开辟和对象构造处理
_first = _allocator.allocate(size);
_last = _first;
_end = _first + size;
}
~vector()
{
//析构容器有效的元素,然后释放_first指针指向的堆内存
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p);//把_first指针指向的数组的有效元素进行析构操作
}
_allocator.deallocate(_first);//释放堆上的数组内存
_first = _last = _end = nullptr;
}
vector(const vector<T>& rhs)
{
int size = rhs._end - rhs._first;
//拷贝有效元素
_first = _allocator.allocate(size);
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
_allocator.construct(_first + i, rhs._first[i]);
}
_last = _first + len;
_end = _first + size;
}
vector<T>& operator = (const vector<T>& rhs)
{
if (this == &rhs)
return *this;
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p);//把_first指针指向的数组的有效元素进行析构操作
}
_allocator.deallocate(_first);//释放堆上的数组内存
int size = rhs._end - rhs._first;
_first = new T[size];
int len = rhs._last - rhs._first;
for (int i = 0; i < len; ++i)
{
_first[i] = rhs._first[i];
}
_last = _first + len;
_end = _first + size;
return *this;
}
void push_back(const T &val)//向容器末尾添加元素
{
if (full())
expand();
_allocator.construct(_last, val);
_last++;
}
void pop_back()//从容器末尾删除元素
{
if (empty())
return;
//--_last;不仅要把_last指针--,还需要析构删除的元素
--_last;
_allocator.destory(_last);
}
T back() const//返回容器末尾的元素的值
{
return *(_last - 1);
}
bool full()const { return _last == _end; }
bool empty()const { return _first == _last; }
int size()const { return _last - _first; }
private:
T* _first;//指向数组起始的位置
T* _last;//指向数组中有效元素的后继位置
T* _end;//指向数组空间的后继位置
Alloc _allocator;//定义容器的空间配置器对象
void expand()//容器的二倍扩容
{
int size = _end - _first;
//T* ptmp = new T[2 * size];
T* ptmp = _allocator.allocate(2 * size);
for (int i = 0; i < size; ++i)
{
_allocator.construct(ptmp + i, _first[i]);
//ptmp[i] = _first[i];
}
delete[]_first;
for (T* p = _first; p != _last; ++p)
{
_allocator.destroy(p);
}
_allocator.deallocate(_first);
_first = ptmp;
_last = _first + size;
_end = _first + 2 * size;
}
};
class Test
{
public:
Test(){ cout << "Test()" << endl; }
~Test() { cout << "Test()" << endl; }
};