C++:vector容器的使用和实现

时间:2024-11-02 21:52:20

目录

前言:

一、vector的介绍

二、vector的功能

1、vector的构造

2、迭代器遍历

3、空间增长问题

 4、增删查改

四、迭代器失效问题

五、浅拷贝问题

六、vector的模拟实现


前言:

前面我们介绍了string容器的使用,容器是一通百通的,只要理解了string,那么理解接下来的容器是非常简单的。那么就开始vector的学习之旅吧!

一、vector的介绍

1、相信大家都知道数组的使用,vector其实就是表示可变大小数组的序列容器。

2、vector和数组一样,采用的是连续存储空间来存储元素,也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自 动处理。

3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大 小。

4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。

6. 与其它动态序列容器相比(deque, list and forward_list), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list统一的迭代器和引用更好。

二、vector的功能

容器的使用和实现都是大差不差,之前讲了string,这篇重点讲解它的迭代器失效和拷贝问题,分为两个专题来讲解。下面来讲它的使用和实现:

1、vector的构造

以下是cplusplus官方的example:

// constructing vectors
#include <iostream>
#include <vector>

int main ()
{
  // constructors used in the same order as described above:
  std::vector<int> first;                                // empty vector of ints
  std::vector<int> second (4,100);                       // four ints with value 100
  std::vector<int> third (second.begin(),second.end());  // iterating through second
  std::vector<int> fourth (third);                       // a copy of third

  // the iterator constructor can also be used to construct from arrays:
  int myints[] = {16,2,77,29};
  std::vector<int> fifth (myints, myints + sizeof(myints) / sizeof(int) );

  std::cout << "The contents of fifth are:";
  for (std::vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it)
    std::cout << ' ' << *it;
  std::cout << '\n';

  return 0;
}

2、迭代器遍历

主要讲解正向和反向迭代器: 

1:正向迭代器就是从下标[0]的位置一直遍历到结束,反向迭代器就是反着来,需要注意的是反向迭代器也是++遍历,因为是反向,所以理解成反向往前++遍历,不然--则是往后遍历。

2:const迭代器,只允许读取,不允许修改。

3:范围for,其实底层也是iterator迭代器。

void test_vector1()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	//插入之后遍历
	for (size_t i = 0; i < v.size(); ++i)
	{
		cout << v[i] << " ";
	}
	cout << endl;

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

	vector<int>::reverse_iterator str = v.rbegin();
	while (str != v.rend())
	{
		
		cout << *str << " ";
		str++;
	}
	cout << endl;

	// const对象使用const迭代器进行遍历打印
	vector<int>::const_iterator it = v.begin();
	while (it != v.end())
	{
        //*it += 1;//不可修改
		cout << *it << " ";
		++it;
	}
	cout << endl;


	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	cout << v.max_size() << endl;//数组最大存储量

	v.pop_back();
	v.pop_back();
	v.pop_back();
	v.pop_back();
	v.pop_back();//多删一个的话是会报错的
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

3、空间增长问题

resize和reserve还有reverse,这三个比较容易搞混,其实就是见名声意:

resize:不但增容,还会将其全部初始化,同时影响size和capacity的大小。

reserve:就是简单的开辟一块空间,capacity会影响,不会影响size的大小。

reverse:反向的意思,反向迭代器reverse_iterator。与空间这一块没有什么关系。

