【C++】STL--Vector使用及其模拟实现

目录

1. Vector的介绍

2. Vector的使用

2.1. vector的定义

2.2. vector迭代器的使用

2.3. vector的空间增长问题

3. vector中的增删改查

3.1. push_back(尾插)

3.2. pop_back(尾删)

3.3. insert

3.4 erase

3.5 operator [ ] 

4. 测试


1. Vector的介绍

官方文档介绍

  • vector表示可变大小数组的序列容器
  • 与数组一样,vector采用的是使用连续空间来储存元素,那也就意味着,采用下标对vector进行访问和数组一样高效。但是相较于数组大小的固定,vector的大小是动态可变的,并且这个变化是由容器自动处理的
  • 本质上讲,vector是动态分配数组大小实现的,当有新元素插入时,当需要扩容空间时,数组的大小将会重新分配。具体的做法是,重新开辟一块更大的内存,然后将原数组的元素拷贝过去(很明显是一个 代价很高的任务)
  • vector的空间分配策略:每一次分配空间都是按照对数级增长(一般是1.2或者是1.5),形象地来讲就是一种囤地策略,每次重新分配都会多囤一点,这样的话插入时,有地方就话直接插入,如此每一次重新分配内存的高额代价就会被多囤地的免费插入摊平,总的来看,末尾插入的时间代价还是常数
  • 总的来说,vector访问元素、在末尾添加删除元素比较高效,但是其他操作相对效率低

2. Vector的使用

vector在日常的使用非常广泛,我们应该熟悉它的常用接口。接下来我们从基础的接口开始,学会它的使用及模拟实现。

vector模拟实现的基本结构:

template<class T>
class vector
{
public:
	typedef T* iterator;
	typedef const T* const_iterator;

	//无参构造
	vector()
		:_start(nullptr)	//数组首地址
		, _finish(nullptr)	//当前末尾元素的下一个地址
		, _endofstoage(nullptr)	//当前分配空间的下一地址
	{
	}

	//资源管理
	~vector()
	{
		if (_start)
		{
			delete[] _start;
			_start = _finish = _endofstoage = nullptr;
		}
	}


	size_t size() const
	{
		return _finish - _start;
	}


	size_t capacity() const
	{
		return _endofstoage - _start;
	}
private:
	iterator _start;
	iterator _finish;
	iterator _endofstoage;
};

2.1. vector的定义

构造函数声明constructor接口说明
vector() (重点)无参构造
vector (const vector& x); (重点)拷贝构造
vector ( size_type n, const value_type& val = value_type() )构造并初始化 n 个 val
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造
//无参构造
vector()
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstoage(nullptr)
{}
 
//拷贝构造
void swap(vector<T>& v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish);
	std::swap(_endofstoage, v._endofstoage);
}
 
//vector(const vector& v)
vector(const vector<T>& v)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstoage(nullptr)
{
	vector tmp(v.begin(), v.end());
	swap(tmp);
}
 
