【C++】vector类

一、vector的介绍及使用:

1.1、vector的介绍:

1.vector是表示可变大小数组的序列容器。
2.就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3.本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
4.vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

5.因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6.与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。

1.2、vector的使用:

vector学习时一定要学会查看文档:vector的文档介绍,vector在实际中非常的重要,在实际中我们熟悉常见的接口就可以,下面列出了哪些接口是要重点掌握的。

此外对于vector内部的成员函数,基本上和string相同,因此本文章在模拟实现之前只介绍大多使用函数的函数名以及功能,具体细节会在模拟实现中解释。
 

1.2.1、vector的构造:

                                            构造函数接口说明
vector()无参的默认构造
vector(size_type n, const value_type& val = value_type())构造并初始化n个val
vector (const vector& x)拷贝构造
vector (InputIterator first, InputIterator last)迭代器区间构造(各种类型的迭代器)

1.2.2、vector迭代器iterator:

iterator类型                                                 接口说明
begin + end获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
rbegin + rend获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator

1.2.3、vector的空间类接口:

            函数接口                                  接口说明
size获取数据个数
capacity获取容量大小
empty判断是否为空
reserve改变vector的capacity
resize改变vector的size

  capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
  reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。(即可以直接将扩容一次性到位)
  resize在开空间的同时还会进行初始化,影响size。

1.2.4、vector的增删查改接口:

函数接口                                                接口说明
push_back尾插
pop_back 尾删
find查找(注意这个是算法模块实现,不是vector的成员接口)
insert在pos之前插入val
erase删除pos位置的数据
swap交换两个vector的数据空间
operator[]像数组一样访问

二、vector的模拟实现:

接下来我们更加彻底的了解一下vector的底层:

2.1、vector的构造:

由上面对vector的介绍可知,vector有四种常用的构造:

                                            构造函数接口说明
vector()无参的默认构造
vector(size_type n, const value_type& val = value_type())构造并初始化n个val
vector (const vector& x)拷贝构造
vector (InputIterator first, InputIterator last)迭代器区间构造(各种类型的迭代器)

2.1.1、无参的默认构造:

//默认构造
		vector()
			:_start(nullptr)  //也可以在声明时用缺省值
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}

2.1.2、构造并初始化n个val对象:

  对于该构造方式,我们需要先使用reserve接口提前开辟好vector所用的空间,再将n个val尾插进去。

  值得注意的是,在给val缺省值时,我们用的是匿名对象的方式,匿名对象的生命周期只在它所在那那行代码,但是经过const修饰后的匿名对象的生命周期与val一致。

vector(size_t n, const T& val = T())
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

2.1.3、迭代器区间构造:

根据一个迭代器区间进行构造,这个迭代器可以是任意的类型。

//迭代器区间构造
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			while (first != last)  //迭代器遍历不要使用<,链表的迭代器不是连续的
			{
				push_back(*first);
				++first;
			}
		

对于以上两种初始化方法,我们进行一下测试,构造对象vector<int> v(10,5),我们看一下会有怎样的结果:

可以看出运行失败,报错为非法简介寻址,因为当构造v(10,5)时,编译器会将10默认当作为int类型,而第一种构造的参数为size_t,调用该函数就会发生隐式类型转换,所以编译器会优先以迭代器初始化的方式来进行构造,但其本身时int类型不是地址,不能被解引用,若是强制解引用,就会出现野指针问题,就会运行报错。

而解决此问题的方法也非常简单,便是令写一份int类型的构造:

//为vector<int>类型构造,防止其被识别为迭代器区间构造,造成非法指针访问
		vector(int n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

 

2.1.4、拷贝构造:

对于拷贝构造来说,我们有传统写法和现代写法之分,传统写法就是所谓的普通操作步骤,即:开空间、赋值。

//拷贝构造
		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			
			//memcpy(_start, v._start, sizeof(T) * v.size()); //浅拷贝
			for (size_t i = 0; i < v.size(); ++i)
			{
				_start[i] = v._start[i];
			}

			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}

当然,既然有传统写法,也就有现代写法,我们一般用的也都是现代写法实现拷贝构造:(代码如下)

template <class InputIterator>
vector(InputIterator first, InputIterator last)//迭代器初始化
    :_start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    while (first != last)
    {
        push_back(*first);
        ++first;
    }
}

//现代写法
vector(const vector<T>& v)
    :_start(nullptr)
    , _finish(nullptr)
    , _endofstorage(nullptr)
{
    vector<T> tmp(v.begin(), v.end());
    swap(tmp);//this和tmp进行swap
}