//空间增长问题
void test_vector3()
{
	vector<int> v;
	//v.resize(10);//在开空间的同时还会初始化为0
	//v.resize(10, 100);//10个有效元素都赋值100
	//v.resize(12);//剩余的两个有效元素都是为0
	v.reserve(10);//只负责开空间,capacity = 10,没有有效元素size == 0
	//for (size_t i = 0; i <10; ++i)//不能写成i<v.size()
	//{
	//	//v[i] = i;
	//	v.push_back(i);
	//}
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

 4、增删查改

 push_back 和 pop_back 上面代码和string中也有写,用法都是一样的。仔细观察,vector库中,其实并没有find成员接口。但是需要使用它,因为insert中的介绍有显示position:

find和insert:就是要先找到这个元素的位置,然后再进行插入或者删除。 

// 指定位置之前插入元素操作–insert
void TestV4()
{
	// 迭代器失效问题 -- 类似野指针问题
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
 
	vector<int>::iterator pos = find(v.begin(), v.end(), 2);
	if (pos != v.end())
	{
		v.insert(pos, 20); // 在pos之前插入20
        
        /*
        * 如果想要继续通过迭代器操作vector中的元素,需要给pos重新赋值
        * 即 pos = v.insert(pos, 20); // 函数返回指向新插入元素的迭代器
        */
	}
 
	// 在insert之后,pos迭代器位置就失效了,不能再去访问了--insert时增容导致的
	//cout << *pos << endl;
	//*pos = 200;
}

上述代码中就是迭代器失效问题,它其实是跑不通的,会奔溃。请看备注或着看下面的专题。

find和erase:和insert的原理其实也差不多。

// 任意位置插入:insert和erase,以及查找find
void TestV6()
{
	// 使用列表方式初始化,C++11新语法
	vector<int> v{ 1, 2, 3 };
 
	vector<int>::iterator pos = find(v.begin(), v.end(), 2);
    if (pos != v.end())	
	{
        v.erase(pos); // 删除pos位置的数据
        /*
        * 如果想要继续通过迭代器操作vector中的元素,需要给pos重新赋值
        * 即 pos = v.erase(pos); // 返回指向被删除元素下一个w的迭代器
        */
    }
    // 在erase之后,pos迭代器位置就失效了,不能再去访问了
	// 这里的失效是指pos位置的意义变了,pos不再指向原来的值
	//cout << *pos << endl;
	//*pos = 200;
}

对于vector常用的功能已经完成了,接下来我们深度刨析它的迭代器失效问题。

四、迭代器失效问题

迭代器的主要作用:让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector 的迭代器就是原生态指针 T*
因此迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

会引起其底层空间改变的操作,都有可能是迭代器失效

比如:resize、reserve、insert、assign、push_back 等,可能会扩容的接口都会导致迭代器失效。可能指的是:空间不够了需要扩容。

首先来看第一种迭代器失效问题:

在iterator定义之后,插入一个6,会没有什么问题,但是多插入几个,就会出现问题了。为什么呢?那就要提起一开始说的了,vector是一个可动态增容的容器,push_back,插入的时候,容量不够,是要导致增容的,如果空间不够,增容的原理就是增加更大的空间,然后copy数据,释放原来的数组,但是it还是指向原来的数组,所以就导致了迭代器失效了。

这个解决方案就是,不要在代码中 iterator定义和遍历的中间段插入。 

再看第二个问题:删除所有的偶数

void test1()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	//要求删除所有的偶数
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0)
			v.erase(it);
		++it;
	}
	for (auto e : v)
	{
		cout << e << " ";
	}
}

 

 这个时候删除了2,指向3,++it,跳过了3,直接判断4,如果3的位置是30呢,意义发生了改变。这里的it失效,原因是it的位置不对了,再++it就不行了,这个报错是vs编译检查出来的。gcc下不一定会报错,越界访问则会报错,如果没有越界访问的话,但是出的结果不对。

修改方案:erase底层接口是有一个返回值的,请看erase底层实现的代码。

以上两种结果都是迭代器失效。

五、浅拷贝问题

浅拷贝就是按字节数据拷贝,并没有开空间,还是使用原来的空间。请看下面代码:

	void push_back(T data)
	{
		if (_finish == _endofstorge)
		{
			size_t sz = size();
			size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;
			T* tmp = new T[newcapacity];

			if (_start)
			{
				//memcpy(tmp, _start, sizeof(T) * size());
				for (size_t i = 0; i < sz; i++)
				{
					tmp[i] = _start[i];
				}
				delete[] _start;
			}

			_finish = tmp + sz;
			_endofstorge = tmp + newcapacity;
			_start = tmp;
		}

		assert(_finish);
		*(_finish) = data;
		++_finish;
	}

