当前位置: 首页 > news >正文

【C++】vector的模拟实现和相关接口介绍

文章目录

  • 前言
  • 一、vector
    • 1.vector的定义接口
    • 2.2 vector iterator 的使用
    • 3.vector 空间增长问题
    • 4.vector 增删查改
    • 5.vector 迭代器失效问题。
  • 二、vector的模拟实现
    • 1.vector的定义
    • 2.reserve扩容
    • 3.迭代器(iterator)
    • 4.operator[]
    • 5.push_back()尾插
    • 6.vector的构造函数
    • 7.resize扩容
    • 8. operator=
    • 9 print_container 打印
  • 三、vector的模拟实现代码展示
    • 1. vector.h文件
    • 2.vector.cpp文件
    • 3.test.cpp文件
  • 总结


前言

今天我们来看看vector的相关知识吧。
在这里插入图片描述


一、vector

想要学好vector这就要结合vector的文档了,这里呢跟之前string是大同小异的。大家一定要多多看看文档。这里小编就比较重要的接口吧。如果还想了解其他接口的可以查看文档的哦。

1.vector的定义接口

(constructor)构造函数声明接口说明
vector()(重点)无参构造
vector(size_type n, const value_type& val =value_type())构造并初始化n个val
vector (const vector& x); (重点)拷贝构造
vector (InputIterator first, InputIterator last)迭代器区间构造

2.2 vector iterator 的使用

iterator的使用接口说明
begin /end(重点)获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
rbegin /rend获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator

在这里插入图片描述
在这里插入图片描述

3.vector 空间增长问题

容量空间接口说明
size获取数据个数
capacity获取容量大小
empty判断是否为空
resize(重点)改变svector的size
reserve(重点)改变vector的capacity

注意:

  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。

我们现在可以来验证一下capacity在vs环境下的扩容:

#include<iostream>
#include<vector> 
using namespace std; 
int main()
{
	vector<int> v;
	size_t se = v.capacity();
	cout << "making se grow" << endl;
	for (size_t i = 0; i < 100; i++)
	{
		v.push_back(i);
		if (se != v.capacity())
		{
			se = v.capacity();
			cout <<" capacity changed:" << se << endl;
		}
	}
	return 0;
}

在这里插入图片描述

  • reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题
#include<iostream>
#include<vector> 
using namespace std; 
// 如果已经确定vector中要存储元素大概个数,可以提前将空间设置足够
// 就可以避免边插入边扩容导致效率低下的问题了
int main()
{
	vector<int> v;
	size_t se = v.capacity();
	cout << "making se grow" << endl;
	for (size_t i = 0; i < 100; i++)
	{
		v.reserve(100);//这里给出了要开辟的空间大小,已经全部开好了
		v.push_back(i);
		if (se != v.capacity())
		{
			se = v.capacity();
			cout <<" capacity changed:" << se << endl;
		}
	}
	return 0;
}

在这里插入图片描述

  • resize在开空间的同时还会进行初始化,影响size

4.vector 增删查改

vector增删查改接口说明
push_back(重点)尾插
pop_back (重点)尾删
find查找。(注意这个是算法模块实现,不是vector的成员接口)
insert在position之前插入val
erase删除position位置的数据
swap交换两个vector的数据空间
operator[] (重点)像数组一样访问

5.vector 迭代器失效问题。

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装比如:vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

这里我们用insert来举例:
insert是一个在指定位置插入的接口,先来看看他的代码:

void insert(iterator pos, const T& x) 
{
	if (_finish == _end_of_storage) 
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2); 
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	++_finish;
}

在这里插入图片描述

从逻辑上来看这段代码是完全没有问题的。在不扩容的情况下代码是可以正常云进行的。但是,如果在插入的过程中需要扩容,那代码就会崩溃。那是那是什么原因呢?其实这里就是一个迭代器失效
在这里插入图片描述

当需要扩容时,_start要指向新的空间tmp,同时释放掉旧空间,但是pos是指向旧空间的,释放_start的时候就一起释放掉了,所以这里pos就是野指针了啊。因为他指向的内容已经被释放掉了。既然是野指针那就是不能对他解引用了。

解决方法: 这里的解决方法呢也是很简单,就是更新一下pos的位置就行了 。

