系列三 vector

本文详细介绍了C++标准库中的vector,作为一种动态增长的数组,vector在内存管理上有其独特之处。当空间不足时,会进行扩容操作,涉及数据搬运。文章讨论了vector的类声明,包括size、capacity等成员函数,以及push_back方法的工作原理。此外,还提到了vector迭代器的设计,与list迭代器的区别,以及vector在扩容过程中可能面临的拷贝构造和析构问题。
摘要由CSDN通过智能技术生成

定义

相比较于array这种静态的数组来说,vector是一种可以动态增长的数组。vector可以保证数据在逻辑和物理上都是连续的,因此在原来的空间上,假设数组的空间不够,此时需要重新开辟空间,但是重新开辟的空间不一定和原空间是相连的,因此为了保证vector的数据的连续性,vector在扩容的时候是需要有数据的搬运过程的。

class的声明

template<class T, class Alloc = alloc>

class vector{
public:
	typedef T value_type;
	typedef value_type iterator;
	typedef value_type& reference;
	typedef size_t size_type;
protected:
	iterator start;
	iterator finish;
	iterator end_of_storage;
public:
	iterator begin(){return start;}
	iterator end(){return finish;}
	size_type size() const{return size_type(end() - begin());}
	size_type capacity() const{return size_type(end_of_storage - begin());}
	bool empty() const{return begin() == end();}
	reference operator[](size_type n){return *(begin() + n);}
	reference front(){return *begin();}
	reference back(){return *(end() - 1);}
};


vector设计中最主要的就是三个iterator。start,finish,end_of_sotrage分别存的是当前存储数组元素的内存空间的起始地址,最后一个元素的下一个地址,连续空间的地址。

  • begin()返回起始地址
  • end()返回元素的尾地址
  • size()当前存了多少个元素
  • capacity当前空间最多可以存多少元素
  • empty()当前空间是否为空
  • []运算符重载,范围数组元素
  • front()取vector中的第一个元素
  • end()取vector中的最后一个元素

size和capacity

  • capacity是当空间不够时,系统一次allocate的空间的大小。对于下图左边的空间来说,capacity=8
  • size是指当前数组存的元素的个数,size=6.
    size永远小于或者等于capacity.当size>capacity,空间扩容,同时将现有的数据搬到新的空间中去。
    在这里插入图片描述
    新扩的空间大小一般是按照原有空间的2倍大小增大。

push_back

对于vector常用的push_back来说,这里就会存在扩容的问题,源代码如下。push_back的逻辑很简单,检测当前空间是否够存,够的话,我就调用全局函数construct将元素存到内存中去;不够的话,调用辅助的函数来插入。

template<class T, class Alloc>

void vector<T, Alloc>::push_back(const T& x){
	if(finish != end_of_storage){//还有空间
		construct(finish, x);//全局函数
		++finish;
	}else{
		insert_aux(end(), x);//调用下面的辅助的函数。
	}
}

辅助函数有两个参数position和x,position代表新插入元素的位置,x代表新插入的数值。这个辅助函数除了被push_back调用以外,还会被insert调用(在任意位置插入元素,这种插入也可能会导致扩容)。

void vector<T, Alloc>::insert_aux(iterator position, const T& x){
	//这里重新检测就是因为除了push_back调用,还会有insert调用 ,所以要检查
	if(finish != end_of_storage){//空间够
		construct(finish, *(finish - 1));
		++finish;
		T x_copy = x;
		copy_backward(position, finish - 2, finish - 1);
		*position = x_copy;
	}else{//空间不够
		const size_type old_size = size();
		//扩容,扩大为原来的两倍,当原数组为空,给分两个空间
		const size_type len = old_size != 0 ? 2 * old_size : 1; 
		//数据搬运
		//首先先分配空间
		iterator new_start = data_allocator::allocate(len);
		iterator new_finish = new_start;
		//进行数据搬运
		try{
			//根据原来的start,position(也就是尾地址),copy到新地址(new_start),返回当前新地址的最后一个元素的下一个地址
			new_finish = uninitialized_copy(start, position, new_start);
			//将新插入的x放入新的尾地址
			construct(new_finish, x);
			//指针+1
			++new_finish;
			//这里有这行是因为这个函数对于insert函数来说,因为可能在中间进行插入,因此涉及到扩容后分两段搬运的问题。
			new_finish = uninitialized_copy(position, finish, new_finish);
		}
		catch(...){
			destroy(new_start, new_finish);
			data_allocator::deallocate(new_start, len);
			throw;
		}

		//释放原来的vector的空间
		destroy(begin(), end());
		deallocate();
		//调整新的迭代器的指向
		start = new_start;
		finish = new_finish;
		end_of_storage = new_start + len;
	}
}

vector的扩容存在下面几个问题:

  • 大量调用拷贝构造函数和析构函数
  • 设计数据间的搬运

vector的迭代器的设计

vector的迭代器相较于list专门写了一个类来处理,他可以直接声明指针就好了。

template<class T, class Alloc = alloc>

class vector{
public:
	typedef T value_type;
	typedef value_type* iterator;//T*
};

找iterator的特性

vector<int> vec;
vector<int>::iterator iter = vec.begin();

//找iterator_category
iterator_traits<iter>::iterator_category;

//找difference——type
iterator_traits<iter>::difference_type

//找指向的value——type
iterator_traits<iter>::value_type;

Reference

侯捷老师

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值