vector是一个能够支持任何类型的容器,本身为一个可以动态增长的数组。

1、vector基本数据结构

STL中所有的容器都包括三部分:

  • 迭代器,遍历容器的元素,控制容器空间的边界和元素移动。
  • 构造函数,满足容器多种多样的初始化。
  • 属性获取,比如begin(),end()等。
template <class T, class Alloc = alloc>
class vector {
public:
// 定义 vector 自身的嵌套型别
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
// 定义迭代器, 这里就只是一个普通的指针
typedef value_type* iterator;
typedef const value_type* const_iterator;
typedef value_type& reference;
typedef const value_type& const_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; // 当前可用空间的尾
...
};

因为 vector 需要表示用户操作的当前数据的起始地址,结束地址,还需要其真正的最大地址。所以总共需要 3 个迭代器,分别来指向数据的头(start),数据的尾(finish),数组的尾(end_of_storage)。

2、构造函数

vector是一种class template, 并不需要手动的释放内存,生命周期结束后就自动调用析构从而释放调用空间。

void deallocate() {
if (start)
data_allocator::deallocate(start, end_of_storage - start);
}
// 调用析构函数并释放内存
~vector() {
destroy(start, finish);
deallocate();
}

3、属性获取

比如返回 vector 的开始和结尾,返回最后一个元素,返回当前元素个数,元素容量,是否为空等。这里需要注意的是因为 end() 返回的是 finish,而 finish 是指向最后一个元素的后一个位置的指针,所以使用 end() 的时候要注意。

public:
// 获取数据的开始以及结束位置的指针. 记住这里返回的是迭代器, 也就是 vector 迭代器就是该类型的指针.
iterator begin() { return start; }
iterator end() { return finish; }
reference front() { return *begin(); } // 获取值
reference back() { return *(end() - 1); }
...
size_type size() const { return size_type(end() - begin()); } // 数组元素的个数
size_type max_size() const { return size_type(-1) / sizeof(T); } // 最大能存储的元素个数
size_type capacity() const { return size_type(end_of_storage - begin()); } // 数组的实际大小
bool empty() const { return begin() == end(); }
//判断 vector 是否为空, 并不是比较元素为 0,是直接比较头尾指针。

4、push和pop操作

vector 的 push 和 pop 操作都只是对尾进行操作, 这里说的尾部是指数据的尾部。当调用 push_back 插入新元素的时候,首先会检查是否有备用空间,如果有就直接在备用空间上构造元素,并调整迭代器 finish。

如果没有备用空间,就扩充空间(重新配置-移动数据-释放原空间),这里实际是调用了另外一个函数:insert_aux 函数。

上图中显示push_back这个函数判断了一次 finish != end_of_storage 这是原因是因为 insert_aux 函数可能还被其他函数调用。

在else 分支进行vector 的动态扩容机制:如果原空间大小为 0 则分配 1 个元素,如果大于0则分配原空间两倍的新空间,然后把数据拷贝过去。

5、pop 元素:

从尾端删除一个元素。

void pop_back()
{ // erase element at end
erase(end() - 1);
}

6、erase 删除元素

erase 函数清除指定位置的元素, 其重载函数用于清除一个范围内的所有元素。实际上就是将删除元素后面所有元素往前移动,对于 vector 来说删除元素的操作开销很大,所以说 vector 它不适合频繁的删除操作,因为它本身是一个数组。示意图如下所示:

清除指定范围内的元素,首先将 finish 迭代器后面的元素拷贝回去,然后返回拷贝完成的尾部迭代器,最后再销毁之后的元素。

删除指定位置的元素就是实际就是将指定位置后面的所有元素向前移动, 最后析构掉最后一个元素。

7、insert 元素

  • 插入点之后的现有元素个数 > 新增元素个数

  • 插入点之后的现有元素个数 <= 新增元素个数

  • 如果备用空间不足

注意:

vector有一个迭代失效问题。所谓迭代失效时由于元素空间重新分配导致之前迭代器访问的元素不存在了,总体有两种情况:

  1. 由于插入元素,使得容器元素整体迁移导致存放原容器元素的空间不再有效,从而使得指向原空间的迭代器失效。
  2. 由于删除元素,使得某些元素次序发生改变导致原本指向某元素的迭代器不再指向期望指向的元素。

全局函数说明:

  1. copy(a,b,c):将(a,b)之间的元素拷贝到(c,c-(b-a))位置。
  2. uninitialized_copy(first, last, result):具体作用是将 [first,last)内的元素拷贝到 result 从前往后拷贝。
  3. copy_backward(first, last, result):将 [first,last)内的元素拷贝到 result 从后往前拷贝。

8、vector总结

  1. vector的成员函数都不做边界检查(at方法会抛异常),使用者要自己确保迭代器和索引值的合法性。
  2. 优点
    • 在内存中占用一块联系的内存空间存储数据,可以像数组一样操作,并且支持动态扩容。
    • 正因如此,其元素可以随机访问,支持下标和vector.at()操作。
    • 节省空间。
  3. 缺点
    • 由于其顺序存储的特性,vector插入、删除操作的时间复杂度是Q(n)。
    • 只能在末端进行pop和push操作。
    • 当动态长度超过默认分配大小后,需要整体重新分配、拷贝和释放空间。

