C++STL【vector】

vector

1. vector介绍

  • vector文档
  • vector其实就是一个顺序表,它表示可变大小数组的序列容器。
  • 像数组一样,可以使用下标+[] 来访问vector的元素,和数组一样高效;甚至,它的大小是可以动态改变的,其大小由容器自动处理。
  • 在底层上,vector使用动态分配空间来储存元素。当新元素插入,原空间不够时,需要重新分配一块连续的空间来增加存储空间,做法是开辟大一点的空间,然后将原内容拷贝过来,再释放原空间,此举的时间成本相对较高。
  • vector会额外分配一些空间,以适应可能的增长,实际的存储空间可能比需要的空间更大。不同的STL库的实现采取不同的策略。
  • vector的成员变量和string不同,string是两个整型存size和capacity,一个char指针指向动态开辟的空间;而vector是三个迭代器(底层类似于指针),分别是开头的迭代器、最后一个元素下一个位置的迭代器、开辟的空间的最末端的迭代器。
  • string是管理字符串的类,那么vector< char>实例化为char是否能替代string呢?
    当然不可以,因为string后都有’\0’,可以更好和C的字符串对接,另外string的接口也更加丰富,可以更好的管理字符串。

2. vector的常用接口

vector的许多接口中有很多别名:
在这里插入图片描述

相比于string,vector的接口数量就显得很少了,下面我们看看vector常用的接口。

  1. 构造函数(constructor)

    (constructor)功能
    explicit vector (const allocator_type& alloc = allocator_type());默认构造函数
    explicit vector (size_type n, const value_type& val = value_type(), const allocator_type& alloc = allocator_type());用n个val值初始化
    template <class InputIterator> vector (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type());用迭代器初始化(可以允许其他类型作为参数初始化)
    vector (const vector& x);拷贝构造函数
    vector<int> v1;
    vector<int> v2(3, 2);
    int nums[] = { 1,2,3 };
    vector<int> v3(arr, arr + 3);
    vector<int> v4(v3);
    
  2. 容量操作

    函数功能
    size_type size(); const返回有效元素个数
    size_type capacity(); const返回实际空间大小
    bool empty(); const判断是否为空
    void resize (size_type n, value_type val = value_type());改变有效元素个数
    void reserve (size_type n);改变空间大小

    补充:

    1. resize:
      当n小于有效元素个数时,会将n之后的所有元素删除,只保留从头到n位置的元素;
      当n大于有效元素个数,却小于实际空间大小时,会在最后一个有效元素后填充值val,直到n位置;
      当n大于实际空间大小时,会开辟空间,然后在最后一个有效元素后填充值val,直到n位置。
    2. reserve:
      当n大于实际空间大小时,开辟空间。其余任何情况不做处理。
  3. 迭代器

    函数功能
    iterator begin();
    const_iterator begin() const;
    返回容器开头的位置的迭代器
    iterator end();
    const_iterator end() const;
    返回容器最后一个有效元素的下一个位置的迭代器
    reverse_iterator rbegin();
    const_reverse_iterator rbegin() const;
    返回容器最后一个有效元素的位置的迭代器
    reverse_iterator rend();
    const_reverse_iterator rend() const;
    返回容器开头的前一个位置的迭代器

    在这里插入图片描述

  4. 访问元素

    函数功能
    reference operator[] (size_type n);
    const_reference operator[] (size_type n) const;
    访问下标为n的元素
    reference at (size_type n);
    const_reference at (size_type n) const;
    访问下标为n的元素
    vector<int> v(3, 2);
    cout << v[0] << endl;
    cout << v.at(1) << endl;
    
  5. 修改操作

    函数功能
    void push_back (const value_type& val);尾插一个值
    void pop_back();尾删一个值
    insert在某个位置插入元素
    erase删除某个位置的元素
    void swap (vector& x);交换两个vector对象
    void clear();清空有效元素

