【C++】vector类:模拟实现(适合新手手撕vector)

在实现本文的vector模拟前,建议先了解关于vector的必要知识:【C++】容器vector常用接口详解-CSDN博客icon-default.png?t=N7T8https://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语言实现顺序表时一样,至上有三个参数:

  1. 指向一段连续空间的指针
  2. 空间的有效大小
  3. 空间的实际大小

实现vector的迭代器,可以就将其视为指针,因此可以使用三个迭代器(指针)就完成上述三个参数的实现,它们分别是_start,_finish,_end_of_storage,此时

  1. 指向一段连续空间的指针:_start
  2. 空间的有效大小size:_finish - _start
  3. 空间的实际大小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的大小,具体要分三种情况:

  1. n大于capacity时:直接套用reserve扩容后再初始化新内容
  2. n大于size小于capacity时:有效值不变的情况下初始化新内容
  3. 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是类似的,但也有不同,欢迎持续关注

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值