首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【C++】STL--Vector使用及其模拟实现

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

作者头像
小陈又菜
发布2025-12-24 10:54:01
发布2025-12-24 10:54:01
180
举报

1. Vector的介绍

官方文档介绍

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

2. Vector的使用

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

vector模拟实现的基本结构:

代码语言:javascript
复制
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);

使用迭代器进行初始化构造

代码语言:javascript
复制
//无参构造
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

代码语言:javascript
复制
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

代码语言:javascript
复制
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两个环境下试一下:

代码语言:javascript
复制
//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(尾插)

代码语言:javascript
复制
//尾插
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(尾删)

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

代码语言:javascript
复制
//尾删
void pop_back(const T& x)
{
	if (_finish > _start)
	{
		--_finish;
	}
}

3.3. insert

  • 首先要判断pos是否合法
  • 然后判断是否需要扩容,如果需要这里采用了一个相对量来保证pos的正确性
  • 然后从后往前移动元素,前面的覆盖后面的
  • 最后插入值,然后更新_finish,返回pos
代码语言:javascript
复制
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
代码语言:javascript
复制
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所在位置的元素即可:

代码语言:javascript
复制
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. 测试

代码语言:javascript
复制
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;
}

(本篇完)

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-09-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 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. 测试
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档