C++:STL——vector

一、构造函数

#include<iostream>
#include<vector>
using namespace std;

int main()
{
	//无参构造
	vector<int> v1;
	//n个相同对象构造(10个5)
	vector<int> v2(10, 5);
	//迭代器区间构造(利用v2的迭代器区间)
	vector<int> v3(v2.begin(), v2.end());
	//拷贝构造
	vector<int> v4(v2);
	return 0;
}

         迭代器可以互用:

         利用数组构造:

二、排序算法(这个不是vector的内容,所有容器都可以使用) 


sort的排序区间是左内闭右开的

排序要传迭代空间,第三个参数是greater或less类型的模板
没有第三个参数,默认是升序

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>//排序算法的头文件
using namespace std;
int main()
{
	int a[10] = { 2,7,3,4,1,6,8,2,9,5 };
	vector<int> v1(a, a + 10);
	for (auto& e : v1)
	{
		cout << e << " ";
	}
	//升序
	sort(v1.begin(), v1.end());
	//gless<int> le;<
	//sort(v1.begin(), v1.end(),le);
	for (auto& e : v1)
	{
		cout << e << " ";
	}
	//降序
	greater<int> gt;//>
	sort(v1.begin(), v1.end(), gt);
    //上面两行可以用匿名对象浓缩成一行
    //sort(v1.begin(), v1.end(), greater<int>);
	for (auto& e : v1)
	{
		cout << e << " ";
	}
	return 0;
}

三、迭代器

 正向,反向,const,非const

升序排序用反向迭代器就是降序 

四、容量部分

大致与string一样,下面有个易错点

这个写法会报错:[ ]会对vector进行越界断言,而这个断言是按照size的大小进行的,当i超过size时,便会报越界的错。使用reserve后,虽然capacity是10,但是size却是初始化时的0,使用进行遍历填值时,越界就报错了。

如果想这样访问的话,就应该使用resize,直接把size设置好(开辟了但没有数据的位置默认是0)

如果你硬要用reserve,就用push_back进行插入

要记住:reserve仅仅只是开空间,resize是开空间并初始化

五、访问部分

 注意[ ]与at都会进行越界检查(以0,size为范围),[ ]是断言,at是抛异常

看一看data

与c_str很像,返回vector数组的指针 

六、修改部分

提供尾插尾删,头插与头删用insert与eares实现(insert是在pos之前插入,eares删除pos及之后位置) 越界删除会报错

接下来是一个易错点:如何删除一个vector对象中的多个相同元素?
        vector中没有find这个函数,但是在算法头文件中有

        这是一个模板,可以给各个容器使用。find的区间是左闭右开的,如果没有找到,返回last(最后一个数据的下一个数据的地址所在处),找到了,就返回找到的地址。返回的是一个指针,需要接收

#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
int main()
{
	int a[10] = { 1,3,2,3,5,8,3,4,3,0 };
	vector<int> v1(a,a+10);
	vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);
	while (pos != v1.end())
	{
		v1.erase(pos);
	}
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

        这样写似乎是对的,但实际上会报错,原因是迭代器失效(这个后面细讲) 
每次从头找的话就没问题,但是效率很低

clear与swap与之前一样,assign是对之前的值清了之后重新赋值(用的很少)

        下面是vector的扩容逻辑:

 

        vs下差不多是1.5倍扩容,linux下是2倍扩容 

七、vector的vector(用于实现二维数组)

八、vector的模拟实现

#include<iostream>
#include<assert.h>
using namespace std;
namespace djl
{
	//声明模板类型T
	template<class T>

	class vector
	{
	public:
		//重命名
		typedef T* iterator;
		typedef const T* const_iterator;

		//无参构造函数
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_endoftorage(nullptr)
		{

		}

		//n个相同对象的构造函数
		vector(size_t n, const T& val = T())
		{
			resize(n, val);
		}

		//拷贝构造
		vector(const vector<T>& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_endoftorage(nullptr)
		{
			_start = new T[v.capacity()];

			//这个写法有局限性,只能进行浅拷贝
			//memcpy(_start, v._start, sizeof(T) * v.size());
			
			//利用赋值运算符重载进行数据深拷贝
			for (int i = 0; i < v.size();i++)
			{
				_start[i] = v._start[i];
			}
			
			_finish = _start + v.size();
			_endoftorage = _start + v.capacity();
		}
		//另一种写法
		/*vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endoftorage(nullptr)
		{
			reserve(v.capacity());
			for (auto e : v)
			{
				push_back(e);
			}
		}
		*/

		//析构函数
		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _endoftorage = nullptr;
			}
		}

		//capacity
		size_t capacity()
		{
			return _endoftorage - _start;
		}
		size_t capacity()const
		{
			return _endoftorage - _start;
		}

		//size
		size_t size()
		{
			return _finish - _start;
		}
		size_t size()const
		{
			return _finish - _start;
		}

