C++入门 vector的使用 + 进阶【模拟实现】

2023-05-16

在这里插入图片描述

目录

  • 基本接口函数介绍
    • 迭代器
    • 空间容量
    • 增删查改
    • 迭代器失效问题探讨
  • vector模拟实现
    • reserve
    • 迭代器
    • 空间容量
    • 删除
    • insert
    • 析构函数
    • vector拷贝构造函数
    • 拷贝赋值运算符

基本接口函数介绍

函数名功能
vector()(重点)无参构造,构造一个空容器,没有元素。
vector(size_type n, const value_type& val = value_type())构造并初始化n个val
vector (const vector& x); (重点)拷贝构造
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造

测试:

void func() 
{
	int arr[] = {8,4,3,6,1};
	vector<int> vc;//构造一个空容器,没有元素
	vector<int> vc1(10,5);//构造并初始化n个val
	vector<int> vc2(vc1);//拷贝构造
	//使用迭代器进行初始化构造
	vector<int> vc3(arr ,arr + sizeof(arr) / sizeof(arr[0]));

	test(vc);
	test(vc1);
	test(vc2);
	test(vc3);
	//test(vc);
}

测试效果
在这里插入图片描述

调用不带参数的构造函数,成员属性初始值值会给0
在这里插入图片描述

迭代器

迭代器分类

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

在这里插入图片描述

//使用正向迭代器
void test1(vector<int> &v) 
{
	vector<int>::iterator it = v.begin();
	while (it != v.end()) 
	{
		cout << *it++ << " ";
	}	
	cout << endl;
}
//使用反向迭代器
void test2(vector<int>& v)
{
	vector<int>::reverse_iterator it = v.rbegin();
	while (it != v.rend())
	{
		cout << *it++ << " ";
	}
	cout << endl;
}

空间容量

容量空间接口说明
size获取数据个数
capacity获取容量大小
empty判断是否为空

resize(重点)

1、调整容器大小,使其包含n个元素。
2、如果n小于当前容器的大小,则将内容减少到其前n个元素,并删除(并销毁)超过的元素。
3、如果n大于当前容器的大小,则在容器的末尾插入足够多的元素来扩展容器的内容,使容器的大小达到n。如果指定了val,则将新元素初始化为val的副本,否则将对它们进行值初始化。

reserve (重点)

请求向量容量至少足以容纳n个元素。
如果n大于当前的容量,该函数将导致容器重新分配其存储空间,将其容量增加到n(或更大)。
在所有其他情况下,函数调用不会导致重新分配,vector容量也不会受到影响。 也不能改变它的元素。

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

2、reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。

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

用一份测试代码去观察:

void func1() 
{
	vector<int> v;
	int size = v.capacity();
	cout << size << endl;
	for (int i = 0; i < 100; i++) 
	{
		v.push_back(i);
		if (v.capacity() != size)
		{
			size = v.capacity();
			cout << "扩容后:" << size << endl;
		}
	}
}

vs下capacity是按1.5倍增长的
在这里插入图片描述

在linux下以2倍增长
在这里插入图片描述

增删查改

vector增删查改接口说明
push_back(重点)尾插
pop_back (重点)尾删
find查找。(注意这个是算法模块实现,不是vector的成员接口)
insert在position之前插入val
erase删除position位置的数据
swap交换两个vector的数据空间
operator[] (重点)返回此下标位置的值
void func1() 
{
	//插入5个值
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	
	for (auto val : v) 
	{
		cout << val << " ";
	}
	cout << endl;
	//在一段迭代去区间查找值为2的元素,找到返回此位置的迭代器,
	//没有找到就返回end()处的迭代器
	vector<int>::iterator pos = find(v.begin(),v.end(),2);
	if (pos != v.end()) 
	{
		//将值插入到pos位置之前
		v.insert(pos,30);
	}
	
	for (auto val : v)
	{
		cout << val << " ";
	}
	cout << endl;
	//在一段迭代去区间查找值为2的元素,找到返回此位置的迭代器,
	//没有找到就返回end()处的迭代器
	pos = find(v.begin(), v.end(), 30);
	if (pos != v.end()) 
	{
		//删除pos位置的值
		v.erase(pos);
	}
	
	for (auto val : v)
	{
		cout << val << " ";
	}
	
}