void insert(iterator pos, const T& x)  
{
	if (_finish == _end_of_storage)  
	{
		size_t p = pos - _start;//记录一下pos的位置
		reserve(capacity() == 0 ? 4 : capacity() * 2);  
		pos = p + _start; //更新pos的位置 
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = x;
	++_finish;
}

那是不是不扩容迭代器就不会失效呢?我们现在再来看看这段代码,现在我们想在2的位置插入一个数据10,同时我们还让2*10,看看代码的运行结果。

int main()
{
	vector<int>v;
	v.push_back(1);
	v.push_back(2);  
	v.push_back(3);
	v.push_back(4);
	v.push_back(4); 
	auto it = find(v.begin(), v.end(), 2);//find是查找的接口,可以去库里面看一下的
		v.insert(it, 10);
		*(it) *= 10;
	for (auto e : v)
	{
		cout << e << " ";
	}
	return 0;
}

在这里插入图片描述

可以看到这里并没有成功,它直接把插入的数据扩大了10倍,这跟我们预想不一样啊?那是怎么回事呢?insert接口里面我们不是记录并且更新pos的嘛?那为什么不行呢?其实这里还是迭代器失效。看,这里的it是实参,pos是形参,那形参的改变能影响实参吗?当然不可以。所以这里it还是指向的是原来的空间,这里还是失效的。如果一定要访问,那就重新更新一下这个失效迭代器的值。一般情况下再insert之后就不要访问了。因为我们不知道它什么时候扩容!这里由于这里是我们自己模拟实现的代码,代码是可以跑的,但是我们用库里面的函数的时候vs就会强行检查 。
在这里插入图片描述
所以insert之后不要访问。

现在在来看看erase这个函数的迭代器失效的问题,这里erase是删除pos指向的位置的数据。erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此erase之后我们认为迭代器已经失效了。
现在我们以删除所有偶数来举例:

vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4); 
v.push_back(5);
auto it = v.begin();
while (it != v.end())
{
	if (*it % 2 == 0)
	{
		v.erase(it);
	}
	cout << *it << " ";
	++it; 
}

在这里插入图片描述
在这里插入图片描述
看看这两种结果,最后一个数不是偶数和最后一个数是偶数的情况代码的运行结果是不一样的。
解决方法这里的解决方法和上面的一样,就是重新跟新一下 原来的位置。
这里erase库里面给了一个返回值。就是返回指向下一个数据的位置。所以我们可以用库里面的函数来完成删除所有偶数这个 问题。

std::vector<int> v;                 
v.push_back(1); 
v.push_back(2);    
v.push_back(3);    
v.push_back(4); 
std::vector <int >::iterator it = v.begin();   
while (it != v.end())
{
	if (*it % 2 == 0) 
	{
		it=v.erase(it);   
	} 
	else
	{
		cout << *it << " ";
		it++; 
	   
	}
}

在这里插入图片描述

二、vector的模拟实现

在这里插入图片描述

1.vector的定义

vector就是一个类模板,底层还是数组,不过这里是用指针控制的。

#include<iostream>
#include<assert.h>
#include<vector>
template<class T>
class vector
{
	typedef  T* iterator;
	typedef const T* const_iterator;
public:
//由于这两函数比较简单我就先放在这里面了
size_t size()
{
	return _finish - _start;
}
size_t capacity()
{
	return _end_of_storage - _start;
}
private:
	iterator _start=nullptr;
	iterator _finish=nullptr;
	iterator _end_of_storage=nullptr;
};

2.reserve扩容

这里我们要检查一下是否需要扩容。如果要扩容就申请空间拷贝数据 。

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t old_size = size();
		T* tmp = new T[n];
		//memcpy(tmp, _start, old_size * sizeof(T); 浅拷贝 
		if (_start)
		{
			for (size_t i = 0; i < old_size; i++)
			{
				tmp[i] = _start[i]; 
			}
			delete _start;
		}
		_start = tmp;
		_finish = tmp + old_size;
		_end_of_storage = tmp + n;
	}
}

3.迭代器(iterator)

迭代器分为两种,一种是普通迭代器,另一种就是const修饰的迭代器

typedef  T* iterator;
typedef const T* const_iterator;
iterator begin()
{
	return _start;
}
iterator end()
{
	return _finish;
}
const_iterator begin()const 
{
	return _start;
}
const_iterator end() const 
{
	return _finish;
}

4.operator[]

运算符重载也有两种,一种是可修改的。另一种则是不可修改的

T& operator[](size_t i)
{
	return _start[i];
}
const T& operator[](size_t i)const 
{
	return  _start[i]; 
}

