STL容器——vector

本文详细介绍了STL中的vector容器,包括其与array的区别、动态空间扩展策略、构造与内存管理、迭代器实现以及resize和capacity等关键功能。vector采用动态空间,当空间不足时,通过成倍(通常是1.5或2倍)扩容来确保空间,这个过程可能导致迭代器失效。同时,文章提到了预分配空间的reserve函数和释放空间的方法,以及元素的插入与删除操作。
摘要由CSDN通过智能技术生成

STL容器——vector

vectorarray非常相似,两者的唯一差别在于空间运用的灵活性。array是静态空间,一旦配置了就不能更改;要换个大一点的房子,可以,一切琐碎得由客户端自己来;首先配置一块新空间,然后将元素从旧地址一一搬往新地址,再把原来的空间释放给系统。vector是动态空间,随着新元素的加入,它的内部机制会自行扩充空间以容纳新元素。

vector定义摘要

template<class T, class Alloc = alloc>
class vector{
public:
    typedef T				value_type;
    typedef value_type* 	pointer;
    typedef value_type* 	itertor;
    typedef value_type&		reference;
    typedef size_t			size_type;
    typedef ptrdiff_t		difference_type;

protected:
    typedef simple_alloc<value_type, Alloc> data_allocator;
    iterator start;			//表示目前使用空间的头
    iterator finish;		//表示目前使用空间的尾
    iterator end_of_storage;//表示目前可用空间的尾
    
    void insert_aux(iterator position,const T& x);
    void deallocate(){
        if(start)
            data_allocator::deallocate(start, end_of_storage - start);
    }
    void fill_initialize(size_type n, const T& value){
        start = allocate_and_fill(n, value);
        finish = start + n;
        end_of_storage = finish;
    }

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); }
    
    vector() : start(0), finish(0), end_of_storage(0) {}
    vector(size_type n, const T& value) { fill_initialize(n, value); }
    vector(int n, const T& value) { fill_initialize(n, value); }
    vector(long n, const T& value) { fill_initialize(n, value); }
    explicit vector(size_type n) { fill_initialize(n, T()); }
    
    ~vector(){
        destory(start, finish);
        deallocate();		//这是vector的一个成员函数
    }
    
    reference front() { return *begin(); }		//第一个元素
    reference back() { return *(end() - 1); }	//最后一个元素
    
    void push_back(const T& x){
        if(finish != end_of_storage){
            construct(finish, x);
            ++finish;
        }
        else
            insert_aux(end(), x);
    }
    void pop_back(){
		--finish;
        destory(finish);
   	}
    iterator erase(iterator position){		//清除某位置上的元素
        if(position + 1 != end())
            copy(position+1, finish, position);		//后续元素往前移
        --finish;
        destory(finish);
        return position;
    }
    void resize(size_type new_size, const T& x){
        if(new_size < size())
            erase(begin() + new_size, end());
        else
            insert(end(), new_size - size(), x);
    }
    void resize(size_type new_size) { resize(new_size, T& x); }
    void clear() { erase(begin(), end()); }
    
protected:
    //配置空间并填满内容
    iterator allocate_and_fill(size_type n, const T& x){
        iterator result = data_allocator::allocate(n);
        uninitialized_fill_n(result, n, x);
        return result;
    }
};

vector的迭代器

vector维护的是一个连续线性空间,所以不论其元素类型是什么,普通指针都可以作为vector的迭代器而满足所有必要条件。因为vector迭代器所需要的操作行为,如operator*,operator->,operator++,operator--,operator+,operator-,operator+=,operator-=,普通指针天生就具备。vector支持随机存取,而普通指针正有这样的能力。所以,vector提供的是random access iterator

template<class T. class Alloc = alloc>
class vector{
	typedef T		value_type;
    typedef value_type* iterator;	//vector的迭代器是普通指针
    ...
};

根据上述定义,如果客户端写出这样的代码:

vector<int>::iterator ivite;
vector<Shape>::iterator svite;

ivite的类型其实就是int*svite的类型其实就是Shape*

vector的数据结构

vector所采用的数据结构非常简单:线性连续空间。它以两个迭代器startfinish分别指向配置得来的连续空间中目前已被使用的范围,并以迭代器end_of_storage指向整块连续空间(含备用空间)的尾端。

template<class T, class Alloc = alloc>
class vector{
...
protected:
    iterator start;				//表示目前使用空间的头
    iterator finish;			//表示目前使用空间的尾
    iterator end_of_storage;	//表示目前可用空间的尾
    ...
};

运用start,finish,end_of_storage三个迭代器,便可轻易地提供首尾标识,大小,容量,容器判断,注标([ ])运算子,最前端元素,最后端元素等功能。

