【C++进阶三】vector深度剖析(迭代器失效和深浅拷贝)

【C++进阶三】vector深度剖析

vector其实就是顺序容器 ,string类只用考虑存储字符,但vector中可以存储任一类型,所以vector的实现需要用模板

1.vector的接口函数

使用cplusplus(点击链接)查看vector

库中的vector的模板参数有两个

后一个是内存池,用来提升空间利用效率

内存池在现阶段暂时无需做深入了解

1.1构造和拷贝构造

三种常见构造

cpp 复制代码
vector<int> v1;
vector<int> v2(10,1);//构造顺序表并初始化10个1
vector<int> v3(v2);

用迭代器区间初始化

cpp 复制代码
string str("PTN");
vector<string> v(str.begin(),str.end());

1.2迭代器的使用

和string一样,vector有正向和反向两种迭代器,且使用方法和string相同

begin:获取第一个数据位置的iterator/const_iterator
end:获取最后一个数据下一个位置的iterator/const_iterator
rbegin:获取最后一个数据位置的reverse_iterator
rend:获取第一个数据前一个位置的reverse_iterato

1.3空间容量相关函数

vector的空间相关的函数和string的机会一模一样,如果不懂可以查看这篇文章string容量操作

1.4find,swap和sort

这三个函数都在头文件:algorithm中

参数是一段迭代器区间,以及在此区间你需要查找的值,找到后返回这个值对应的迭代器位置,若找不到则返回迭代器last

cpp 复制代码
vector<int> v{0,1,2,3,4,5,6,7,8,9};
auto pos = find(v.begin(),v.end(),5);
cout << *pos;

使用auto是为了简写迭代器也可以用vector<int>::iterator替代

sort内部实现是快排,我们只需要传一个迭代器区间就可以将整个区间排好序

cpp 复制代码
vector<int> v{5,6,3,7,8,4,1,9,2,0};
sort(v.begin().v.end());

1.5增删查改

1.5.1insert和erase

和string不同,vector的insert的参数pos不是整型,而是迭代器

默认是在pos位置前插入一个数据

insert和find常常配合在一起使用

cpp 复制代码
vector<int> v{1,2,3,4,5,6,7,8,9};
auto pos = find(v.begin(),v.end(),5);
v.insert(pos,999);

和string的erase不同,vector的erase一次只删除一个数据,然而string如果使用缺省值就是将全部数据删完

vector的erase甚至可以删除一段区间

cpp 复制代码
vector<int> v{1,2,3,4,5,6,7,8,9,10};
auto pos = find(v.begin(),v.end(),10);
v.erase(pos);
//删除一个区间
v.erase(v.begin()+1,v.end()-1);

1.5.2 随机访问operator[ ]

vector中最常用的是[ ],它支持随机访问

cpp 复制代码
vector<int> v{0,1,2,3,4,5,6,7,8,9};
for(int i = 0;i < v.size();i++)
{
 	cout << v[i] << " ";
}

2.什么是迭代器失效?

迭代器失效的本质原因是:扩容后start和finish的地址发生变化,指向原先位置的迭代器失效

2.1insert

vector的每一次扩容都不是在原地扩容,而是新开辟一块儿空间后将原先的数据拷贝到新空间

cpp 复制代码
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
auto pos = find(v.begin(),v.end(),2);
v.insert(pos,10);
v.insert(pos,20);

这段代码在2前面插入一个10和20,但是这段代码会出错

原因如下图:

库中的vector提供了返回值来解决此问题:

insert会返回一个迭代器,此迭代器是新插入的元素的迭代器

如果想要继续通过迭代器操作vector中的元素,只需给pos重新赋值

cpp 复制代码
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
auto pos = find(v.begin(),v.end(),2);
pos = v.insert(pos,10);
v.insert(pos,20);

2.1erase

例:删除顺序表中所有的偶数

cpp 复制代码
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(4);
v.push_back(6);

auto it = v.begin();
while(it != v.end())
{
	if(*it % 2 == 0)
	{
		it = v.erase(it);
	}
	++it;
}