5.push_back()尾插

void push_back(const T& x)
{
	if (_finish == _end_of_storage)  
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}
	*_finish = x;
	++_finish;
}

这里可以这样写,还可以写成复用一下insert。同时还可以写出头插的

void push_back(const T& x)//尾插
{
	insert(end(), x); 
}

void push_front(const T&x)//头插
{
     insert(begin(),x);
}

6.vector的构造函数

vector()
{}
vector() = default;//强制生成
//这两种都是可以的

vector(const vector<T>& v)
{
	reserve(v.size());
	for (auto& e : v)
	{
		push_back(e);
	}
}
~vector()
{
	delete _start;
	_start = _finish = _end_of_storage;  
}
vector(size_t n, const T& val = T())  //用n个val构造
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
}
template <class inputiterator >//这个是vector的区间构造函数
vector(inputiterator first,inputiterator last)
{
	while (first != last)  
	{
		push_back(*first);
		++first;   
	}
}

7.resize扩容

resize就是开辟n个空间,然后再用val初始化。如果原来的数组里面有数据,那就接着数据后面依次插入val。这里一共分为三种情况。
第一种:n<=size 直接删除大于n的部分数据,
第二种:size<n<capacity 在已有的空间里面插入val
第三种 :n>capacity 这里需要扩容,然后依次插入val

void resize(size_t n, const T& val = T())
{
	if (n <=size())  
	{
		_finish =_start+ n;   
		return;
	}
	else
	{
		reserve(n);//这里就传的n会在reserve函数中在判断一下。
		while (_finish < _start + n)
		{

			*_finish = val;
			++_finish;
		}
	}
}

8. operator=

赋值运算重载,这里我们可以先把被赋值的对象清理一下。然后再依次插入数据。这样就比之前那种先开辟赋值对象一样大的空间,,然后再拷贝过去的方法要巧妙得多了。这里我们让程序本身来完成这想问题。这里呢有两种写法:
第一种:就是用程序直接完成。

void clear()
{
	_finish=_start;
}
vector<T>& operator=( vector<T>& v) 
{
	clear();
	for (auto& e : v)
	{
		push_back(e);
	}
	return *this;
}

第二种:把两个对象里面得成员变量交换一下

void swap(vector<T>&v)
{
	std::swap(_start, v._start);
	std::swap(_finish, v._finish );
	std::swap(_end_of_storage, v._end_of_storage ); 
}
vector<T>& operator=( vector<T>& v) 
{
	clear();
	 swap(v);
	return *this;
}

9 print_container 打印

这就是一个打印函数。我们来想想,如果我们想打印vector 就要用vector得打印函数,打印string就要用string的打印函数。那我们可以不可以写一个打印的任何形式的模板啊?那我们来实现一下吧。

template <class container>
void print_container(const container&v)     
{
	typename container::const_iterator it = v.begin(); 
	while (it != v.end()) 
	{
		cout << *it << " ";
		++it; 
	}
}

但是这里要特别注意typename哪里。如果这里不加typename的话。那么程序是会报错的。那是因为编译器规定,不能再没有实例化的类模板中取东西。因为编译器无法确定const_iterator 中是类型还是成员变量。所以这用了typename就是要程序员自己确定。这里也可以用auto让他自己去推导。

template <class container>
void print_container(const container&v)     
{
	auto it = v.begin(); 
	while (it != v.end()) 
	{
		cout << *it << " ";
		++it; 
	}
}

三、vector的模拟实现代码展示