迭代器失效问题探讨

野指针问题

//测试二
void func2() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	
	vector<int>::iterator it = v.begin();
	while (it != v.end()) 
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//查找 pos位置,找到后返回此位置的迭代器
	vector<int>::iterator pos = find(v.begin(), v.end(), 3);
	//在pos位置前插入10,空间会扩容并释放旧的空间,
	//重新分配一块新的空间,由于旧空间已经被释放完了,所以不可访问
	v.insert(pos,10);
	it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}
	
	cout << endl;
	//pos是野指针,使用pos去访问旧空间是一个隐患
	v.erase(pos);
	it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		it++;
	}

}

这个程序的运行是会存在错误的,错误的原因是pos还在访问那块旧的空间,pos迭代器却早已经失效了,旧空间释放后内存使用权限已经归还给操作系统了,而pos还指向那块旧的空间,v.erase(pos);这句代码就是去访问一块非法内存,所以就会程序崩溃
在这里插入图片描述
图解:
在这里插入图片描述

下面这块程序的功能是将容器中所有的偶数都删除掉,只保留奇数数据
这段程序在不同的平台上是不一样的

//测试一
void func1() 
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	//v.push_back(7);
	
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0) 
		{
			v.erase(it);
		}
		it++;
	}
	for (auto e : v) 
	{
		cout << e << " ";
	}
}

在vs上,程序就直接崩溃了
在这里插入图片描述
在linux上出现段错误
在这里插入图片描述
图解析:
在这里插入图片描述
从图中我们可以看出it每次删元素的时候都会跳过一个元素,end的位置每次也都在发生变化,直到删除6的时候 it 和 end已经开始错开了,分道扬镳,此后再也不会相遇,it此时就是野指针了,访问了一块非法的内存,但是在linux上平台上我们只需要将尾插一个7(奇数)就能解决这个问题,vs下无论是最后一个值是奇数还是偶数都会报错,it已经失效,编译器会直接对it++检查
在这里插入图片描述
是因为当插入7的时候刚好检查到最后一个位置不会再erase掉,it再往后迭代的时候就遇到了end,循环也就终止了,但是这段代码还是错误的代码,因为不同的平台跑出的结果是不一样的

解决方案

vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
		{
			//erase删除it位置的元素后,会返回it位置的下一个位置,
			//再回去检查这个元素是否是偶数
			it = v.erase(it);
		}
		else 
		{
			it++;
		}	
	}

总结:
insert和erase都会导致迭代器失效
1、insert(it, x) 或者 erase(it) 以后迭代器的意义变了
2、insert(it, x) 或者 erase(it) 以后it变成了野指针

vector模拟实现

namespace mzt 
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		iterator begin() { return _start; }
		iterator end() { return _finish; }
		size_t capacity() { return _endofstroage - _start; }
		size_t size() { return _finish - _start; }

		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstroage(nullptr)
		{ }
		vector(vector<T>& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstroage(nullptr)
		{
			reserve(v.capacity());
			for (auto &e : v) 
			{
				push_back(e);
			}
		}

		void swap(vector<T>& v)
		{
			::swap(_start,v._start);
			::swap(_finish,v._finish);
			::swap(_endofstroage,v._endofstroage);
		}
		vector<T>& operator=(vector<T> v)
		{
			if (this != &v) 
			{
				swap(v);
			}
			return *this;
		}

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

				_start = tmp; 
				_finish = _start + sz;
				_endofstroage = _start + n;
			}
		}
		//尾插
		void push_back(const T& v)
		{
			if (_finish == _endofstroage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish++ = v;
		}
		void erase() 
		{
			assert(!empty());
			--_finish;
		}

		void resize(size_t n,T a = T()) 
		{
			if (n < capacity()) 
			{
				_finish = _start + n;
			}
			else 
			{
				if (n > capacity()) 
				{
					reserve(n);
				}
				while (_finish < _endofstroage) 
				{
					*_finish++ = a;
				}
			}
		}
		size_t operator[](size_t n) 
		{
			assert(n < size());
			return _start[n];
		}
	
		iterator insert(iterator pos, const T& x)
		{
			if (_finish == _endofstroage) 
			{
				size_t len = pos - _start;
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				//pos失效后重新计算pos的位置
				pos = _start + len;
			}
			iterator end = _finish - 1;
			while (end >= pos) 
			{
				*(end + 1) = *end;
				end--;
			}	
			*pos = x;
			++_finish;
			return pos;
		}
		iterator erase(iterator pos)
		{
			assert(!empty());
			iterator it = pos + 1;
			while (it != _finish) 
			{
				*(it - 1) = *it;
				it++;
			}
			--_finish;
			return pos;
		}

		bool empty() { return _start == _finish; }

		~vector() 
		{
			if (_start) 
			{
				delete[] _start;
			}
			_start = _finish = _endofstroage = __nullptr;
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _endofstroage;
	};


}

