STL vector

vector是个顺序容器,可以认为是个变长数组。与数组一样,vector是使用连续的存储空间来保存元素,这意味着可以使用指向其元素的常规指针的偏移量来访问它们的元素,而且效率与数组一样高。vector的随机存取效率很高,能达到O(1)的时间复杂度,但也和数组一样,当从中间插入或者删除元素时,需要移动元素,效率比较低。但从其末尾添加或删除元素也相对高效。

但与数组不同的是,vector的大小可以动态变化,容器在需要的时候会自动扩容。vector是使用动态分配的内存来保存元素,当插入一个新元素时如果内存已经不够了就需要重新申请一块更大的内存空间,再将所有的元素拷贝到新内存空间,然后再释放掉旧的内存空间同时销毁旧的元素,最后再插入新元素。在这个过程中,复制元素的时候需要调用元素的构造函数,销毁旧元素的时候也需要调用析构函数。也就是说这个过程可能比较耗时,特别是元素的构造析构比较复杂或者元素数量比较多的时候。所以为了避免过多的内存拷贝和元素构造析构,vector容器会多分配额外的内存空间,也就是说vector的的实际内存容量可能大于容纳其元素所需的存储空间(即其大小)。当vector需要扩容的时候,不同的标准库的扩容策略可能不太一样,GCC的扩容策略是两倍扩容,即新分配的内存空间是旧内存空间的两倍。

扩容异常安全 

vector 自动扩容的异常安全级别是strong guarantee,成功或者回滚保证。就是说即使在自动扩容的时候抛出了异常(比如bad_alloc内存不足异常),vector已能够保证原有的数据不会被破坏,或者说是自身不会被改变。因为在扩容时是分三步走:新开辟一片内存空间,拷贝元素,销毁旧元素,一般只有前两部可能抛出异常,但是即使前两步抛出了异常,因为这时候旧元素并没有被销毁,所以还是能够回滚到之前的状态。

移动构造

如果元素定义了移动构造函数,但是由于vector需要保证 strong guarantee异常安全,除非元素的移动构造函数保证不会抛异常并显示标识为noexcept,vector在扩容时才会采用移动构造函数。因为移动构造函数和拷贝构造函数不同,拷贝构造函数不会破坏旧元素,而移动构造函数通常是从旧元素“窃取”资源,也就是移动构造函数会改变旧元素,如果抛异常了就不能保证能回滚到之前的状态。

迭代器类型

以下这几对迭代,分别组成的空间都是前开后闭空间。

begin,end正向迭代器
rbegin,rend反向迭代器
cbegin,cend常量const正向迭代器
crbegin,crend常量const反向迭代器

容量大小

size元素数量
max_size可能最大的元素数量
resize改变元素数量
empty判断是否为空
capacity容量大小
reserve改变容量大小
shrink_to_fit  

 

关于size、capacity和max_size的区别参考这个例子:

// comparing size, capacity and max_size
#include <iostream>
#include <vector>

int main ()
{
  std::vector<int> myvector;

  // set some content in the vector:
  for (int i=0; i<100; i++) myvector.push_back(i);

  std::cout << "size: " << myvector.size() << "\n";
  std::cout << "capacity: " << myvector.capacity() << "\n";
  std::cout << "max_size: " << myvector.max_size() << "\n";
  return 0;
}

这个程序可能的输出如下

size: 100 capacity: 128 max_size: 1073741823

resize

来看一下resize,这个成员函数是用来改变vector元素的数量。

void resize (size_type n);
void resize (size_type n, const value_type& val);

 参数n为调整大小后的元素数量。那么可能会有以下三种情况:

  1. n小于当前元素数量,那么就会收缩到前n个元素,或者说只保留前n个元素,其余的元素会被移除并销毁。
  2. n大于当前元素数量,那么就会在当前最后一个元素后面插入新的元素,直到元素数量达到n。如果传递了val这个参数,那么新元素就按val来初始化,否则就是value-initialization。值初始化的详细介绍见这个链接:value-initialization。这里可以先简单认为是默认初始化,一般是模式为0.
  3. 如果n比容量capacity还大,那么vector就会自动扩容。

示例:

// resizing vector
#include <iostream>
#include <vector>

int main ()
{
  std::vector<int> myvector;

  // set some initial content:
  for (int i=1;i<10;i++) myvector.push_back(i);

  myvector.resize(5);
  myvector.resize(8,100);
  myvector.resize(12);

  std::cout << "myvector contains:";
  for (int i=0;i<myvector.size();i++)
    std::cout << ' ' << myvector[i];
  std::cout << '\n';

  return 0;
}