1. vector.h文件

 
#include<iostream>
#include<assert.h>
#include<vector>
using namespace std; 
namespace V
{
	template<class T>
	class vector
	{
	public:  
			typedef  T* iterator;
			typedef const T* const_iterator;
			iterator begin()
			{
				return _start;
			}
			iterator end()
			{
				return _finish;
			}
			const_iterator begin()const 
			{
				return _start;
			}
			const_iterator end() const 
			{
				return _finish;
			}
		T& operator[](size_t i)
		{
			return _start[i];
		}
		const T& operator[](size_t i)const 
		{
			return  _start[i]; 
		}
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t old_size = size();
				T* tmp = new T[n];
				//memcpy(tmp, _start, old_size * sizeof(T); 浅拷贝 
				if (_start)
				{
					for (size_t i = 0; i < old_size; i++)
					{
						tmp[i] = _start[i]; 
					}
					delete _start;
				}
				_start = tmp;
				_finish = tmp + old_size;
				_end_of_storage = tmp + n;
			}
		}
		size_t size()
		{
			return _finish - _start;
		}
		size_t capacity()
		{
			return _end_of_storage - _start;
		}
		void push_back(const T& x)
		{
			insert(end(), x);  
			/*if (_finish == _end_of_storage)  
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = x;
			++_finish;*/
		}
		void insert(iterator pos, const T& x)   
		{
			if (_finish == _end_of_storage)  
			{
				size_t p = pos - _start;//记录一下pos的位置
				reserve(capacity() == 0 ? 4 : capacity() * 2);  
				pos = p + _start; //更新pos的位置 
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			++_finish;
		}
		void   erase(iterator pos)  
		{
			iterator it = pos + 1;
			while (it != end())
			{
				*(it - 1) = *it;
				it++;
			} 
			--_finish;    
		}
		vector()
		{}
		vector(const vector<T>& v)
		{
			reserve(v.size());
			for (auto& e : v)
			{
				push_back(e);
			}
		}
		~vector()
		{
			delete _start;
			_start = _finish = _end_of_storage;  
		}
		vector(size_t n, const T& val = T())  
		{
			reserve(n);
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		void resize(size_t n, const T& val = T())
		{
			if (n <=size())  
			{
				_finish =_start+ n;   
				return;
			}
			else
			{
				reserve(n);
				while (_finish < _start + n)
				{

					*_finish = val;
					++_finish;
				}
			}
		}
		template <class inputiterator >
		vector(inputiterator first,inputiterator last)
		{
			while (first != last)  
			{
				push_back(*first);
				++first;   
			}
		}
		void clear()
		{
			_finish=_start;
		}
		void swap(vector<T>&v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish );
			std::swap(_end_of_storage, v._end_of_storage ); 
		}
		vector<T>& operator=( vector<T>& v) 
		{
			clear();
			//swap(v);
		/*	for (auto& e : v)
			{
				push_back(e);
			}*/
			return *this;
		}
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};
	void test_vector();
	template <class container>
	void print_container(const container&v)     
	{
		auto it = v.begin(); 
		while (it != v.end()) 
		{
			cout << *it << " ";
			++it; 
		}
	}
}

2.vector.cpp文件

主要是测试文件

#include"vector.h"
namespace V
{

	void test_vector()
	{
		vector<int> v;
		vector<int >x;
		x.push_back(1);
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(3);
		x = v;
		print_container(v);
	}
}

3.test.cpp文件

#include"vector.h"
using namespace std; 
int main()
{
	V::test_vector();
	
	return 0;
}

总结

好了,今天就分享到这里了。欢迎大家留言。
在这里插入图片描述

相关文章:

  • 通过通道扩展实现 NI-DAQmx 的同步与触发
  • Python FastAPI + Celery + RabbitMQ 分布式图片水印处理系统
  • 软件工程-UML
  • DEBUG:工具简介
  • Python----机器学习(线性回归:自求导的方法实现)
  • Redis 的哨兵模式
  • 蓝桥杯 01游戏
  • 数据结构 哈希表 字符串哈希
  • VMware安装Ubuntu实战分享
  • 【算法学习计划】贪心算法(下)
  • 在ensp进行OSPF+RIP+静态网络架构配置
  • [GESP202503 C++六级题解]:P11963:环线
  • 关于VMware Tools 不再随旧版客户机操作系统的 VMware Workstation 一起提供。
  • 高级java每日一道面试题-2025年3月22日-微服务篇[Nacos篇]-Nacos的主要功能有哪些?
  • TBKDVR硬盘录像机device.rsp命令执行漏洞
  • CISCO路由器配置DHCP中继
  • YOLOv12即插即用-Pconv(风车卷积)
  • QT自定义信号与槽
  • NHANES指标推荐:TyG-BMI
  • 自然语言处理|如何用少样本技术提升低资源语言处理?
  • 联合国报告:全球经济前景恶化,面临高度不确定性
  • 哈马斯官员:若实现永久停火,可交出加沙地带控制权
  • 上海高院与上海妇联签协议,建立反家暴常态化联动协作机制
  • Offer触手可及,2025上海社会组织联合招聘专场活动正寻找发光的你
  • 德国总理默茨发表首份政府声明:将提升国防能力,全力发展经济
  • 西班牙政府排除因国家电网遭攻击导致大停电的可能