		//扩容
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * size());

					//利用赋值运算符重载进行数据深拷贝
					for (int i = 0; i < size(); i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				//下面这个顺序写法是错误的,size()是通过_finish-_start计算的,
				// 但这里改变了原来的_start,导致size()计算出错,
				// 从而使finish计算错误
				/*_start = tmp;
				_finish = tmp + size();*/
				//下面这个顺序才是正确的(先算要用到size()的_finish,再计算_start)
				_finish = tmp + size();
				_start = tmp;
				_endoftorage = _start + n;
			}
		}

		//尾插
		void push_back(const T& x)
		{
			if (_finish == _endoftorage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
			}
			*_finish = x;
			_finish++;
		}

		//迭代器
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin() const
		{
			return _start;
		}
		const_iterator end() const
		{
			return _finish;
		}

		//[ ]运算符重载
		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		const T& operator[](size_t pos)const
		{
			assert(pos < size());
			return _start[pos];
		}

		//插入
		iterator insert(iterator pos, const T& x)
		{
			assert(pos >= _start && pos <= _finish);

			if (_finish == _endoftorage)
			{
				size_t len = pos - _start;
				size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
				reserve(newcapacity);
				//更新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(pos >= _start && pos < _finish);
			iterator it = pos;
			while (it != _finish)
			{
				*(it) = *(it + 1);
				it++;
			}
			_finish--;
			return pos;
		}

		//尾删
		void pop_back()
		{
			erase(--end());
		}

		//resize
		//开空间并初始化
		//T()代表的是T这个类型的默认构造函数
		//在C++中,内置类型也有默认构造函数
		//这里的参数,因为不知道T的类型,采用匿名对象的参数
		void resize(size_t n, const T& val = T())
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);
				while (_finish != _start + n)
				{
					*_finish = val;
					_finish++;
				}
			}
		}

		//这个函数配合赋值运算符重载函数使用
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endoftorage, v._endoftorage);
		}
		//赋值运算符重载
		//采用传值形参,避免浅拷贝
		vector<T>& operator=(vector<T> v)
		{
			swap(v);
			return *this;
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _endoftorage;
	};
}

九、insert迭代器失效问题

        先看一下,下面这个insert函数

        看上去似乎没有什么错误,但会发生偶然性数据插入错误,一但发生扩容,就会出问题,这是因为迭代器失效

        扩容的逻辑是删除旧空间,开辟新空间并拷贝,这会导致原来的pos不对(start与finish都指向了新的空间,但pos指向的是旧的空间,是个野指针)

        解决方法,保存原来pos与star的距离,当扩容后,更新pos

        扩容引发野指针的问题就是迭代器失效的一种

        内部的迭代器失效解决了,但外部的没有解决

        在外部对迭代器进行修改时,有时也会发生错误,因为insert是传值传参,函数内部pos的改变不会影响外部

        这里的逻辑是插入300时发生扩容,内部pos会被新pos取代,但是因为是传值传参,外部的pos不发生改变(指向的是内部函数扩容前的空间),然后在外部对p进行+=10的操作实际上是对旧空间中的pos的位置进行操作的,这样的操作肯定是错误的

        解决方法:因为各个平台的insert机制是不一样的,所以我们一般遵循以下规则

        库里面的insert带了一个迭代器的返回值,来避免这个问题

        自己在自己写的insert中返回pos并且改一下返回值类型就好

十、erase迭代器失效问题

        看这个删除函数

        这个写法是没有问题的,但是会发生迭代器失效

        迭代器失效的原因是扩容,erase删除没有问题,但是再次使用这个迭代器就发生问题了

        

        所以也说eras迭代器失效

        vs和g++对这个问题有不同的解决方法

vs下:
erase以后,迭代器失效了,不能访问,vs进行强制检查,访问会直接报错 

g++下:
一种躺平的状态,一般不报错,但是也会发生迭代器失效

        为了保持在不同的平台下代码的可移植性,库中作出以下处理:返回一个位置,这个位置是刚刚删除的那个位置的下一个位置

十一、 T为自定义类型的问题

        这个是拷贝构造函数

        进行下面这个用例时,一切正常

         发生隐式类型转换(单个参数的函数支持隐式类型转换)

        但是,四个的时候好好的,五个的时候就崩溃了(初步考虑是扩容的问题,因为第五个节点是扩容的节点)

        这本质上是隐藏的深拷贝问题

        当需要扩容的时候,便会调用reserve函数,开辟新的空间,把原空间的内容拷贝到tmp中,在修改_start等指针。再对对象进行拷贝时,由于vector的每一个元素是自定义类型,就会导致拷贝的时候要调用string的拷贝构造函数,而这个函数在这个类中是没有的,只好调用默认拷贝构造函数进行浅拷贝,这就导致新空间和原空间中的对象指向同一块空间 

        最主要的是,下一步会调用delete对原空间进行释放,delete对指定类型的空间(vector中的string类对象)先调用析构函数,再释放空间 (vector的空间),这就使得原空间中每个string对象的地址被释放,而tmp拷贝了一个寂寞(拷贝的是已经被释放的野空间)

        综上:当T是一个需要深拷贝的自定义类型的对象时,扩容就会有问题 

        那么,应该如何解决呢? 

         这个是reserve函数内部,把memcpy这个函数换一下,这里调用的是string的=重载(及T这个类型的赋值运算符重载)

         就会变成上面这样的拷贝

        还有就是拷贝构造函数的memcpy也需要换一下

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值