STL库容器之一:vector

vector的介绍

  1. vector是一个可以改变大小的序列容器。
  2. 和数组一样,vector使用连续的存储位置来存储其元素,这也就意味着可以通过使用下标对vector进行访问,并且效率与数组相同。但与数组不同的是,它们的大小可以动态变化,其存储由容器自动处理。
  3. 本质上,vecto使用动态分配的数组来存储其元素。当插入新元素时,这个数组可能需要重新分配以增加大小,这意味着分配一个新的数组并将所有元素移动到其中。从时间复杂度来看,这是一项代价很高的任务,因此,vector不会在每次向容器中添加元素时重新分配。
  4. vector的分配空间的方式:vector会分配一些额外的存储空间来适应可能的增长,因此容器的实际容量会比实际需要存储的空间更大。不同的库可以实施不同的增长策略来平衡内存使用和重新分配,但在任何情况下,重新分配都应该只在大小呈对数增长的区间内发生,以便在向量末尾插入单个元素时是在常数时间的复杂度内完成的。
  5. 因此,与数组相比,vector 消耗更多的内存,以换取管理存储和以高效方式动态增长的能力。
  6. 与其他动态序列容器 (deque、list 和 forward_lists) 相比,vector 访问其元素非常高效 (就像数组一样), 并且相对高效地在其末端添加或删除元素。对于涉及在末端以外位置插入或删除元素的操作,它们的性能比其他容器差。

记下来,我们根据https://legacy.cplusplus.com/文档中接口的介绍,进而实现一个自己编写的vector容器。

vector的定义:

	template<class T>
	class vector
	{

	private:
		T* _start;
		T* _finish;
		T* _end_of_storage;
	};

这就是vector的定义,_start表示第一个元素的位置,_finish表示最后一个元素的下一个位置,_end_of_storage代表开辟空间的下一个位置 ,就是为了尽可能去减少开辟空间的资源消耗,所以所开辟的空间会比实际的空间会大一点。

vector的构造:

 allocator_type() 是 C++ STL 中容器类的一个成员函数。分配器负责管理容器的内存分配和释放操作。默认的分配器在大多数情况下已经够用了,所以我们不需要自己动手写一个,用库默认提供的就好。

		vector()
			:_start(nullptr),_finish(nullptr),_end_of_storage(nullptr)
		{}

		vector(const vector<T>& x)
		{
			int len = x._finish - x._start;
			int capacity = x._end_of_storage - x._start;
    		_start = new T[len];
            //memcpy(_start, x._start, size() * sizeof(T));
			for (int i = 0; i < len; i++)
			{
				_start[i] = x._start[i];
			}
			_finish = _start + len;
			_end_of_storage = _start + capacity;
		}

		vector(size_t n, const T& val = T())
		{
			_start = new T[n];
			for (size_t i = 0; i < n; i++)
			{
				_start[i] = val;
			}
			_finish = _start + n;
			_end_of_storage = _start + n;
		}

		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			InputIterator cur = first;
			while (cur != last)
			{
				push_back(*cur);
				cur++;
			}
		}

vector的iterator

begin是第一个元素的位置,而end是最后一个位置的下一个位置。 形成左闭右开的结构,STL中的序列容器大多都是左闭右开的结构。

 

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的容量函数:

		size_t size()
		{
			return _finish - _start;
		}

		size_t capacity()
		{
			return _end_of_storage - _start;
		}

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

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

				size_t len = _finish - _start;
				for (size_t i = len; i < n; i++)
				{
					_start[i] = val;
				}

				_finish = _start + n;
			}
		}
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t len = size();
				T* temp = new T[n];
				if (_start)
				{
					//memcpy(temp, _start, size() * sizeof(T));
					for (int i = 0; i < len; i++)
					{
					    temp[i] = _start[i];
					}
					delete[] _start;
				}

				_start = temp;
				_finish = _start + len;
				_end_of_storage = _start + n;
			}
		}

vector的增删查改:

 

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

		void pop_back()
		{
			_finish--;
		}

		iterator insert(iterator position, const T& val)
		{
			if (_finish == _end_of_storage)
			{
				int len = position - _start;
				reserve(capacity() * 2);

				position = _start + len;
			}

			size_t end = size();
			while (_start + end > position)
			{
				_start[end] = _start[end - 1];
				end--;
			}
			_start[end] = val;
			_finish++;

			return position;
		}

		iterator erase(iterator position)
		{
			iterator cur = position;
			for (int i = 0; cur < _finish; cur++)
			{
				*cur = *(cur + 1);
			}

			_finish--;

			return position;
		}

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

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

 vector空间增长:

capacity的代码在vs和g++下分别运行就可以发现,vs下capacity基本是按照1.5倍增长的,g++是按2倍增长的。vs是PJ版本下的STL,g++是SGI版本下的STL。

#include <iostream>
#include <vector>
int main()
{
	std::vector<int> v;
	size_t sz = v.capacity();
	for (int i = 0; i < 100; i++)
	{
		v.push_back(i);

		if (sz != v.capacity())
		{
			sz = v.capacity();
			std::cout << "capacity change:" << sz << std::endl;
		}
	}
	//buluo::test();
	return 0;
}

 VS下:

g++下:

vector迭代器失效的问题。

迭代器的主要作用就是让算法可以不用关心底层的数据结构,其底层就是一个指针或者对指针进行了封装,vector的迭代器就是原生指针T*。因此迭代器失效,实际就是迭代器底层对应的指针所指向的空间被销毁了,而使用一块已经被释放的空间,,如果继续使用已经失效的迭代器,程序就很有可能会崩溃。现在我们通过测试代码,来看看迭代器失效的问题。

  • 扩容导致底层空间改变,造成迭代器失效。
	void test()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		v.push_back(6);

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

		vector<int>::iterator it = v.begin() + 2;
		v.insert(it, 30);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		v.insert(v.begin(), 30);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

 

大家可以看到这段代码貌似没有问题,我让它插入的位置都是正确的,那么我现在就增加一个东西。

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

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

		vector<int>::iterator it = v.begin() + 2;
		v.insert(it, 30);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;

		v.insert(v.begin(), 30);
		for (auto e : v)
		{
			cout << e << " ";
		}
		cout << endl;
	}

仅仅是在刚开始插入的时候多插入了一个7

明显可以看到,程序崩溃了,正常退出时返回值应该为0的,但是这次崩溃了,这就是迭代器失效的问题,我们通过调试窗口看一看。 

通过两张图对比,我们可以发现,当我们进行插入的时候,空间不够了,需要扩容,扩容之后,明显可以看到position位置已经不是之前的位置了,而此时我们仍然在一块不合法的位置进行插入,这就导致程序崩溃了。

那我们应该怎么解决这个问题,当扩容后进行pos的重新定位就可以了,现在我们进行改变insert函数。

		void insert(iterator position, const T& val)
		{
			if (_finish == _end_of_storage)
			{
				int len = position - _start;
				reserve(capacity() * 2);

				position = _start + len;
			}

			size_t end = size();
			while (_start + end > position)
			{
				_start[end] = _start[end - 1];
				end--;
			}
			_start[end] = val;
			_finish++;
		}

  • 指定位置进行删除时 
	void test()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);

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

		auto pos = std::find(v.begin(), v.end(), 3);
		v.erase(pos);

		cout << *pos << endl;
	}

 

可以明显看到上面那段代码的行为是越界的,erase在删除position位置之后元素是会往前移动的,并不会导致底层空间的改变,理论上讲迭代器是不会失效的,但是上面这段代码中,删除的是最后一个元素,那么迭代器就失效了,这时候如果继续访问,就会造成越界的问题。而在VS下对迭代器的检查是很严格的,一般都会直接报错。我们可以换成STL库中的vector看一看。

在Linux中,g++编译器对迭代器的失效检测的并不严格,处理方式没有vs下那么极端,VS只要失效就报错了,而g++可以运行,但是结果可能会出错,我们进行同一段代码的测试:

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;
    v.reserve(100);
    cout << "扩容之后,vector的容量为: " << v.capacity() << endl;

    while (it != v.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    return 0;
}

int main()
{
    vector<int> v{1, 2, 3, 4, 5};
    auto it = find(v.begin(), v.end(), 3);
    v.erase(it);
    cout << *it << endl;
    while (it != v.end())
    {
        cout << *it << " ";
        ++it;
    }
    cout << endl;
    return 0;
}

 

 而解决这个迭代器失效的问题就是在再次使用之前,对迭代器进行重新赋值即可。我们就拿上面的代码举例。

可以看到在接收返回值后,VS下也可以成功运行了。 

使用memcpy拷贝的问题

		vector(const vector<T>& x)
		{
			int len = x._finish - x._start;
			int capacity = x._end_of_storage - x._start;
			_start = new T[len];
			memcpy(_start, x._start, size() * sizeof(T));
			//for (int i = 0; i < len; i++)
			//{
			//	_start[i] = x._start[i];
			//}
			_finish = _start + len;
			_end_of_storage = _start + capacity;
		}

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t len = size();
				T* temp = new T[n];
				if (_start)
				{
					memcpy(temp, _start, size() * sizeof(T));
					//for (int i = 0; i < len; i++)
					//{
					//	temp[i] = _start[i];
					//}
					delete[] _start;
				}

				_start = temp;
				_finish = _start + len;
				_end_of_storage = _start + n;
			}
		}

大家自己写这两个功能模块时,大家可能会想,为什么还要一个一个赋值那么麻烦,C语言的库中不是提供了memcpy的函数么,我们直接拷贝一下不久可以了么,简单高效。那么我们来测试一下下面这段代码,看看memcpy函数会对这两个函数会有什么影响。 

	void test_vector6()
	{
		vector<string> v;
		v.push_back("111111111111111111");
		v.push_back("222222222222222222");
		v.push_back("333333333333333333");
		v.push_back("444444444444444444");
		v.push_back("555555555555555555");

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

		vector<string> v1(v);
		for (auto& e : v1)
		{
			cout << e << " ";
		}
		cout << endl;
	}

 

