C++STL之vector动态数组

一、vector的简单使用以及耗时测试:

#include<iostream>
#include<vector>
#include<cstdlib>//qsort bsearch NULL
#include<ctime>
using namespace std;
const int SIZE = 100000;
int main()
{
	vector<int>vec;
	clock_t start = clock();//ms
	for (int i = 0; i < SIZE; ++i)
	{
		vec.push_back(rand() % SIZE);
	}
	cout << "插入100000个元素耗时: " << (clock() - start) << endl;
	cout << "当前的有效元素个数: " << vec.size() << endl;
	cout << "第一个元素是: " << vec.front() << endl;
	cout << "最后一个元素是: " << vec.back() << endl;
	cout << "第一个元素的地址是: " << &vec[0] << endl;
	cout << "vector的起始地址是: " << vec.data() << endl;
	cout << "插入一个元素99999" ; vec.push_back(99999);
	cout << "当前最后一个元素是: " << vec.back() << endl;
	cout << "最终vector的容量是: " << vec.capacity() << endl;
	vec.push_back(999);
//	//挨个查找
//	auto it = ::find(vec.begin(), vec.end(), 999);
//	if (it != vec.end())
//	{
//		cout << "找到了" << *it << endl;
//	}
//	else
//	{
//		cout << "not find" << endl;
//	}

	return 0;
}
执行结果:
插入100000个元素耗时: 63
当前的有效元素个数: 100000
第一个元素是: 41
最后一个元素是: 1629
第一个元素的地址是: 00A3C040
vector的起始地址是: 00A3C040
插入一个元素99999当前最后一个元素是: 99999
最终vector的容量是: 138255
请按任意键继续. . .

在这里插入图片描述
二、vector具备的一些特点:

1.底层是一个动态开辟的一维数组 ,是一个指针T *array;
2.默认实例化时不分配内存,size=0,即vector<int>vec;实际上并没有给分配内存
3.增长时是以二倍的速度增长0->1->2->4->8...   ;
  对应增长需求比较大的,这样效率明显低下,所以我们有reserve()来提前开辟好内存,
  如需增长直接在在提前开辟好的内存上构造对象即可;
4.扩容的时候,需要先找好一块内存,然后将原来vector中的数据copy到新的二倍的vector上,
  释放原来的内存,调整三个指针的位置,指向新的内存上来;
5.线性的容器都会提供[]运算符重载函数,包括array和deque(deque实际不是线性的,看起来是),
  这样访问的时间复杂度明显是O(1),具体实现后面说;
6.不提供前插或者前删的操作,因为挪动元素实在会产生太大的开销(大量的构造和析构函数调用)
   只有push_back()和pop_back(),insert()
7.resize()是扩容,期间会调用构造函数,也会进行释放内存和重新开辟内存,而reserve()只是进行预留空间,不进行构造对象
8.尽管具有[],查询效率比较高O(1),但是往中间插入效率依然会比较低了
9.at()函数和[]函数使用可以说是一模一样. 都是为了访问对应index中存储的数据, 
  如果index大于vector的size(越界). 两者都是抛出out_of_range的exception.
  new如果失败了,也会抛出bad_alloc的异常。

三、找工作时遇到的几个问题:
1.面试官问:你知道为什么vector是以2倍增长的?

?????当时内心想着人家就这样设计的,我怎么知道设计者内心怎么想的
后来查阅了一些资料,确实有某种说法,不过也有的是用1.5倍增长实现的,emmmm

 有人回答说:vector最关键在于查询,使用移位(2的幂)直接得到哈希链以及节点长度,
 然后相减直接得到键值,复杂度为O(2),性能近似于数组,插入删除可动态,
 这就是vector设计的基本目的。
 显然,增长的倍数不可能很大,也不会比 1 小,那么,它的最佳上限是多少呢?
 如果以 大于2 倍的方式扩容,下一次申请的内存会大于之前分配内存的总和,
 导致之前分配的内存不能再被使用。所以,最好的增长因子在 (1,2)之间。

2.swap()方法的实现:

	void swap(_Myt& _Right)
		{	// exchange contents with _Right
		if (this == &_Right)
			;	// 同一个对象,什么也不做
		else if (this->_Getal() == _Right._Getal())
			{	//如果是相同的分配器,那么就直接交换两个对象中指针的地址即可
			this->_Swap_all(_Right);
			_Swap_adl(this->_Myfirst, _Right._Myfirst);
			_Swap_adl(this->_Mylast, _Right._Mylast);
			_Swap_adl(this->_Myend, _Right._Myend);
			}

		else if (_Alty::propagate_on_container_swap::value)
			{	// swap allocators and control information
			//交换分配器和对象的指针信息
			this->_Swap_alloc(_Right);
			_Swap_adl(this->_Myfirst, _Right._Myfirst);
			_Swap_adl(this->_Mylast, _Right._Mylast);
			_Swap_adl(this->_Myend, _Right._Myend);
			}

		else
			{	// 容器是不匹配的,不能进行交换
 #if _ITERATOR_DEBUG_LEVEL == 2
			_DEBUG_ERROR("vector containers incompatible for swap");

 #else /* ITERATOR_DEBUG_LEVEL == 2 */
			_XSTD terminate();
 #endif /* ITERATOR_DEBUG_LEVEL == 2 */
			}
		}

所以,我们很明了可以看到,两个vector对象,在进行交换的时候
并不是挨个的拷贝,而是直接将两个对象的指针交换,有点浅拷贝的意思,实际上就是。