template <class T, class Alloc = alloc>
class vector{
...
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的构造和内存管理:constructor,push_back

vector提供许多constructors,其中一个允许我们指定空间大小及初值。

vector(size_type n, const T& value) { fill_initialize(n, value); }

void fill_initialize(size_type n, const T& value){
    start = allocate_and_fill(n, value);
    finish = start + n;
    end_of_storage = finish;
}

iterator allocate_and_fill(size_type n, const T& x){
    iterator result = data_allocator::allocate(n);
    uninitialized_fill_n(result, n, x);	//全局函数
    return result;
}

当我们以push_back()将新元素插入于vector尾端时,该函数首先检查是否还有备用空间,如果有就直接在备用空间上构造元素,并调整迭代器finish,使vector变大。如果没有备用空间了,就扩充空间(重新配置,移动数据,释放原空间)。

void push_back(const T& x){
    if(finish != end_of_storage){		//还有备用空间
        construct(finish, x);
        ++finish;
    }
    else
        insert_aux(end(), x);			//没有备用空间
}

template<class T, class Alloc>
void vector<T, alloc>::insert_aux(iterator position, const T& x){
    if(finish != end_of_storage)	//还有备用空间
    {
        //在备用空间起始处构造一个元素,并以vector最后一个元素值为其初始值
    	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;
        //以上配置原则:如果原大小为0,则配置1个元素的大小
        //如果原大小不为0,则配置原大小的两倍
        //前半段用来放置原数据,后半段准备用来放置新数据
        iterator new_start = data_allocator::allocate(len);	//实际配置
        iterator new_finish = new_start;
        try{
            //将原vector的内容拷贝到新vector
            new_finish = uninitialized_copy(start, position, new_start);
            //为新元素设定初值x
            construct(new_finish, x);
            ++new_finish;
            //将安插点的原内容也拷贝过来(提示:本函数也可能被insert(p, x)调用)
            new_finish = uninitialized_copy(position ,finish, new_finish);
        }
        catch(...){
            destory(new_start, new_finish);
            data_allocator::deallocate(new_start, len);
            throw;
        }
        //析构并释放原vector
        destory(begin(), end());
        deallocate();
        
        //调整迭代器,指向新vector
        start = new_start;
        finish = new_finish;
        end_of_storage = new_start + len;
    }
}

注意,所谓动态增加大小,并不是在原空间之后接续新空间(因为无法保证原空间之后尚有可配置的空间),而是以原大小的两倍另外配置一块较大的空间,然后将原内容拷贝过来,然后才开始在原内容之后构造新元素,并释放原空间,因此,对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器就都失效了。

vector删除其中的元素,迭代器如何变化?为什么是两倍扩容?释放空间?

size()函数返回的是已用空间大小,capacity()返回的是总空间大小,capacity() - size()则是剩余的可用空间大小。当size()capacity()相等,说明vector目前的空间已被用完,如果再添加新元素,则会引起vector空间的动态增长

由于动态增长会引起重新分配内存空间,拷贝原空间,释放原空间,这些过程会降低程序效率。因此,可以使用reserve(n)预先分配一块较大的指定大小的内存空间,这样当指定大小的内存空间未使用完时,是不会重新分配内存空间的,这样便提升了效率。只有当 n > capacity()时,调用reserve(n)才会改变vector容量。

resize()成员函数只改变元素的数目,不改变vector的容量

  • 空的vector对象,size()capacity()都为0;
  • 当空间大小不足时,新分配的空间大小为原空间大小的2倍;
  • 使用reserve()预先分配了一块内存后,在空间未满的情况下,不会引起重新分配,从而提升了效率;
  • reserve()分配的空间比原空间小时,不会引起重新分配的
  • resize()只改变容器的元素数目,不改变容器的大小;
  • reserve(size_type)只是扩大capacity值,这些内存空间可能还是未初始化的,如果此时使用下标运算符[]来访问,则可能会越界;而resize(size_type new_size)会真正使容器具有new_size个对象;

不同的编译器,vector有不同的扩容大小,在vs下是1.5倍,在GCC下是2倍。

空间和时间的权衡,简单来说,空间分配的多,平摊时间复杂度低,但浪费空间也多。

采用成倍方式扩容,可以保证常数的时间复杂度,而增加指定大小的容量只能达到O(n)的时间复杂度。

如何释放空间:

由于vector的内存占用空间只增不减,比如你首先分配了10000个字节,然后erase掉后面的9999个,留下一个有效元素,然是内存占用仍为10000个,所有内存空间是在vector析构时才能被系统回收。empty()检测容器是否为空,clear()可以清空所有元素

clear()无法保证内存的回收。

vector的增加删除是怎么做的?为什么是1.5倍或者2倍?

  • 新增元素:vector通过一个连续的数组存放元素,如果数组已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,再插入新增的元素;
  • vector的任何操作, 一旦引起空间重新配置,指向原vector的所有迭代器就都失效了
  • 初始时刻vectorcapacity为0,插入第一个元素后capacity增加为1
  • 考虑可能产生的堆空间浪费,成倍增长倍数不能太大,使用较为广泛的扩容方式有两种,以2倍方式扩容,或者以1.5倍方式扩容。
  • 新增元素:vector通过一个连续的数组存放元素,如果数组已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,再插入新增的元素。
  • pop_back()可以删除最后一个元素,erase()可以删除由一个iterator指出的元素,也可以删除一个指定范围的元素。remove()也可以删除vector容器的元素,不同的是,采用remove不会改变容器的大小,而pop_back()erase()等成员函数会改变容器的大小。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值