2.2、模拟迭代器iterator:

typedef T* iterator;
		typedef const T* const_iterator;

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

2.3、空间类接口模拟实现:

容量空间接口说明
size获取数据个数
capacity获取容量大小
empty判断是否为空
resize改变vector的size
reserve 改变vector的capacity

size、capacity、empty:

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

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

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

reserve:

void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();  //记录扩容前对象的大小
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * size()); //浅拷贝
					for (size_t i = 0; i < sz; ++i)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				_end_of_storage = _start + n;
			}
		}

这里我们需要记录一下扩容前对象的大小,为确定扩容后的_finish准备,那为什么不直接+size()?看一下函数size(),其是由迭代器相减得出的结果,即_finish-_start,如果按照_finish = _start + size()求解,此时得size()是刚销毁后重置后得,其值为0,所以导致_finish得地址错误。

resize:

		void resize(size_t n, T& val = T())
		{
			if (n < size())
			{
				//删除数据
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);
				}

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

			}
		}

 2.4、增删查改:

  对于这些接口,上述提到过:迭代器失效与扩容机制息息相关,而增删查改会频繁的调用扩容机制,因此在这里也就详细的介绍关于迭代器失效的场景以及解决的方法。

push_back、pop_back:

void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}

			*_finish = x;
			++_finish;
		}

		void pop_back()
		{
			assert(!empty());
			--_finish;
		}

insert:

iterator insert(iterator pos, const T& val)
		{
			assert(pos <= _finish);
			assert(pos >= _start);
			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start; //保留扩容前的pos位置
				reserve(capacity() == 0 ? 4 : 2 * capacity());
				pos = _start + len; //确定扩容后的pos位置,解决迭代器pos失效
			}

			//挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			//插入数据
			*pos = val;
			++_finish;
			return pos;
		}

 

对于insert,当一个对象容量满了时,我们需要对其进行扩容,而在扩容后,就会出现一个经典的问题-迭代器失效,当我们将想要插入数据的位置pos和数据val传入函数时,pos会指向对象中的一个数据,而扩容后,整个对象的数据空间改变,但是pos还是指向原先那个已经被销毁的空间,导致迭代器失效,而解决办法便是在扩容后,更新一下pos的位置即可。

erase:

iterator erase(iterator pos)
		{
			assert(pos < _finish);
			assert(pos >= _start);

			//挪动数据
			iterator begin = pos + 1;
			while (begin != _finish)
			{
				*(begin - 1) = *begin;
				++begin;
			}

			--_finish;
			return pos;
		}

 对于erase模拟实现,我们所需要避免的,并不是异地扩容的问题,因为erase是不需要扩容的,但是有可能会发生一些找不到的问题,即如果在最后一个位置删除,亦或者连续的偶数需要删除,这都是我们需要在代码中处理的细节问题,因此对于这些问题,我们在模拟实现时只需要记住一件事,一定要留意pos的指向,即我们必须在函数用过后及时更新pos的值,这也与库中的处理方法相同,通过返回值的方法。

其他函数接口:

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

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

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

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

三、深拷贝问题:

3.1、 vector<vector< int >>:

void test_vector9()
{
    vector<vector<int>> vv;
    vector<int> v(5, 1);
    vv.push_back(v);
    vv.push_back(v);
    vv.push_back(v);
    vv.push_back(v);

    for (size_t i = 0; i < vv.size(); i++)
    {
        for (size_t j = 0; j < vv[i].size(); j++)
        {
            cout << vv[i][j] << " ";
        }
        cout << endl;
    }
    cout << endl;
}

 

可以看出vector支持T为vector类型。

此时,我们再尾插一个数据,观察一下扩容后整个对象:

void test_vector9()
{
    vector<vector<int>> vv;
    vector<int> v(5, 1);
    vv.push_back(v);
    vv.push_back(v);
    vv.push_back(v);
    vv.push_back(v);
    vv.push_back(v);

    for (size_t i = 0; i < vv.size(); i++)
    {
        for (size_t j = 0; j < vv[i].size(); j++)
        {
            cout << vv[i][j] << " ";
        }
        cout << endl;
    }
    cout << endl;
}
}

可以明显的看出,扩容后整个对象出现问题。

为什会出现这样的问题,仔细观察一下,对象中的大多数据都为随机值, 不难猜出,是异地扩容后,发生了浅拷贝,对象中的_start还是指向原来的地址,而且异地扩容后,会对原空间地址进行销毁,也就是_start变成了野指针。