1.如果分配器分配的同一个类型:交换三根指针指向即可
2.如果分配器不一样:得交换对象的使用的分配器Swap_alloc(),然后交换三根指针
3.ERROR

这样做效率明显提高!!!

3.使用的时候哪些方法可能会抛出异常:

1.使用到new() 的地方 C++中new失败不用NULL判断,而是捕获异常,bad_alloc异常
2.at()和operator[]()根据下标访问来元素,当下标越界时 会抛出out_of_range越界的异常

4.分配allocator的实现:

见下面代码

5.如何缓解二倍增长带来的效率问题呢:

resize()调用会析构释放原来的内存,开辟二倍的空间大小,
并且还有将原来的对象拷贝构造到新的内存空间上,效率问题显而易见

所以,我们缓解该问题,实现一个方法叫reserve(),预留空间,提前开辟内存
这样到时候直接去预留的空间构造函数,就不用多次resize()。

四、之前自己实现的一个vector:


我简单的实现一个vector,不用和标准库多相似,只要知道设计思想即可。
template <typename T>
class myallocator
{
public:
	//构造
	void construct(void *ptr, const T &val)
	{
		//定位new,表示在ptr指向的内存,构造一个值为val的对象
		new (ptr)T(val); 
	}
	//析构
	void destroy(T *ptr)
	{
		ptr->~T();
	}
	//开辟内存
	T* allocate(size_t size)
	{
		//malloc
		return (T*)malloc(size);
	}
	//释放内存
	void deallocate(void *ptr)
	{
		free(ptr);
	}
};
template <typename T, typename Allocator = myallocator>
class Vector
{
public:
	//默认构造的vector,底层没分配过内存0
	Vector() :mpVec(NULL), mSize(0), mCur(0){}
	//size表示初始的内存大小,val表示内存初始值
	Vector(int size, const T &val = T())
		:mSize(size), mCur(size)
	{
		mpVec = _allocator.allocate(mSize * sizeof(T));
		for (int i = 0; i < mSize; ++i)
		{
			_allocator.construct(mpVec + i, val);
		}
	}
	//拷贝构造
	Vector(const Vector &src)
		:mSize(src.mSize), mCur(src.mCur)
	{
		mpVec = _allocator.allocate(sizeof(T)*mSize);
		for (int i = 0; i < mCur; ++i)
		{
			_allocator.construct(mpVec + i, src.mpVec[i]);
		}
	}
	//operator=
	Vector& operator=(const Vector &src)
	{
		if (this == &src)
			return *this;

		for (int i = 0; i < mCur; ++i)
		{
			_allocator.destroy(mpVec + i);
		}
		_allocator.deallocate(mpVec);

		mpVec = _allocator.allocate(sizeof(T)*mSize);
		for (int i = 0; i < mCur; ++i)
		{
			_allocator.construct(mpVec + i, src.mpVec[i]);
		}

		return *this;
	}
	~Vector()
	{
		for (int i = 0; i < mCur; ++i)
		{
			_allocator.destroy(mpVec + i);
		}
		_allocator.deallocate(mpVec);
		mpVec = NULL;
	}

	//末尾添加元素    push_front   pop_front  O(n)
	void push_back(const T &val)
	{
		if (full())
			reSize();
		_allocator.construct(mpVec + mCur, val);
		mCur++;
	}
	//末尾删除
	void pop_back()
	{
		if (empty())
			return;
		--mCur;
		_allocator.destroy(mpVec + mCur);
	}

	T front()const{ return mpVec[0]; }
	T back()const{ return mpVec[mCur - 1]; }

	bool full()const{ return mCur == mSize; }
	bool empty()const{ return mCur == 0; }

	T& operator[](int index){ return mpVec[index]; }

	//内存以2倍方式增长
	void reSize()
	{
	  //默认的size==0
		if (mSize == 0)
		{
			mpVec = _allocator.allocate(sizeof(T));
			mSize = 1;
			mCur = 0;
		}
		else
		{
			T *ptmp = _allocator.allocate(mSize * 2 * sizeof(T));
			for (int i = 0; i < mCur; ++i)
			{
				_allocator.construct(ptmp + i, mpVec[i]);
			}
			mSize *= 2;
			for (int i = 0; i < mCur; ++i)
			{
				_allocator.destroy(mpVec + i);
			}
			_allocator.deallocate(mpVec);
			mpVec = ptmp;
		}
	}

	int size()const{ return mCur; }

	//定义当前容器的迭代器类型 ,遍历容器(遍历容器底层的数据结构)
	class iterator
	{
	public:
		iterator(T *p = NULL)
		{
			ptr = p;
		}
		bool operator!=(const iterator &it)
		{
			return ptr != it.ptr;
		}
		int operator-(const iterator &it)
		{
			return ptr - it.ptr;
		}
		void operator++()
		{
			++ptr;
		}
		T& operator*()
		{
			return *ptr;
		}
	private:
		T *ptr;  //本质上就是一根被泛化的指针
	};

	iterator begin(){ return iterator(mpVec); }
	iterator end(){ return iterator(mpVec + mCur); }

private:
	T *mpVec;//动态数组的起始地址,相当于first
	int mSize;//容量
	int mCur;//当前size
	Allocator _allocator;
   //也可以在这里添加一个end指针,这里命名有点不规范,影响不大
};

五、补充vector和array区别

vector:动态增长 动态开辟内存  可扩容  相对灵活
array:固定长度  不可扩容 灵活度差

参考链接:https://en.cppreference.com/w/cpp/container/vector

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值