输出为: 

myvector contains: 1 2 3 4 5 100 100 100 0 0 0 0

resize要注意迭代器失效问题,在容器收缩的情况下,所有未删除元素的迭代器、指针和引用在调整大小后仍然有效,并指向它们在调用前引用的相同元素。如果容器展开,end迭代器将失效;如果重新分配了存储空间,所有与该容器相关的迭代器、指针和引用也将失效。

对于异常问题,如果n小于或等于容器的大小,resize函数永远不会抛出异常(no-throw guarantee)。如果n更大,并且发生了重新分配,如果元素的类型是可复制的或者移动操作不会抛异常,则在exception的情况下能保证容器不会发生变化(strong guarantee)。

 reserve

void reserve (size_type n);

reserve是用来调整vector容量大小到至少能容纳n个元素。 

如果n大于当前vector容器的容量,该函数将导致容器重新分配存储空间,将容量增加到n(或更大)。在所有其他情况下,函数调用不会导致重新分配,vector的容量也不会受到影响。这个函数对vector大小没有影响,并且不能改变其元素。

 这个函数主要是用来给vector预先扩容,比如这种场景:依次往一个空vector中插入100个元素。最开始vector容量为1,在插入的过程中需要多次扩容最终才能容下100个元素,它的容量变化可能是这样的:1,2,4,8,16,32,64,128.但是假如一开始就用reserve来预留100容量,那么这个过程只需要扩容一次。

 

元素访问

operator[]像数组一样访问
at访问某个元素,有越界保护
front返回第一个元素
back返回最后一个元素
data返回vector存储数据的内存起始指针,之后可以通过指针访问元素

  

元素 操作

assign给元素赋值
push_back从末尾添加一个元素
pop_back删除最后一个元素
insert插入一个元素
erase删除一个元素
clear清空所有元素
emplace 构造和插入元素,
swap交互元素内容
emplace_back 构造元素并把它从末尾插入

 

erase

iterator erase (const_iterator position);
iterator erase (const_iterator first, const_iterator last);


删除某个位置或者某个范围(([first,last))前闭后开区间)内的元素。由于vector是使用数组作为底层存储结构,如果删除的位置不是末尾vector就需要将删除范围之后的元素重新分配到新的位置。所以相对于list和 forward_list,vector的erase操作通常比较低效。
参数
position
指向准备要删除的元素的迭代器。
first,last 指定删除的范围,[first,last)组成一个前闭后开区间,从first到last范围内的元素都会被删除,包括first指向的元素,但是不包含last指向的元素。
返回值是一个迭代器,它指向删除范围的最后一个元素的下一个元素的新位置,因此元素被删除之后,后面的元素要移动到新的位置。
算法复杂度是总共要删除的元素个数加上删除范围最后一个元素之后的所有元素的线性叠加。
指向first(或position)之前的迭代器,指针和引用都保持有效,而指向first(或position)及之后的会失效。
异常安全
如果移除的元素包含容器的最后一个元素,那么能够保证不会抛异常(no-throw guarantee)。因为这种情况不需要移动元素。
否则只能保证basic guarantee,容器能回到一个有效状态。

insert

 

single element (1)
iterator insert (iterator position, const value_type& val);
fill (2)
    void insert (iterator position, size_type n, const value_type& val);
range (3)
template <class InputIterator>
    void insert (iterator position, InputIterator first, InputIterator last);


在position指定的位置插入新元素,如果容器内存不足以容纳新元素就好自动扩容。和erase一样,从中间insert一个元素需要移动元素效率相对比较低。
返回值是一个指向第一个新插入的元素的迭代器。
算法复杂度是要插入的元素数量和要移动的元素数量的线性叠加。

上表的几个插入操作函数基本差不多,要注意一点是迭代器失效问题。如果发生了内存重新分配,所有与容器相关的迭代器、指针和引用都将失效。insert和emplace可以在任何位置插入元素,只有指向插入位置之前元素的迭代器、指针和引用不会失效,其它的都会失效。push_back,emplace_back由于是从尾部插入,之后导致end迭代器失效。

 pop_back函数之会导致end迭代器和指向的被移除元素的迭代器失效。

emplace,emplace_blace和insert、push back的区别是它们是直接在vector的内存空间直接用参数来构造元素,避免了不必要的拷贝。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值