vector(3)

vector 迭代器失效问题。(重点)
迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对 指针进行了封装,比如:vector的迭代器就是原生态指针T 。因此迭代器失效,实际就是迭代器 底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间*,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。
对于vector可能会导致其迭代器失效的操作有:
1.会引起其底层空间改变的操作,都有可能是迭代器失效
比如:resize、reserve、insert、 assign、push_back等.
这里拿insert来举例子:
vector.h
// 迭代器失效,本质因为一些原因,迭代器不可用
iterator insert(iterator pos, const T& x)
{
	assert(pos >= _start && pos <= _finish);
	if (_finish == _endofstorage)
	{
		size_t len = pos - _start;
		reserve(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len;
	}
	iterator i = _finish - 1;
	while (i >= pos)
	{
		*(i + 1) = *i;
		--i;
	}
	*pos = x;
	++_finish;
	return pos;
}
void test_vector2()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	//v1.push_back(5);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	v1.insert(v1.begin(), 30);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
 
这上面的代码是正确的版本,也就是可以运行的版本
但是我们平常在写代码也会遇到好多好多问题,以下是一些常见问题:

这里的错误在于实参传递给形参,但是形参的改变不会影响实参,这里传递给前面的begin,函数的返回值,传递的是拷贝而不是值本身,因为拷贝相当于是临时对象,临时对象具有常性,就相当于被const修饰,权限给缩小了
还有哪些情况会导致产生临时对象? 如类型转换
2.指定位置元素的删除操作–erase
erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理 论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end 的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素 时,vs就认为该位置迭代器失效了。
以下代码的功能是删除vector中所有的偶数,请问那个代码是正确的,为什么?
代码1:
#include <iostream>
using namespace std;
#include <vector>
int main()
{
vector<int> v{ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
v.erase(it);
++it;
}
return 0;
}
 
代码2:
#include <iostream>
using namespace std;
#include <vector>
int main()
{
vector<int> v{ 1, 2, 3, 4 };
auto it = v.begin();
while (it != v.end())
{
if (*it % 2 == 0)
it = v.erase(it);
else
++it;
}
return 0;
}
 



注意:Linux下,g++编译器对迭代器失效的检测并不是非常严格,处理也没有vs下极端。
// 1. 扩容之后,迭代器已经失效了,程序虽然可以运行,但是运行结果已经不对了
int main()
{
vector<int> v{1,2,3,4,5};
for(size_t i = 0; i < v.size(); ++i)
cout << v[i] << " ";
cout << endl;
auto it = v.begin();
cout << "扩容之前,vector的容量为: " << v.capacity() << endl;
// 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效
v.reserve(100);
cout << "扩容之后,vector的容量为: " << v.capacity() << endl;
// 经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux
下不会
// 虽然可能运行,但是输出的结果是不对的
while(it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
程序输出:
1 2 3 4 5
扩容之前,vector的容量为: 5
扩容之后,vector的容量为: 100
0 2 3 4 5 409 1 2 3 4 5
 
// 2. erase删除任意位置代码后,linux下迭代器并没有失效
// 因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的
#include <vector>
#include <algorithm>
int main()
{
vector<int> v{1,2,3,4,5};
vector<int>::iterator it = find(v.begin(), v.end(), 3);
v.erase(it);
cout << *it << endl;
while(it != v.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
程序可以正常运行,并打印:
4
4 5
 
// 3: erase删除的迭代器如果是最后一个元素,删除之后it已经超过end
// 此时迭代器是无效的,++it导致程序崩溃
int main()
{
vector<int> v{1,2,3,4,5};
// vector<int> v{1,2,3,4,5,6};
auto it = v.begin();
while(it != v.end())
{
if(*it % 2 == 0)
v.erase(it);
++it;
}
for(auto e : v)
cout << e << " ";
cout << endl;
return 0;
}
 
迭代器失效解决办法:在使用前,对迭代器重新赋值即可。
vector 空间增长问题
resize
改变vector的size
代码示例如下:
void test_vector6()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	v1.resize(10);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	v1.resize(15, 1);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	v1.resize(2);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}
 
功能和在string里面差不多
拷贝构造
void test_vector7()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	vector<int> v2(v1);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}
 
这里我们没有自己写拷贝构造的话,编译器会有自己生成的拷贝构造进行调用,也就是浅拷贝,值进行拷贝过去,

这样子析构的时候,会析构两次,程序出现问题
所以需要自己写深拷贝:
vector(const vector<T>&v)
{
	reserve(v.capacity());//这里是以防v1里面的数据过多,需要用reserve预留开辟一块空间
    for(auto &e:v)//这里用了范围for,用到了引用,e是v1的别名,将v1里面的元素进行遍历
    {
        push_back(e);//尾插到v2里面
    }
}
 
赋值拷贝
代码如下:(现代写法)
void swap(vector<T>& tmp)
{
	std::swap(_start, tmp._start);
	std::swap(_finish, tmp._finish);
	std::swap(_endofstorage, tmp._endofstorage);
}
// v1 = v3
vector<T>& operator=(vector<T> v)
{
	swap(v);
	return *this;
}
vector<int> v3 = { 10,20,30,40 };
v1 = v3;
for (auto e : v1)
{
	cout << e << " ";
}
cout << endl;
 
区间构造迭代器
// 类模板的成员函数,也可以是一个函数模板
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}
// vector<double> v4(10, 1.1);
// vector<int> v4(10, 1);
vector(size_t n, const T& val = T())
{
	reserve(n);
	for (size_t i = 0; i < n; i++)
	{
		push_back(val);
	}
}
//这里要将n前面的size_t改为int ,因为编译器会选择更其最匹配的进行调用
vector(int n, const T& val = T())
{
	reserve(n);
    //这里也需要注意
	for (int i = 0; i < n; i++)
	{
		push_back(val);
	}
}
vector(initializer_list<T> il)
{
	reserve(il.size());
	for (auto& e : il)
	{
		push_back(e);
	}
}
 
只有int会有这样的问题,出现不匹配的问题,别的类型不会
使用memcpy拷贝问题
- memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存 空间中
 - 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型 元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅 拷贝。
 
例子如下:
void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t oldSize = size();
		T* tmp = new T[n];
		if (_start)
		{
			// memcpy(tmp, _start, sizeof(T) * oldSize);
			for (size_t i = 0; i < oldSize; i++)
			{
				tmp[i] = _start[i];
			}
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + oldSize;
		_endofstorage = _start + n;
	}
}
void test_vector9()
{
	vector<string> v1;
	v1.push_back("1111111111111111");
	v1.push_back("1111111111111111");
	v1.push_back("1111111111111111");
	v1.push_back("1111111111111111");
	v1.push_back("1111111111111111");
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}
 




