私有成员

iterator _start;//返回起始位置
iterator _finish;//返回最后一个元素的下一个位置
iterator _endofstroage;//返回空间的结束标记位置

reserve

//返回最后一个元素的下一个位置
size_t size()const
{
	return _finish - _start;//指针相减计算中间差的元素个数
}

void reserve(size_t n) 
{
	if(n > capapcity())
	{
		size_t sz = size();
		//提前备份好sz大小,防止扩容后_start 指向了新的空间,
		T* tmp = new T[n];
		if (_start) 
		{
			//旧的空间_start的值拷贝到新空间上tmp上
			//memcpy(tmp, _start, sizeof(T) * n);
			//memcpy针对内置类型是可以的,但是如果针对
			//的是自定义类型的话,可能会存在浅拷贝的问题,
			//所以不管T类型是否是自定义类型也好,内置类型也好
			//一律都去调用T类型的operator=
			for(size_t i = 0; i < sz; i++)
			{
				tmp[i] = _start[i];
			}
			//回收旧空间
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;//提前备份sz的大小,防止计算错误
		//错误示范:_finish = _start + size();
		//_start 已经指向了一块新分配的空间地址就变了
		_endofstroage = _start + n;
		//实际空间开多大
	}
}

这里需要注意的是_start = tmp;的时候_start 就已经指向新的空间了,如果使用_start + size()回去调用size函数
那就成了这样了:_start + (_finish - _start),那就会很有可能是一个负数了,当然我们的编译器也不会这么傻
在这里插入图片描述
当断点执行到这一行的时候_finish指向的位置还是0x00000000,那么_finish压根就没变,解决办法,防止_start指向新空间后地址变了,我们先提前备份好size个数据

void push_back(const T& v) 
{
	//满了考虑扩容
	if (_finish == _endofstroage) 
	{
		//尾插数据的时候提前计算增容大小
		size_t newcapacity = capacity() == 0 ? 4 
								: capacity() * 2;
		reserve(newcapacity);
	}
	//往这块空间填充值,_finish再往后挪动
	*_finish = v;
	_finish++;
}

跟顺序表的尾插类似,考虑扩容就行

迭代器

typedef T* iterator;

//返回开始位置
iterator begin() const
{ return _start; }
//返回最后一个元素的下一个位置
iterator end() const
{ return _finish; }

提一句,范围for的底层是支持迭代器的,迭代器原理类似指针

空间容量

下面的接口函数我们直接对照图就可以清晰地理解
在这里插入图片描述

//返回总空间大小
size_t capacity() const
{
	return _endofstroage - _start;
}
//返回有效数据个数
size_t size()const
{
	return _finish - _start;
}

删除

void erase() 
{
	assert(!empty());
	//指针往前挪动,就相当于减去一个数据
	--_finish;
}
//判空,如果_start 和 _finish都在一个起始位置就说明容器为空
bool empty() const{ return _start == _finish; }

resize

void resize(size_t n,T a = T()) 
{
	//如果n小于实际空间的大小那么就做缩容处理
	if (n < capacity()) 
	{
		_finish = _start + n;
	}
	else 
	{
		//如果n > capacity()个空间,就先扩容
		if (n > capacity()) 
		{
			reserve(n);
		}
		//空间够就对多出来的空间初始化
		while (_finish < _endofstroage) 
		{
			*_finish++ = a;
		}
	}
}

resize的3个情况

1、指定空间小于实际空间容量,那么就做缩容处理,将空间调整到n个大小
2、size() < n < capacity() ,那么就对这段区间的空间初始化
3、n > capacity() ,先扩容到n个大小的空间,再对这段空间初始化

insert

insert在指定pos位置插入一个数据,并返沪pos位置的迭代器

//指定pos位置插入
iterator& insert(iterator pos, const T &x)
{
	//如果空间满了,先扩容再挪动数据
	if (_finish == _endofstroage) 
	{
		//计算pos偏移起始地址的长度
		size_t len = pos - _start;
		size_t newcapacity = capacity() == 0 ? 4 :
								 capacity() * 2;
		//扩容至newcapacity个
		reserve(newcapacity);
		//扩容后pos迭代器就已经失效了,重新计算pos迭代器的位置
		pos = _start + len;
	}
	
	//将【pos,_finish】往后挪动n + 1个位置
	iterator end = _finish - 1;
	while (end >= pos) 
	{
		*(end + 1) = *end;
		end--;
	}
	//存入数据
	*pos = x;
	++_finish;
	//标准库的inert返回的是pos位置的迭代器,这里我们同样这么处理
	return pos ;
}

需要注意的是这里的insert会引发迭代器失效的问题,即使在函数内部将pos迭代器的位置处理了,但是值传递并不会影响到实参,所以外面的pos 还是失效了,需要再次重新给pos赋值,

析构函数

~vector() 
{
	if (_start) 
	{
		delete[] _start;
	}
	_start = _finish = _endofstroage = __nullptr;
}

释放空间,将_start、_finish 、_endofstroage 这三个指针置空

vector拷贝构造函数

不复用接口写法

vector(vector<T>& v)
	:_start(nullptr)
	,_finish(nullptr)
	,_endofstroage(nullptr)
{
	_start = new T[v.capacity()];
	memcpy(_start,v._start,sizeof(T) * v.size());
	_finish = _start + v.size();
	_endofstroage = _start + v.capacity();		
}

复用接口的写法

vector(vector<T>& v)
	:_start(nullptr)
	,_finish(nullptr)
	,_endofstroage(nullptr)
{
	//提前开好一块够大的空间
	
	reserve(v.capacity());
	
	//拷贝一遍
	for (auto e : v) 
	{
		push_back(e);
	}
}

区别不大,都是为了先给_start开一块新的空间,再将原数据拷贝过来,推荐第二种写法,代码更简洁

拷贝赋值运算符

//复用写法
vector<T>& operator=(const vector<T> v)	//复用拷贝构造函数
{
	if (this != &v) 
	{
		//交换this对象和v对象的成员
		swap(v);
	}
	return *this;
}


//不复用写法
vector<T>& operator=(const vector<T> &v)
{
	if (this != &v) 
	{
		//释放旧的空间
		delete[]_start;
		//重新分配一块新的空间
		_start = new T[v.capacity()];
		//将旧空间的数据挪到新空间
		memcpy(_start,v._start,sizeof(T)* v.size());
		_finish = _start + v.size();
		_endofstroage = _start + v.capacity();
	}
	return *this;
}

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++入门 vector的使用 + 进阶【模拟实现】 的相关文章

  • 如果我不定义向量大小,程序会崩溃

    最近在学习C 偶然发现这个问题 std vector
  • 通过引用传递向量

    如果我在一个类中有一个对象向量 我想在另一个类中更改它 我会尝试通过引用传递所有信息 我究竟需要通过引用传递什么 向量 物体 两个都 本质上我要问的是 它们之间有什么区别 vector blah A reference to a vecto
  • vector 超出范围后不清除内存

    我遇到了以下问题 我不确定我是否错了或者它是一个非常奇怪的错误 我填充了一个巨大的字符串数组 并希望在某个点将其清除 这是一个最小的例子 include
  • C++ std::vector 搜索值

    我正在尝试优化std vector 搜索 基于索引的迭代向量并返回与 搜索 条件匹配的元素 struct myObj int id char value std vector
  • 将字符串解析为结构变量

    我试图将字符串解析为其中包含不同变量的结构向量 这是我到目前为止所拥有的 但似乎不起作用 struct client string PhoneNumber string FirstName string LastName string Ag
  • 如果元素 id 与搜索参数匹配,如何从 std::vector 中删除元素

    我正在尝试编写一种算法 如果项目 ID 与参数匹配 该算法将搜索项目向量并从项目向量中删除项目 请参阅下面的示例代码 struct item item int newID id newID bool operator const item
  • 稀疏向量模板类:如何清理它?

    我不确定这是否是一个好问题 如果不是 请关闭它 我开始写 使用boost coordinate vector作为起点 sparse vector有效实现类似向量接口的模板类 但很稀疏 它实现了所有常见的向量运算和一个迭代集合元素的快速稀疏迭
  • 如何随机打乱向量中的元素

    我正在尝试完成一项需要发生以下情况的作业 请求所需的元素数量 n 用元素 0 1 2 n 1 填充向量并将其显示到控制台 随机打乱元素并将新的排列显示到控制台 我可以输入向量 但我不知道如何对向量进行洗牌 注意 我不能使用 random s
  • 在两个点之间创建一条曲线,每个点都具有标准化向量

    因此 我需要一种写入方法来在两点之间创建一条曲线 每个点都有一个指向任意方向的归一化向量 我一直在尝试设计这样一种方法 但一直无法理解数学 在这里 由于一张图片胜过一千个文字 这就是我所需要的 在图中 矢量垂直于红线 我相信向量需要进行相同
  • 查找按降序排序的向量中严格小于某个键的第一个元素

    据我了解 可以使用 find if STL 算法函数来完成此任务 如下所示 long long int k k key scanf lld k auto it find if begin v end v k auto e return e
  • 如何将句子或文档转换为向量?

    我们有将单词转换为向量的模型 例如 word2vec 模型 是否存在类似的模型 可以使用为单个单词学习的向量将句子 文档转换为向量 1 跳克法 以及使用它的工具 谷歌 word2vec https code google com p wor
  • 如何计算某物是否位于某人的视野中

    我有一个对象 它在 2D 空间中具有位置和速度 两者都由向量表示 对象的视野每侧均为 135 度 它看起来与移动的方向相同 速度矢量 我有一些对象 其在 2D 空间中的位置由向量表示 在图中 蓝色背景上的对象是可见的 红色背景上的对象对主体
  • STL 向量、迭代器和插入 (C++)

    我有一个将向量的迭代器传递到的方法 在这个方法中 我想向向量中添加一些元素 但我不确定当只有迭代器时这是否可行 void GUIComponentText AddAttributes vector
  • 使用带有可变参数字符串的函数

    我正在研究带有可变参数的函数 并决定创建一个函数来创建带有参数的向量 我的功能是创建一个int矢量工作 vector
  • 在 clang 中向量化函数

    我正在尝试根据此用 clang 对以下函数进行矢量化铿锵参考 http llvm org docs Vectorizers html 它采用字节数组向量并根据以下条件应用掩码this RFC https www rfc editor org
  • Vector.erase(Iterator) 导致内存访问错误

    我正在尝试对以下内容进行 Z 索引重新排序videoObjects存储在一个vector 该计划旨在确定videoObject这将被放在第一个位置vector 将其删除 然后插入到第一个位置 不幸的是erase 函数总是会导致内存访问错误
  • cudaMalloc使用向量>进行管理 > C++ - NVIDIA CUDA

    我正在通过 NVIDIA GeForce GT 650M GPU 为我创建的模拟实现多线程 为了确保一切正常工作 我创建了一些辅助代码来测试一切是否正常 在某一时刻 我需要更新变量向量 它们都可以单独更新 这是它的要点 device int
  • 将 2D 矢量转换为 2D 数组

    自从我上次访问数组以来已经有一段时间了 我最近一直在使用向量 我需要将 2D 向量转换回 2D 数组 因为我正在使用的库接受类型的参数double array该数组的访问器在哪里foo i j 例如 这是我的代码 double setupH
  • 如何过滤自定义结构体的向量?

    我正在尝试过滤Vec
  • 如何在 C++ 中将向量迭代器转换为 int

    我正在寻找 C 向量中的一个元素 当我找到它时 我想以数字形式 整数 浮点数 获取找到的元素的索引 我天真的尝试是这样的 int x int index vector

随机推荐