3. 模拟实现vector类(部分接口)

在这里插入图片描述

#include<iostream>
#include<assert.h>

using namespace std;

namespace Myspace
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		vector()
		{ }

		vector(size_t n, const T& value = T())
		{
			// 开空间
			_start = new T[n];
			_finish = _start + n;
			_endofstorage = _finish;

			// 放数据
			for (auto& e : *this)
			{
				e = value;
			}
		}

		vector(int n, const T& value = T())
		{
			// 开空间
			_start = new T[n];
			_finish = _start + n;
			_endofstorage = _finish;

			// 放数据
			for (auto& e : *this)
			{
				e = value;
			}
		}

		vector(long n, const T& value = T())
		{
			// 开空间
			_start = new T[n];
			_finish = _start + n;
			_endofstorage = _finish;

			// 放数据
			for (auto& e : *this)
			{
				e = value;
			}
		}

		template<typename InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			/*int len = last - first;
			_start = new T[n];
			_finish = _start + len;
			_endofstorage = _finish;

			for (auto& e : *this)
			{
				e = *first;
				first++;
			}*/
			_start = new T[last - first];
			for (size_t i = 0; i < last - first; i++)
			{
				_start[i] = first[i];
			}
			_finish = _start + (last - first);
			_endofstorage = _start + (last - first);
		}

		vector(const vector& v)
		{
			_start = new T[v.capacity()];
			for (size_t i = 0; i < v.size(); i++)
			{
				_start[i] = v._start[i];
			}
			_finish = _start + v.size();
			_endofstorage = _start + capacity();
		}

		~vector()
		{
			if (_start)
			{
				delete[] _start;
				_start = _finish = _endofstorage = nullptr;
			}
		}

		vector<T>& operator= (vector<T> v)
		{
			swap(v);
			return *this;
		}

		//----------------------------------  迭代器  ---------------------------------------//
		iterator begin()
		{
			return _start;
		}

		const_iterator begin() const
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator end() const
		{
			return _finish;
		}

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

		size_t capacity() const
		{
			return _endofstorage - _start;
		}

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

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				int len = size();
				iterator tmp = new T[n];
				// 这里拷贝数据不能用memcpy,如果T需要深拷贝,memcpy只是浅拷贝
				if (_start)
				{
					for (size_t i = 0; i < n; i++)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + len;
				_endofstorage = _start + n;
			}
		}

		void resize(size_t n, const T value = T())
		{
			if (n <= size())
			{
				_finish = _start + n;
			}
			else
			{
				reserve(n);

				while (_finish != _start + n)
				{
					*_finish = value;
					_finish++;
				}
			}
		}

		//----------------------------------  修改  ---------------------------------------//

		void push_back(const T value)
		{
			if (_finish == _endofstorage)
			{
				reserve(_start == _endofstorage ? 4 : capacity() * 2);
			}
			*_finish = value;
			++_finish;
		}

		void pop_back()
		{
			assert(_start != _finish);
			--_finish;
		}

		iterator insert(iterator pos, const T& value)
		{
			assert(pos >= _start && pos <= _finish);
			if (_finish == _endofstorage)
			{
				int len = pos - _start; // 防止扩容后迭代器失效
				reserve(_start == _endofstorage ? 4 : capacity() * 2);
				pos = _start + len;
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			_finish++;
			*pos = value;
			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);
			iterator it = pos + 1;
			while (it != _finish)
			{
				*(it - 1) = *it;
				++it;
			}
			--_finish;
			return pos;
		}

		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}

		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}

		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];
		}

	private:
		iterator _start = nullptr;	// 给缺省值,在构造函数的初始化列表中自动初始化
		iterator _finish = nullptr;
		iterator _endofstorage = nullptr;
	};
}