但是你会发现它并没有删除完

原因如图:

erase删除后,后面的数据会覆盖过来,此时it++无法指向原本的下一位

解决方法:删除后不用再++迭代器,只用在没删除的时候再++

cpp 复制代码
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(4);
v.push_back(6);

auto it = v.begin();
while(it != v.end())
{
	if(*it % 2 == 0)
	{
		it = v.erase(it);
	}
	else
	{
		++it;
	}
}

3.vector的深浅拷贝问题

内置类型调用默认拷贝构造函数会进行浅拷贝,所以需要我们自己来实现深拷贝

cpp 复制代码
Vector(const Vector<T>& v)
{
	assert(v._start && v._finish && v._endofsto);
	_start = new T[v.capacity()];//给size或capacity都可以
	memcpy(_start, v._start, sizeof(T) * v.size());
}

自定义类型依旧会报错,因为自己实现的拷贝构造中memcpy也是一种浅拷贝(按字节拷贝)

深拷贝是重新开辟一块与原空间大小相同的新空间,并将原空间的数据拷贝给新空间

但是若为string 类型,本身的_str指向字符串,而新空间只是将_str拷贝过去了,两份_str依旧指向同一字符串

v2先进行析构,会调用delete[ ] ,会对数组上每个成员依次调用析构函数,此时指向的字符串就全部被析构了,v1后进行析构,但没有可以析构的字符串,所以会报错

这就是深拷贝中的浅拷贝
由于这种深浅拷贝问题是因为memcpy导致的,所以这里不能使用memcpy,只需要使用一个for循环就能解决:

cpp 复制代码
Vector(const Vector<T>& v)
{
	assert(v._start && v._finish && v._end_of_storage);
	_start = new T[v.capacity()];//给size或capacity都可以
	//memcpy(_start, v._start, sizeof(T) * v.size()); //使用memcpy时,数组是二维数组会发生问题
	for (size_t i = 0; i < size(); i++)
	{
		_start[i] = v._start[i];
	}
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

4.reserve深度剖析

reserve只改变capacity大小,而不会改变size的大小

所以如下代码并不正确

cpp 复制代码
vector<int> v;
v.reserve(10);//开辟10份空间
for(int i = 0;i < 10;i++)
{
	v[i] = i;
}

size此时是0,也就是有效长度为0,虽然你开辟了10份空间,但是运算符操作[ ]的内部实现会检查下标

cpp 复制代码
T& operator[](size_t pos)//[]的模拟实现
{
	assert(pos < size());
	return _start[pos];
}

所以使用reserve后不能直接使用[ ]

模拟实现的reserve也存在使用memcpy造成浅拷贝的问题:

将旧空间上的_str等拷贝到新空间上,释放旧空间就导致_str所指向的字符串析构

当新空间析构时,_str所指向的字符串就会造成二次析构,从而报错

更改模拟的的reserve:

相关推荐
我命由我123451 小时前
Spring Boot 自定义日志打印(日志级别、logback-spring.xml 文件、自定义日志打印解读)
java·开发语言·jvm·spring boot·spring·java-ee·logback
徐小黑ACG2 小时前
GO语言 使用protobuf
开发语言·后端·golang·protobuf
0白露3 小时前
Apifox Helper 与 Swagger3 区别
开发语言
Tanecious.4 小时前
机器视觉--python基础语法
开发语言·python
叠叠乐4 小时前
rust Send Sync 以及对象安全和对象不安全
开发语言·安全·rust
niandb5 小时前
The Rust Programming Language 学习 (九)
windows·rust
Tttian6226 小时前
Python办公自动化(3)对Excel的操作
开发语言·python·excel
Merokes7 小时前
关于Gstreamer+MPP硬件加速推流问题:视频输入video0被占用
c++·音视频·rk3588
独好紫罗兰7 小时前
洛谷题单2-P5713 【深基3.例5】洛谷团队系统-python-流程图重构
开发语言·python·算法
闪电麦坤958 小时前
C#:base 关键字
开发语言·c#