当前位置: 首页 > news >正文

C++STL系列之vector


前言

vector是变长数组,有点像数据结构中的顺序表,它和list也是经常被拿出作对比的, vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小,如果扩容,因为要开一个新数组把旧元素挪过来,消耗很大,所以扩容一般是扩二倍或者1.5倍。

vector的模拟也可以像string一样来实现,但是SGI下源码用的是三个迭代器,后面会具体讲。


一、vector类以及常用接口和函数

1.构造函数

和string基本一样

2.迭代器

发现也一样,因为双向迭代器在C++11后都是四个迭代器,具体使用也没啥说的,简单示范一下,遍历容器:

vector<int> v = {1,2,3,4};
vector<int>::iterator it = v.begin();
while (it != v.end())
{cout << *it << ' ';it++;
}

有模板以后,类名不再是类型名,vector< T >才是类型名

3.容量方面

在这里插入图片描述
和string基本一样。
vs下是1.5倍扩容,而g++下是2倍扩容,vs是PJ版本STL,g++是SGI版本STL。

4.重要函数

在这里插入图片描述
一眼瞅过去和string基本一样,这里emplace系列在C++11中已经讲完了不再提。主要改变是insert和erase,从string之后,所有的insert和erase基本上都只支持迭代器插入/删除。
在这里插入图片描述
在这里插入图片描述
返回值也是迭代器,解决迭代器失效的问题,后面谈。

5.二维数组

vector<vector<int>> vv;//这就是一个二维数组。每一维放的是vector<int>

二、迭代器失效问题

想知道这个先知道一个问题:vector的迭代器是什么样的?和string类似,

typedef T* iterator;
typedef const T* const_iterator;

先看迭代器失效的一些场景:

erase掉偶数

vector<int> v{ 1,2,3,4,5,6 };
auto it = v.begin();
while (it != v.end())
{if (*it % 2 == 0){v.erase(it);}++it;
}

在一个位置反复insert

vector<int> v{ 1,2,3,4,5,6 };
auto it = v.begin();
int n = 10;
while (n--)
{v.insert(it, 10);
}

这两份代码在vs下都崩溃了。
只要涉及到底层空间的改变都有可能会导致迭代器失效。
为什么会失效呢?
以上操作,都有可能会导致vector扩容,也就是说vector底层原理旧空间被释放掉,下一步使用时,it还使用的是释放之间的旧空间,在对it迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。
关于erase

erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效了。

Linux下检查和处理都没有vs更加严格和极端,可能不会崩溃,但是结果都是未定义的,所以我们要学会正确的使用迭代器。
解决方案:
库里的实现均带有返回值,返回最新位置的迭代器,想上面那么使用就可以拿it重新接受。

it = v.erase(it);
it = v.insert(it,30);

三、模拟实现

SGI下没有使用像string一样的 T * arr和size和capacity,他使用了三个迭代器(T*类型的三个指针),这样处理更加的方便,扩容方面,指针的计算也更有效率。
_start --充当T * arr;
_finish size()
_end_of_storage capacity();
构造:
在这里插入图片描述
push_back
在这里插入图片描述
看完这个push_back()就会明白很多,if判断是判断size等不等于capacity,++finish相当于++_size。
rerserve:
在这里插入图片描述
这个更加清晰,n是新的capacity的大小,start指向开始,finish指向tmp+size(),end_of_storage指向空间的结束。
OK,知道这些就好自己手搓了。

size和capacity

size_t capacity()const
{return _end_of_storage - _start;
}
size_t size()const
{return _finish - _start;
}

reserve