vector概述的更多相关文章

  1. STL源码剖析读书笔记之vector

    STL源码剖析读书笔记之vector 1.vector概述 vector是一种序列式容器,我的理解是vector就像数组.但是数组有一个很大的问题就是当我们分配 一个一定大小的数组的时候,起初也许我们 ...

  2. 带你深入理解STL之Vector容器

    C++内置了数组的类型,在使用数组的时候,必须指定数组的长度,一旦配置了就不能改变了,通常我们的做法是:尽量配置一个大的空间,以免不够用,这样做的缺点是比较浪费空间,预估空间不当会引起很多不便. ST ...

  3. vector源码1(参考STL源码--侯捷):源码

    vector源码1(参考STL源码--侯捷) vector源码2(参考STL源码--侯捷) vector源码(参考STL源码--侯捷)-----空间分配导致迭代器失效 vector源码3(参考STL源 ...

  4. Java集合框架之Vector浅析

    Java集合框架之Vector浅析 一.Vector概述: 位于java.util包下的Vector是Java集合框架的重要一员,虽然没有ArrayList那么的常用,但是我们还要对其做相关学习: 1 ...

  5. java源码--Vector和Stack

    一.Vector简介 1.1.Vector概述 通过API中可以知道: 1)Vector是一个可变化长度的数组 2)Vector增加长度通过的是capacity和capacityIncrement这两 ...

  6. 【c++】标准模板库STL入门简介与常见用法

    一.STL简介 1.什么是STL STL(Standard Template Library)标准模板库,主要由容器.迭代器.算法.函数对象.内存分配器和适配器六大部分组成.STL已是标准C++的一部 ...

  7. STL源码剖析读书笔记--第四章--序列式容器

    1.什么是序列式容器?什么是关联式容器? 书上给出的解释是,序列式容器中的元素是可序的(可理解为可以按序索引,不管这个索引是像数组一样的随机索引,还是像链表一样的顺序索引),但是元素值在索引顺序的方向 ...

  8. Java集合源码分析(三)Vevtor和Stack

    前言 前面写了一篇关于的是LinkedList的除了它的数据结构稍微有一点复杂之外,其他的都很好理解的.这一篇讲的可能大家在开发中很少去用到.但是有的时候也可能是会用到的! 注意在学习这一篇之前,需要 ...

  9. STL——序列式容器

    一.容器概述与分类 1. STL容器即是将运用最广的一些数据结构实现出来.常用的数据结构有array, list, tree, stack, queue, hash table, set, map…… ...

随机推荐

  1. 前端项目 node8升级到node16,代码升级汇总

    背景 公司的项目是vue项目,环境是node@8x版本的,最近我创建react hook的项目,发现至少需要node14才支持,打开官网才发现node都已经到16版本了.失策啊,失策.于是直接升级到最 ...

  2. Entropy Search for Information-Efficient Global Optimization

    目录 概 主要内容 的估计 的估计 Hennig P, Schuler C J. Entropy search for information-efficient global optimizatio ...

  3. RTD2171U替代方案|CS5266Typec转HDMI 4K60HZ转换芯片方案|CS5266芯片

    RTD2171U 是一款USB Type-C到HDMI转换器结合了USB Type-C输入接口和数字高清多媒体接口(HDMI)输出转换芯片.嵌入式微控制器基于工业标准8051内核.接收器端口将信道配置 ...

  4. Java EE数据持久化框架作业目录(作业笔记)

    第1章 MyBatis入门>>> 1.1.4 在Eclipse中搭建MyBatis基本开发环境 1.2.5 使用MyBatis查询所有职员信息 1.3.3 获取id值为1的角色信息. ...

  5. Java初学者作业——输入一个五位数字,计算各位数字之和并输出,运行结果为五个数字之和(实践2)

    返回本章节 返回作业目录 需求说明: 编写Java程序,输入一个五位数字,计算各位数字之和并输出,运行结果为五个数字之和. 实现思路: (1)声明变量num,用于存储用户输入的数字. (2)通过Sca ...

  6. 使用 JavaScript 中的变量、数据类型和运算符,计算出两个 number 类型的变量与一个 string 类型的变量的和,根据 string 类型处于运算符的不同位置得到不同的结果

    查看本章节 查看作业目录 需求说明: 使用 JavaScript 中的变量.数据类型和运算符,计算出两个 number 类型的变量与一个 string 类型的变量的和,根据 string 类型处于运算 ...

  7. Kafka集群安装Version2.10

    Kafka集群安装,基于版本2.10, 使用kafka_2.10-0.10.1.0.tgz安装包. 1.安装规划 Storm集群模式,安装到下面三台机器 IP Hostname 10.43.159.2 ...

  8. Swoole 中使用 Lock 实现进程间锁

    注意:不要在 lock 和 unlock 操作中间使用可能引起协程切换的 API. $lock = new Swoole\Lock(SWOOLE_MUTEX); echo "[Master] ...

  9. go.mod file not found in current directory or any parent directory; see 'go help modules'

    go的环境设置问题,执行 go env -w GO111MODULE=auto 我的环境:Windows 7, Go 1.17 D:\Apps\GOPATH\src\code.oldboyedu.co ...

  10. react中异步组件以及withRouter的使用

    什么是异步组件?简单来说就是异步加载一个组件,正常情况浏览器加载的是我们打包好的bundle.js文件,那么这个文件是集合了所有js是代码,然而我们首屏加载并不需要一次性加载所有的组件,这会造成性能的 ...