当我们到了第五个push_back,也就是需要扩容的时候,我们发现:tmp与原本_start的位置指向的是同一个位置(注意外部的_start与内部的第一个_start指向的位置是一样的),这是由于memcpy引起的,而我们知道,memcpy所引起的异地扩容会释放旧空间,即释放旧位置所指向的位置,但这一释放,就导致了新开辟的tmp内部的指针变量指向的空间也被释放了。即:
 

至此,我们知道这是由于reserve中的memcpy所造成的的浅拷贝导致的,那么如何进行处理呢?

既然浅拷贝的memcpy不行,那我们就可以通过赋值的方式在拷贝中开辟新空间,进行深拷贝:

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();  //记录扩容前对象的大小
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * size()); //浅拷贝
					for (size_t i = 0; i < sz; ++i)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				_end_of_storage = _start + n;
			}
		}

因此我们同样也需要注意: 在C++中要避免使用C语言中的函数:memcpy、realloc、malloc等(realloc原地扩还好,若是异地扩容,就会发生我们所提到的错误)

四、模拟vector代码:

vector.h:

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

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}

		//默认构造
		vector()
			:_start(nullptr)  //也可以在声明时用缺省值
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{}

		//vector<int> v(10,5)
		vector(size_t n, const T& val = T())
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		//为vector<int>类型构造,防止其被识别为迭代器区间构造,造成非法指针访问
		vector(int n, const T& val = T())
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			reserve(n);
			for (size_t i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

		//迭代器区间构造
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			while (first != last)  //迭代器遍历不要使用<,链表的迭代器不是连续的
			{
				push_back(*first);
				++first;
			}
		}

		//拷贝构造
		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			
			//memcpy(_start, v._start, sizeof(T) * v.size()); //浅拷贝
			for (size_t i = 0; i < v.size(); ++i)
			{
				_start[i] = v._start[i];
			}

			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
		}
		
		~vector()
		{
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}

		void resize(size_t n, T& val = T())
		{
			if (n < size())
			{
				//删除数据
				_finish = _start + n;
			}
			else
			{
				if (n > capacity())
				{
					reserve(n);
				}

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

			}
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();  //记录扩容前对象的大小
				T* tmp = new T[n];
				if (_start)
				{
					//memcpy(tmp, _start, sizeof(T) * size()); //浅拷贝
					for (size_t i = 0; i < sz; ++i)
					{
						tmp[i] = _start[i];
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = _start + sz;
				_end_of_storage = _start + n;
			}
		}

		void push_back(const T& x)
		{
			if (_finish == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}

			*_finish = x;
			++_finish;
		}

		void pop_back()
		{
			assert(!empty());
			--_finish;
		}

		iterator insert(iterator pos, const T& val)
		{
			assert(pos <= _finish);
			assert(pos >= _start);
			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start; //保留扩容前的pos位置
				reserve(capacity() == 0 ? 4 : 2 * capacity());
				pos = _start + len; //确定扩容后的pos位置,解决迭代器pos失效
			}

			//挪动数据
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}

			//插入数据
			*pos = val;
			++_finish;
			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos < _finish);
			assert(pos >= _start);

			//挪动数据
			iterator begin = pos + 1;
			while (begin != _finish)
			{
				*(begin - 1) = *begin;
				++begin;
			}

			--_finish;
			return pos;
		}

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

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

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

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

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

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

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};

	void test_vector1()
	{
		vector<int> v(10, 5);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);
		for (size_t i = 0; i < v1.size(); ++i)
		{
			cout << v1[i] << " ";
		}
		cout << endl;
		v1.pop_back();

		vector<int>::iterator it = v1.begin();
		while (it != v1.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
	}

	void test_vector2()
	{
		vector<int> v1(10, 5);
		vector<int> v2(v1);
		for (size_t i = 0; i < v2.size(); ++i)
		{
			cout << v2[i] << " ";
		}
		cout << endl;

		vector<int> v3;
		v3.push_back(1);
		v3.push_back(2);
		v3.push_back(3);
		v3.push_back(4);
		v3.push_back(5);
		auto pos = find(v3.begin(), v3.end(), 2);
		v3.insert(pos, 20);
		for (size_t i = 0; i < v3.size(); ++i)
		{
			cout << v3[i] << " ";
		}
		cout << endl;
		v3.erase(pos);
		for (size_t i = 0; i < v3.size(); ++i)
		{
			cout << v3[i] << " ";
		}
		cout << endl;
	}

	void test_vector3()
	{
		vector<int> v(10, 5);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值