void reserve(size_t n)
{if (capacity() < n){size_t sz = size();//注意保存一下size,因为后面还需要计算finishT* tmp = new T[n];if (_start){for (size_t i = 0; i < sz; ++i){tmp[i] = _start[i];}//memcpy(tmp, _start, sizeof(T) * sz);delete[] _start;}_start = tmp;_finish = _start + sz;_end_of_storage = _start + n;}
}

迭代器:

iterator begin()
{return _start;
}
const_iterator begin()const
{return _start;
}
const_iterator end()const
{return _finish;
}
iterator end()
{return _finish;
}

insert:

后面的容器基本上都是写完insert之后复用insert,比如push_back,就是insert(end(),x);
insert的注意事项:就是扩容之后迭代器位置的改变,因为要返回新插入元素的迭代器。挪动数据这些都小菜一碟。

iterator insert(iterator pos,const T& x)
{assert(pos <= _finish && pos >= _start);if (_finish == _end_of_storage){size_t dx = pos - _start;size_t _capacity = capacity() == 0 ? 4 : capacity() * 2;reserve(_capacity);//迭代器失效,reserve需要开一个新的空间再把原来的空间释放,finish和start都转移了//过去,但是pos还在原空间,变成了野指针pos = _start + dx;}//1 6 4 8 10//1 2 6 4 8 10iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;
}

erase:

iterator erase(iterator pos)
{assert(pos >= _start && pos < _finish);iterator it = pos + 1;while (it != _finish){*(it - 1) = *it;it++;}--_finish;return pos;
}

pop_back和push_back复用insert和erase即可。

resize

void resize(size_t n, const T& val = T())
{if (n < size()){_finish = _start + n;}//_size = 4,_capacity = 7 , 6 8else{reserve(n);for (size_t i = size(); i < n; ++i){_start[i] = val;}_finish = _start + n;}
}

各种构造:

vector(size_t n, const T& val = T())
{resize(n, val);
}
vector(int n, const T& val = T())
{resize(n, val);
}
template <class InputIterator>
vector(InputIterator first, InputIterator last)
{while (first != last){push_back(*first);first++;}
}
vector(std::initializer_list<T> il)
{T* tmp = new T[il.size()];for (size_t i = 0; i < il.size(); ++i){tmp[i] = *(il.begin() + i);}_start = tmp;_end_of_storage = _finish = _start + il.size();
}
vector(const vector<T>& ot)
{T* tmp = new T[ot.size()];for (size_t i = 0; i < ot.size(); ++i){tmp[i] = ot[i];}_start = tmp;_finish = _end_of_storage = _start + ot.size();
}
vector(vector<T>&& ot):_finish(nullptr),_start(nullptr),_end_of_storage(nullptr)
{swap(ot);
}

注意:拷贝构造通常只拷贝到size处,并非拷贝整个容量。

这里你可能会疑问,为什么用n个T元素构造vector有两个版本?一个传的size_t,一个int,正常不就是size_t吗,看下面这种情况:
在这里插入图片描述
10和3都是int类型,编译器会默认走最匹配的,这里InputIterator first, InputIterator last是一个类型更加匹配导致了编译错误,所以开一个vector(int n, const T& val = T())来防止这种情况的发生。

赋值运算符重载

operator = 的现代写法(string里面类似)
就是复用拷贝构造之后swap一下

vector<T>& operator=(const vector<T>& ot)
{if (&ot == this){return *this;}vector<T> tmp(ot);swap(tmp);return *this;
}

[]

T& operator[](size_t pos)
{assert(pos < size());return _start[pos];
}
const T& operator[](size_t pos)const
{assert(pos < size());return _start[pos];
}

个人gitee

四、浅拷贝的问题

如果你移动元素用的是memcpy,此时可能一些情况下程序就会崩溃。
分析:

  1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
  2. 如果拷贝的是内置类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
    所以:如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。
    使用等于就可以,因为自定义类型会调用operator =,比如string类型就会调用operator=达到深拷贝的效果。

五、优缺点

vector主要对比的对象就是list
优点:
1.vector支持下标访问([] 和 at),O(1)拿到元素,效率高
2.尾插效率很高
3.连续内存布局使 CPU 缓存命中率高,遍历速度快。
4.内存开销较list小,因为list内部需要存指针
缺点:
1.中间插入或者删除很低效,需要挪动数据。
2.迭代器插入和删除涉及到失效的问题
3.每次扩容开销很大

总结

vector用的也不少,需要多加练习。
关于反向迭代器,这个会在stack和queue的那篇博客来讲,因为他们的思想一样
明天更新list,stack,queue

http://www.lryc.cn/news/593793.html

相关文章:

  • 尚庭公寓-----day2 业务功能实现
  • 计算机视觉:AI 的 “眼睛” 如何看懂世界?
  • 万字解析LVS集群
  • 安全事件响应分析--基础命令
  • XSS相关理解
  • 商业秘密的法律属性与保护路径探析
  • XSS漏洞学习总结
  • 基于Scrapy-Redis的分布式爬虫系统:工业级实现与深度优化
  • XSS漏洞总结
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘pillow’问题
  • 从零手写红黑树(C++实现详解)
  • 【工具变量】地级市城市包容性绿色增长数据(2011-2023年)
  • [FFmpeg] AVFormatContext、AVInputFormat、AVOutputFormat | libavformat
  • 语义熵怎么增强LLM自信心的
  • MyBatis动态SQL全解析:五大核心标签实战指南
  • IIS部署 .net项目
  • 新华三ACG身份验证实验
  • Linux操作系统之线程(三)
  • JavaScript基础语法和简单数据结构
  • 响应式单位rpx及搭配使用UI产品工具
  • Java-Lambda表达式
  • Ceph存储阈值调整:优化nearfull_ratio参数
  • Vue组件化开发小案例
  • lvs 集群技术
  • LVS技术知识详解(知识点+相关实验部署)
  • sql练习二
  • MySQL练习3
  • Linux内核设计与实现 - 第6章 内核数据结构
  • [AI风堇]基于ChatGPT3.5+科大讯飞录音转文字API+GPT-SOVITS的模拟情感实时语音对话项目
  • 一动一静皆消耗——IC设计之低功耗技术(Low Power Design)