//初始化n个val 
vector(size_t n, const T& val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstoage(nullptr)
{
	reserve(n);
	for (size_t i = 0; i < n; ++i)
	{
		push_back(val);
	}
}
 
//使用迭代化区间初始化
template <class InputIterator>
vector(InputIterator first, InputIterator last)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstoage(nullptr)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

2.2. vector迭代器的使用

iterator的使用接口说明
begin+end (重点) 获取第一个数据位置的 iterator/const_iterator , 获取最后一个数据的下一个位置的 iterator/const_iterator
rbegin+rend(反向迭代器)获取最后一个数据位置的 reverse_iterator ,获取第一个数据前一个位置的reverse_iterator
iterator begin()
{
	return _start;
}
iterator end()
{
	return _finish;
}
 
const iterator begin() const
{
	return _start;
}
const iterator end() const
{
	return _finish;
}

2.3. vector的空间增长问题

容量空间接口说明
size获取数据个数
capacity获取容量大小
empt判断是否为空
resize(重点)改变 vector 的 size
reserve(重点) 改变 vector 的 capacity
void resize(size_t n, T val = T())
{
	if (n > capacity())
	{
		reserve(n);
	}
	if (n > size())
	{
		while (_finish <= _start + n)
		{
			*_finish = val;
			++_finish;
		}
	}
	else
	{
		_finish = _start + n;
	}
}

void reserve(size_t n)
{
	size_t sz = size();
	if (n > capacity())
	{
		T* tmp = new T[n];
		if (_start)
		{
			for (int i = 0; i < sz; ++i)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
		}
		_start = tmp;
	}
	_finish = _start + sz;
	_endofstoage = _start + n;

}

注意:

vector在扩容的时候有一个小细节,那就是vector的扩容在vs和gcc下是有区别的,vs下是1.5倍扩容,然而在在gcc下是2倍扩容。所以不能固话地认为vector的扩容就是2倍。具体增长的多少要根据需求定义。Vs是PJ盘本的STL,g++是SGI版本的STL。

我们可以在VS和Clion两个环境下试一下:

//vs下
int main()
{
	vector<int> v;
	size_t sz = v.capacity();
	for (int i = 0; i < 100; ++i)
	{
		v.push_back(i);
		if (sz != v.capacity())
		{
			sz = v.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	}
 
	return 0;
}

VS中的结果:

Clion中gcc的结果:


3. vector中的增删改查

vector 增删查改

接口说明

push_back (重点)

尾插

pop_back (重点)

尾删

find

查找。(注意这个是算法模块实现,不是 vector 的成员接口)

insert

在 pos 之前插入 val

erase

删除 pos 位置的数据

swap

交换两个 vector 的数据空间

operator[] (重点)

像数组一样访问

3.1. push_back(尾插)

//尾插
void push_back(const T& x)
{
	if (_finish == _endofstoage)
	{
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
	}
	*_finish = x;
	++_finish;
}

3.2. pop_back(尾删)

这个地方熟悉一点的同学都知道,尾删的逻辑是覆盖而并不是删除:

//尾删
void pop_back(const T& x)
{
	if (_finish > _start)
	{
		--_finish;
	}
}

3.3. insert

  • 首先要判断pos是否合法
  • 然后判断是否需要扩容,如果需要这里采用了一个相对量来保证pos的正确性
  • 然后从后往前移动元素,前面的覆盖后面的
  • 最后插入值,然后更新_finish,返回pos
iterator insert(iterator pos, const T& x)
{
	assert(pos <= _finnsh && pos >= _start);
	if (_finish == _endofstoage)
	{
		//这里使用pos到首地址的偏移量
        //因为扩容之后原先pos的失效了
        size_t n = pos - _start;
		size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
		reserve(newcapacity);
		pos = _start + n;
	}
	iterator end = _finish - 1;
	while(end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	++_finish;

	return pos;
}

PS:注意这里我们将pos声明为iterator,可能有人会问为什么不使用下标更加方便呢?当时在这里使用下标确实会更加方便,但是这个容器不是vector了,而是list或者map,就并不适配。


3.4 erase

  • 判断pos是否合法
  • 然后循环congpos开始,后一个覆盖前一个
  • 更新_finish
  • 返回pos
iterator erase(iterator pos)
{
	assert(pos >= _start && pos <= _finish);
	iterator end = pos + 1;
	while (end < _finish)
	{
		*(end - 1) = *end;
		++end;
	}
	--_finish;
	return pos;
}

3.5 operator [ ] 

这个函数比较简单,只要返回pos所在位置的元素即可:

T& operator[](size_t pos)
{
	assert(pos < size());
	return _start[pos];
}

const T& operator[](size_t pos) const
{
	assert(pos < size());
	return _start[pos];
}

4. 测试

int main() {
	// 1. 默认构造 + push_back
	v::vector<int> v1;
	for (int i = 1; i <= 5; i++) {
		v1.push_back(i * 10);
	}

	cout << "v1 after push_back: ";
	for (size_t i = 0; i < v1.size(); i++) {
		cout << v1[i] << " ";
	}
	cout << "\n";

	// 2. 测试 insert
	auto it = v1.begin() + 2;
	v1.insert(it, 99);

	cout << "v1 after insert 99 at pos 2: ";
	for (size_t i = 0; i < v1.size(); i++) {
		cout << v1[i] << " ";
	}
	cout << "\n";

	// 3. 测试 erase
	v1.erase(v1.begin() + 3);

	cout << "v1 after erase at pos 3: ";
	for (size_t i = 0; i < v1.size(); i++) {
		cout << v1[i] << " ";
	}
	cout << "\n";

	// 4. 测试 pop_back
	v1.pop_back();
	cout << "v1 after pop_back: ";
	for (size_t i = 0; i < v1.size(); i++) {
		cout << v1[i] << " ";
	}
	cout << "\n";

	// 5. 测试拷贝构造
	v::vector<int> v2 = v1;
	cout << "v2 (copy of v1): ";
	for (size_t i = 0; i < v2.size(); i++) {
		cout << v2[i] << " ";
	}
	cout << "\n";

	// 6. 测试初始化构造 (5个元素,值全为 7)
	vector<int> v3(5, 7);
	cout << "v3 (5 elements = 7): ";
	for (size_t i = 0; i < v3.size(); i++) {
		cout << v3[i] << " ";
	}
	cout << "\n";

	// 7. 测试迭代器区间构造
	v::vector<int> v4(v1.begin(), v1.end());
	cout << "v4 (constructed from v1): ";
	for (size_t i = 0; i < v4.size(); i++) {
		cout << v4[i] << " ";
	}
	cout << "\n";

	return 0;
}


(本篇完)

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值