在实现本文的vector模拟前,建议先了解关于vector的必要知识:【C++】容器vector常用接口详解-CSDN博客https://blog.csdn.net/2301_80555259/article/details/141529230?spm=1001.2014.3001.5501
目录
一.基本结构
二.构造函数(constructor)
三.迭代器(iterator)
四.容量操作(capacity)
1.resize
2.reserve
五. 修改操作(modify)
六.访问操作(access)
七.重载赋值运算符
八.析构函数(destructor)
九.打印(print)
一.基本结构
创建头文件:vector.h 实现文件:vector.cpp 测试文件:test.cpp
当然此处为了简化直接使用了头文件+测试文件,在自定义的命名空间内创建vector类,配上基本结构:
由于vector是顺序表,因此和C语言实现顺序表时一样,至上有三个参数:
- 指向一段连续空间的指针
- 空间的有效大小
- 空间的实际大小
实现vector的迭代器,可以就将其视为指针,因此可以使用三个迭代器(指针)就完成上述三个参数的实现,它们分别是_start,_finish,_end_of_storage,此时
- 指向一段连续空间的指针:_start
- 空间的有效大小size:_finish - _start
- 空间的实际大小capacity:_end_of_storage - _start
template<class T>
class vector
{
public:
//用指针实现vector的迭代器
typedef T* iterator;
typedef const T* const_iterator;
private:
iterator _start;
iterator _finish;
iterator _end_of_storage;
};
二.构造函数(constructor)
//无参构造
vector()
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{}
//带参构造,用n个value来初始化vector
vector(int n, const T& value = T())
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
reserve(n);
for (int i = 0; i < n; i++)
{
push_back(value);
}
}
//拷贝构造
vector(const vector& v)
:_start(nullptr)
,_finish(nullptr)
,_end_of_storage(nullptr)
{
//易错点:这里并没有进行初始化
reserve(v.size());
for (auto e : v)
{
push_back(e);
}
}
//迭代器区间构造
template<class InputIterator>
vector(InputIterator first, InputIterator last)
:_start(nullptr)
, _finish(nullptr)
, _end_of_storage(nullptr)
{
while (first != last)
{
push_back(*first);
first++;
}
}
这里单独把这个构造拿出来谈谈, 最开始我只在无参构造中使用初始化列表,没有对其他构造函数的_start等进行初始化,这个bug我找了半天,每一个构造也是构造函数,跟其他构造函数没有关系,是凭借拷贝构造自身创建一个新的对象,因此也要进行初始化序列来初始化。这是最为保险的,否则就要在成员变量声明处加上默认值。
发现这个错误的bug就在使用赋值重载时,临时变量需要调用拷贝构造,但若此时拷贝构造没有去手动写初始化列表,那么其_finish和_end_of_storage不是nullptr,是随机值,这样就导致了错误然而在有参构造时,尽管没去写初始化列表,对应的_start和_finish等也都是nullptr,此时就没有问题,但是为了保险,我建议所有构造函数都应该去手写一遍初始化列表。
三.迭代器(iterator)
iterator begin()
{
return _start;
}
iterator end()
{
return _finish;
}
const_iterator begin()const
{
return _start;
}
const_iterator end()const
{
return _finish;
}
四.容量操作(capacity)
容量操作方面的函数有:
- size
- capacity
- empty
- resize
- reserve
前三个较为简单,很容易实现:
size_t size()const
{
return _finish - _start;
}
size_t capacity()const
{
return _end_of_storage - _start;
}
bool empty()const
{
return size() == 0;
}
1.resize
resize会改变size的大小,同时也有可能改变capacity的大小,具体要分三种情况:
- n大于capacity时:直接套用reserve扩容后再初始化新内容
- n大于size小于capacity时:有效值不变的情况下初始化新内容
- n小于size时:直接将size缩小到n
void resize(size_t n, const T& value = T())
{
if (n < size())
{
_finish = _start + n;
}
else
{
reserve(n);
while (_finish != _start + n)
{
*_finish = value;
_finish++;
}
}
}
如何来理解:const T& value = T() ?
在C++中内置类型(如int,double,char)特殊处理过,升级为了类,也有默认构造函数,它会将变量初始化为默认值。
此处使用了匿名对象int tmp1 = int();//0 int tmp2 = int(10);//10
2.reserve
reserve扩容也是同理,只在n大于capacity的时候进行扩容
void reserve(size_t n)
{
if (n > capacity())
{
//记录原本的数据个数
size_t old_size = size();
iterator tmp = new T[n];
memcpy(tmp, _start, old_size * sizeof(T));
delete[] _start;
_start = tmp;
_finish = tmp + old_size;
_end_of_storage = tmp + n;
}
}
五. 修改操作(modify)
//modify
void push_back(const T& x)
{
if (_finish == _end_of_storage)
{
reserve(capacity() == 0 ? 4 : 2 * capacity());
}
*_finish = x;
_finish++;
}
void pop_back()
{
assert(size() > 0);
--_finish;
}
iterator insert(iterator pos, const T& x)
{
assert(pos >= _start);
assert(pos <= _finish);
if (_finish == _end_of_storage)
{
//注意这里扩容会使原有的pos迭代器失效
//因此这里使用len计算相对长度,更新迭代器
size_t len = pos - _start;
reserve(capacity() == 0 ? 4 : 2 * capacity());
pos = _start + len;
}
iterator end = _finish;
while (end > pos)
{
*end = *(end - 1);
end--;
}
*pos = x;
++_finish;
return pos;
}
void erase(iterator pos)
{
assert(pos >= _start);
assert(pos < _finish);
iterator it = pos + 1;
while (it < _finish)
{
*(it - 1) = *it;
it++;
}
--_finish;
}
六.访问操作(access)
//access
T& operator[](size_t i)
{
assert(i < size());
return _start[i];
}
const T& operator[](size_t i)const
{
assert(i < size());
return _start[i];
}
七.重载赋值运算符
注意这里的参数不是常量引用,而是按值传递。这是因为之后调用swap函数,按值传递可以使得传入的参数会被复制一份给临时对象,从而避免了对原对象的修改。
//operator =
void swap(vector& v)
{
std::swap(_start, v._start);
std::swap(_finish, v._finish);
std::swap(_end_of_storage, v._end_of_storage);
}
//在模板内,写vector和vector<T>都行
//因为要交换内部内容,因此不加const
vector& operator=(vector v)
{
swap(v);
return *this;
}
八.析构函数(destructor)
~vector()
{
if (_start)
{
delete[] _start;
_start = _finish = _end_of_storage = nullptr;
}
}
九.打印(print)
template<class T>
void print_vector(const vector<T>& v)
{
//规定,从没有实例化的类模版中取东西
//编译器不能区分const_iterator是类型还是静态成员变量,因此要加typename
//当然也可以更简单写为auto it = v.begin();
typename vector<T>::const_iterator it = v.begin();
while (it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
template<class container>
void print_container(const container& v)
{
for (auto e : v)
{
cout << e << " ";
}
cout << endl;
}
本文结束,下次将会介绍容器中的list,和vector是类似的,但也有不同,欢迎持续关注