被注释掉的memcpy 本质上其实是一种浅拷贝,它的工作原理是把每一个字节的内容都进行拷贝,因此这样其实是一种浅拷贝。这里是不能使用memcpy来用的。

六、vector的模拟实现


template<class T>
class vector
{
public:
	typedef T* iterator;
	typedef const T* const_iterator;
	vector()
		: _start(nullptr)
		, _finish(nullptr)
		._endofstorge(nullptr)
	{}
	template<class InputIterator>
	vector(InputIterator first, InputIterator last)
		: _start(nullptr)
		, _finish(nullptr)
		, _endofstorge(nullptr)
	{
		while (first != last)
		{
			push_back(*first);
			first++;
		}
	}
	
	vector(const vector<T>& v)//v1(v2)
		: _start(nullptr)
		, _finish(nullptr)
		, _endofstorge(nullptr)
	{
		reserve(v.capacity());
		for (const auto& e : v)
		{
			push_back(e);
		}
	}
	vector(const vector<T>& v)
		: _start(nullptr)
		, _finish(nullptr)
		, _endofstorge(nullptr)
	{
		vector<T> tmp(v.begin(), v.end());
		swap(tmp);
	}



	//vector<int> v1(10,1);
	//vector<char> v2(10, 'A');
	vector(int n, const T& val = T())
		: _start(nullptr)
		, _finish(nullptr)
		, _endofstorge(nullptr)
	{
		reserve(n);
		for (int i = 0; i < n; i++)
		{
			push_back(val);
		}
	}
	vector(size_t n, const T& val = T())
		: _start(nullptr)
		, _finish(nullptr)
		, _endofstorge(nullptr)
	{
		reserve(n);
		for (size_t i = 0; i < n; i++)
		{
			push_back(val);
		}
	}
	~vector()
	{
		delete[] _start;
		_start = _finish = _endofstorge = nullptr;
	}

	//perator= // v1 = v2
	vector<T>& operator = (vector<T>& v)
	{
		//_start = new T[v.capacity()];
		//_finish = _start + v.size();
		//_endofstorge = _start + v.capacity();
		swap(v);
		return *this;
	}

	//capcity
	bool empty()const
	{
		return _finish == _start;
	}
	size_t capacity()const
	{
		return _endofstorge - _start;
	}
	size_t size()const
	{
		return _finish - _start;
	}

	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];
	}

	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 < olsSize(); i++)
				{
					tmp[i] = _start[i];
				}
				delete[] _start;
			}
			_start = tmp;
			_finish = tmp + oldSize;
			_endofstorge = _start + n;
		}
	}

	void resize(size_t n, const T& val = T())
	{
		if (n > capacity()
		{
			reserve(n);
		}
		if (n < size())
		{
			_finish = _start + n;
		}
		else
		{
			_finish = _start + n;
		}
	}
	void push_back(const T& x)
	{
		if (_finish == _endofstorge)
		{
			size_t newCapacity = capacity() == 0 ? 4 : capcity() * 2;
			reserve(newCapacity);
		}
		*_finish = x;
		++_finish;
	}
	void pop_back()
	{
		assert(!empty());
		--_finish;
	}
	//----------------------------------迭代器失效:扩容引起,野指针问题--------------------------------------------------
	iterator insert(iterator pos, const T& val)
	{
		assert(pos >= _start);
		assert(pos < _finish);
		if (_finish == _endofstorge)
		{
			size_t len = pos - _start;
			size_t newCapacity = capacity() == 0 ? 4 : capacity() * 2;
			reserve(newCapacity);

			//扩容导致迭代器失效,需要重新处理一下
			pos = _start + len;
		}
		//挪动数据
		iterator end = _finish - 1;
		while (end >= pos)
		{
			*(end + 1) = *end;
			--end;
		}

		*pos = val;
		++_finish;

		return pos;
	}

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

		iterator begin = pos + 1;
		while (begin < _finish)
		{
			*(begin - 1) = *(begin);
			++begin;
		}
		--_finish;

		return pos;
	}

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

private:
	iterator _start;
	iterator _finish;
	iterator _endofstorge;
};