可以明显看到,程序崩溃了,这是为什么呢 

 memcpy是内存的二进制格式的拷贝,将一段内存空间原封不动的拷贝到另一块空间中,如果是自定义类型的话,memcpy即高效又不会出错,但是如果是自定义类型的元素,且自定义类型的元素涉及资源管理时,这时候就会出错,因为memcpy是浅拷贝。

这样就可以宏观的看到,当插入“555555555555”时,我们需要进行空间的扩增,这个时候将它们按值拷贝之后,之前的空间会被释放,delete[] _start,这时对于自定义类型会去调用自己的析构函数,这样就会导致新创建的空间指向了一片已经被析构的空间,这时候导致程序错误,所以我们需要进行深拷贝,这样才是正解。 

结论:如果对象涉及资源管理时 ,我们需要进行深拷贝,浅拷贝容易造成内存泄漏甚至程序崩溃。

模板参数匹配的问题

    	vector(size_t n, const T& val = T())
		{
			_start = new T[n];
			for (size_t i = 0; i < n; i++)
			{
				_start[i] = val;
			}
			_finish = _start + n;
			_end_of_storage = _start + n;
		}

		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			InputIterator cur = first;
			while (cur != last)
			{
				push_back(*cur);
				cur++;
			}
		}
        int test()
	    {
		    vector<int> v2(10, 1);
		    return 0;
	    }

 

就这么一个简单的代码,为什么会报错呢,这不是调用构造函数就可以了吗?问题的原因就是因为 10和1都是int类型的整数,而我们的vector(size_t n, const T& val = T())第一个参数是size_t,如果要使用该函数的话,第一个参数类型必须是size_t 类型的,这就意味着必须从int转为size_t,但是编译器在编译的时候发现了两个都是int,在匹配时他会匹配与自己最相适应的,所以它就直接匹配到模板的那个构造函数去了,所以导致编译错误,所以这时候我们需要重载一个第一个参数为int的vector(int n, const T& val = T())就可以了。

		vector(int n, const T& val = T())
		{
			_start = new T[n];
			for (int i = 0; i < n; i++)
			{
				_start[i] = val;
			}
			_finish = _start + n;
			_end_of_storage = _start + n;
		}

 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(const vector<T>& x)
	{
		int len = x._finish - x._start;
		int capacity = x._end_of_storage - x._start;
		_start = new T[len];
		//memcpy(_start, x._start, size() * sizeof(T));
		for (int i = 0; i < len; i++)
		{
			_start[i] = x._start[i];
		}
		_finish = _start + len;
		_end_of_storage = _start + capacity;
	}

	vector(size_t n, const T& val = T())
	{
		_start = new T[n];
		for (size_t i = 0; i < n; i++)
		{
			_start[i] = val;
		}
		_finish = _start + n;
		_end_of_storage = _start + n;
	}

	vector(int n, const T& val = T())
	{
		_start = new T[n];
		for (int i = 0; i < n; i++)
		{
			_start[i] = val;
		}
		_finish = _start + n;
		_end_of_storage = _start + n;
	}
	template <class InputIterator>
	vector(InputIterator first, InputIterator last)
	{
		InputIterator cur = first;
		while (cur != last)
		{
			push_back(*cur);
			cur++;
		}
	}


	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<T>& operator=(vector<T> v)
	{
		swap(v);
		return *this;
	}

	

	size_t size()
	{
		return _finish - _start;
	}

	size_t capacity()
	{
		return _end_of_storage - _start;
	}

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

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

			size_t len = _finish - _start;
			for (size_t i = len; i < n; i++)
			{
				_start[i] = val;
			}

			_finish = _start + n;
		}
	}
	void reserve(size_t n)
	{
		if (n > capacity())
		{
			size_t len = size();
			T* temp = new T[n];
			if (_start)
			{
				//memcpy(temp, _start, size() * sizeof(T));
				for (int i = 0; i < len; i++)
				{
					temp[i] = _start[i];
				}
				delete[] _start;
			}

			_start = temp;
			_finish = _start + len;
			_end_of_storage = _start + n;
		}
	}

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

	void pop_back()
	{
		_finish--;
	}

	iterator insert(iterator position, const T& val)
	{
		if (_finish == _end_of_storage)
		{
			int len = position - _start;
			reserve(capacity() * 2);

			position = _start + len;
		}

		size_t end = size();
		while (_start + end > position)
		{
			_start[end] = _start[end - 1];
			end--;
		}
		_start[end] = val;
		_finish++;

		return position;
	}

	iterator erase(iterator position)
	{
		iterator cur = position;
		for (int i = 0; cur < _finish; cur++)
		{
			*cur = *(cur + 1);
			//*(position + i) = *(position + i + 1);
			//_start[position + i] = _start[position + i + 1];
		}

		_finish--;

		return position;
	}

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

private:
	T* _start;
	T* _finish;
	T* _end_of_storage;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值