注意:

  1. 构造函数vector(int n, const T& value = T())接口需要重载多个(int/size_t/long),以防止创建对象时(vector<int> v(2,3);)编译器自动匹配到vector(InputIterator first, InputIterator last)这个接口。
    原因:编译器总会选择最匹配的接口。
  2. 在扩容时拷贝数据的时候,不要使用memcpy(上述代码的第151行),原因如下:
    如果模板实例化为string,那么此时就相当于memcpy(string1, string2, size),将两个string类memcpy,一定是浅拷贝,因为string类本身有个char*的指针,需要动态开辟空间,memcpy仅仅是将两个指针指向了同一块空间,并没有开辟新的空间。
    直接赋值即可,赋值会调用string自己的赋值运算符重载,它自己会实现深拷贝。
    不止是string,其他任何动态管理空间的类都是如此。

4. 迭代器失效

  1. 迭代器的作用:
    迭代器就是为了不管各个容器的底层如何实现,都能够使用算法。其底层实际是个指针,或是对指针的封装,比如string和vector的迭代器就是char* 和 T*。

  2. 迭代器失效:
    当迭代器底层所指向的空间被销毁了,还依旧使用该迭代器,那么就会造成野指针的问题,后果是程序崩溃。在VS2022下直接报错崩溃,在Linux下可能不会报错,因此对于程序员来说,避免迭代器失效是必须的。

  3. 可能引起迭代器失效的场景:

    1. 扩容操作
    #include <iostream>
    using namespace std;
    #include <vector>
    int main()
    {
        vector<int> v{1,2,3,4,5,6};
        auto it = v.begin();
        
        v.resize(100, 8);
        v.reserve(100);
        v.insert(v.begin(), 0);
        v.push_back(8);
        v.assign(100, 8);
        
        while(it != v.end())
        {
            cout<< *it << " " ;
            ++it;
        }
        cout<<endl;
        return 0;
    }
    

    以上五个接口可能会导致迭代器it失效,原因:
    使用接口改变底层空间时,可能会扩容,而vector的扩容逻辑是:开辟一块新空间,将原数据拷贝至新空间,然后释放旧空间。扩完容之后底层地址空间就变了,而外部的迭代器it依旧指向原来已经被释放的空间,对迭代器再次操作时,就是对已经释放的空间进行操作,会引起代码奔溃。

    1. 删除指定位置元素 — erase
    #include <iostream>
    using namespace std;
    #include <vector>
    int main()
    {
        int a[] = { 1, 2, 3, 4 };
        vector<int> v(a, a + sizeof(a) / sizeof(int));
        // 使用find查找4所在位置的iterator
        vector<int>::iterator pos = find(v.begin(), v.end(), 3);
        // 删除pos位置的数据,导致pos迭代器失效。
        v.erase(pos);
        cout << *pos << endl; // 此处会导致非法访问
        return 0;
    }
    

    上述代码导致迭代器失效的原因:
    erase删除pos位置的元素,后面的元素会往前挪动,理论上没有产生扩容,底层地址空间就不会改变,迭代器不应该失效。但是如果pos位置是最后一个元素,删除之后,pos位置就成了end(),是最后一个有效元素的下一个位置,此位置不属于有效数据的区间,此迭代器就失效了。在VS中再对pos迭代器进行操作,程序就会奔溃。

  4. 解决办法:
    完成扩容或删除操作之后,给迭代器重新赋值即可。

  5. Linux下,g++编译器对迭代器失效的检测并不是很严格,处理也没有VS下那么极端。

    1. vs下迭代器失效
      在这里插入图片描述

    2. g++下迭代器失效

      在这里插入图片描述

    由此可见,SGI版本的STL(Linux下的g++编译),迭代器失效后代码不一定会奔溃(如果迭代器不在begin和end的范围内也会奔溃),但是它的结果一定不正确。

  6. 不仅vector存在迭代器失效的问题,string也有迭代器失效的问题,因此我们在使用STL时,一定要小心迭代器失效!

  • 54
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值