【C++】详解STL容器之一的 vector

目录

概述

迭代器

数据结构

优点和缺点

接口介绍

begin

end

rbegin

rend

resize

reseve

insert

erase

其他一些接口

模拟实现

框架

获取迭代器

深浅拷贝

赋值重载

reseve

resize

拷贝构造

构造

析构

insert

erase

其他


概述

vector是STL的容器之一。vector的底层结构类似于数组——在内存中开辟一块连续的空间。与数组不同的是vector可以动态的改变空间的大小(扩容或缩容)。

vector一般不会缩容,而是会经常的扩容——扩容的大小总比用户需要的多,这和vector的扩容机制有关。

vector不支持原地扩容,会新开辟一块更大的空间。将旧空间的值浅拷贝给新空间,然后释放旧空间。vector的空间的动态改变是有代价的,尤其是数据量特别大时,不会轻易缩容。

不同平台的扩容原则是一样的,但扩容的细节并不相同。VS下是根据旧空间的1.5倍扩容,g++是根据旧空间的2倍扩容


迭代器

vector的迭代器不需要像list的那样把普通指针进行封装,详见:http://t.csdnimg.cn/76tVf

因为vector维护的是一段线性空间,它的底层是一块连续的空间。普通的指针刚好能完成数据的随机访问,空间的遍历,数据的存取等。下面是迭代器的源码定义。

templat <class T, class Alloc = alloc>
class vector
{public:
typedef T value_type;
typedef value_type* iterator; //......
}

 T*就是vector的迭代器。如果vector存的是int类型的数据,vector的迭代器就是int*类型的指针。存的如果是string类(自定义类型)类型的数据,vector的迭代器就是string*类型的指针。

迭代器失效

扩容会引发迭代器失效。首先获取了一个空间的迭代器,然后这个空间发生了扩容,此时这个迭代器是指向旧空间的,旧空间会被归还给操作系统。这种情景下的迭代器失效可以理解为野指针问题。VS环境下会强制报错。如下图

数据结构

vector管理线性空间用了三个迭代器,如下是源代码定义

templat <class T, class Alloc = alloc>
class vector
{
//......
protected:
iterator start;
iterator finish;
iterator end_of_storage;
//......
}

start——指向空间的开头

finish——指向有效数据的下一个位置

end_of_storage——指向有效空间的下一个位置


优点和缺点

优点支持随机访问,排序消耗的时间复杂度比其他容器低

有如下代码,验证三大容器vector, list, deque,在相同数据量,相同数据,数据的顺序也相同的情况下,用vector容器的排序有多大优势。在release环境下验证

#include<vector>
#include<list>
#include<deque>
#include<algorithm>
#include<time.h>
#include<iostream>void Test()
{
//数据量int N = 100000;//十万//int N = 1000000;//百万//int N = 10000000;//千万//int N = 100000000;//一亿  std::vector<int> v; //三大容器std::list<int> l;std::deque<int>d;for (int i = N; i > 0; i--) //插入相同的数据,数据顺序也相同{int e = rand();    v.push_back(e);l.push_back(e);d.push_back(e);};clock_t begin1 = clock(); //时间函数sort(v.begin(), v.end()); clock_t end1 = clock(); clock_t begin2 = clock(); l.sort();clock_t end2 = clock(); clock_t begin3 = clock(); sort(d.begin(), d.end());    clock_t end3 = clock(); printf("vector的用时是%d毫秒\n", end1 - begin1); printf("list的用时是%d毫秒\n", end2 - begin2); printf("deque的用时是%d毫秒\n", end3 - begin3);}int main()
{Test();return 0;
}

结果展示

即使排了一亿个数据,快排加vector容器也只用了4秒。list的底层是链表用了将近两分钟,效率低下。deque是一个类似于vector和list的结合体的容器,用了20秒。vector最引以为豪的优势——排序的效率高。原因在于vector的底层是连续的空间,支持随机访问,只需O(1)复杂度便可访问任意位置的数据。

在空间足够的情况下,尾部插入,尾部删除的效率为O(1)

缺点头部插入,头部删除,随机位置插入,随机位置删除,效率为O(N)。原因很简单:这些操作都需要挪动数据。如果数据量大,并且要频繁的头插头删,这便是堪比一个O(N^2)的算法。在标准库的接口中,并没有直接给头插,头删的接口。


接口介绍

begin

获取第一个数据位置的迭代器或const迭代器

end

获取最后一个有效数据的下一个位置的迭代器或const迭代器

rbegin

获取最后一个有效数据的迭代器或const迭代器

rend

获取第一个有效数据的前一个位置的迭代器

resize

改变vector容器有效数据的个数,改变为n个数据。如果n小于有效空间,删数据。n大于有效空间,扩容,并把多余的有效数据初始化为val

reseve

改变容器的有效空间。n小于有效空间时,不做处理。n大于有效空间时,把空间开至n或更大。

insert

在迭代器position之前插入val

在迭代器position之前插入n个val
在迭代器position之前插入迭代器区间first到last的元素

erase

删除迭代器position位置的元素,或删除迭代器区间first到last的元素

其他一些接口

size
获取数据个数
capacity获取容量大小
empty判断是否为空
push_back
尾插
pop_back 尾删
operator[] 
 像数组一样访问

模拟实现

框架

namespace bit
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;
private:iterator _start = nullptr;iterator _finish = nullptr;iterator _endofstorage = nullptr;};
}

获取迭代器

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

深浅拷贝

过vector容器存的是自定义类型,它们的数据可能会指向某一块空间。当我们想要新的vector容器拷贝旧的vector容器数据时,新的vector容器是否要额外开空间储存自定义类型指向的空间的数据,便涉及深浅拷贝问题。

如下示意图

上文已经提到扩容时是浅拷贝,当 vector 需要扩容时,它会分配一个新的更大的内存块,然后将原来的元素拷贝到新的内存中,并释放原来的内存。这确保了在扩容时,原有元素的地址不会改变,从而避免了深拷贝的开销

而在拷贝构造函数和赋值运算符中,vector 会执行深拷贝,即它会复制其中的每个元素,而不是简单地复制指向内存的指针。这样,每个vector 对象都有自己独立的内存存储其元素,互不影响,也避免了浅拷贝可能带来的问题。

有了上述了解,模拟实现一下赋值重载

赋值重载

现在写法

void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_endofstorage, v._endofstorage);}vector<T>& operator=(vector<T> v){swap(v);return *this;}

reseve

void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start){//memcpy(tmp, _start, sizeof(T) * sz);for (size_t i = 0; i < sz; i++){tmp[i] = _start[i];}delete[] _start;}_start = tmp;_finish = _start + sz;_endofstorage = _start + n;}}

resize

void resize(size_t n, const T& val = T()){if (n < size()){_finish = _start + n;}else{reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}}

拷贝构造

vector(const vector<T>& v){_start = new T[v.capacity()];//memcpy(_start, v._start, sizeof(T)*v.size());for (size_t i = 0; i < v.size(); i++){_start[i] = v._start[i]; //赋值重载}_finish = _start + v.size();_endofstorage = _start + v.capacity();}

构造

vector(size_t n, const T& val = T()){resize(n, val);}

析构

~vector(){if (_start){delete[] _start;_start = _finish = _endofstorage = nullptr;}}

insert

iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (_finish == _endofstorage){size_t len = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);// 解决pos迭代器失效问题pos = _start + len;}iterator 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;}

其他

void push_back(const T& x){insert(end(), x);}void pop_back(){erase(--end());}size_t capacity() const{return _endofstorage - _start;}size_t size() const{return _finish - _start;}T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}

本篇内容就到这里啦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/679617.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

漏扫神器Invicti V2024.4.0专业版

前言 Invicti Professional是Invicti Security公司推出的一个产品&#xff0c;它是一种高级的网络安全扫描工具。Invicti Professional旨在帮助组织发现和修复其网络系统中的潜在安全漏洞和弱点。它提供了全面的漏洞扫描功能&#xff0c;包括Web应用程序和网络基础设施的漏洞扫…

如何设置cPanel的自动备份

近期我们购买了Hostease美国VPS云主机产品&#xff0c;由于需要设置服务器的自动备份&#xff0c;我们向Hostease技术团队进行了咨询&#xff0c;他们提到VPS云主机的cPanel面板包含自动备份功能&#xff0c;下面我们就介绍如何进行自动备份的设置。 首先你需要登录到WHM面板&…

含义:理财风险等级R1、R2、R3、R4、R5

理财风险等级R1、R2、R3代表什么&#xff0c;为什么R1不保本&#xff0c;R2可能亏损 不尔聊投资https://author.baidu.com/home?frombjh_article&app_id1704141696580953 我们购买理财产品的时候&#xff0c;首先都会看到相关产品的风险等级。风险等级约定俗成有5级&…

Object类

Object类 概念&#xff1a;Object类是所有类的父类&#xff0c;也就是说任何一个类在定义时候如果没有明确的指定继承一个父类的话&#xff0c;那么它就都默认继承Object类&#xff0c;因此Object类被称为所有类的父类&#xff0c;也叫做基类/超类。 常用方法 方法类型描述eq…

基于 llama2 的提示词工程案例2

优化大型语言模型&#xff08;LLMs&#xff09; 优化大型语言模型&#xff08;LLMs&#xff09;中的提示词&#xff08;prompts&#xff09;是提高模型性能和输出相关性的重要手段。以下是一些优化提示词的方向&#xff1a; 明确性&#xff1a;确保提示词清晰明确&#xff0c;…

javaWeb入门(自用)

1. vue学习 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><script src"https://unpkg.com/vue2"></script> </head> <body><div id"…

linux上如何排查JVM内存过高?

怎么排查JVM内存过高&#xff1f; 前言&#xff1a; 想必工作一两年以后的同学都会逐渐面临到&#xff0c;jvm等问题&#xff0c;但是可能苦于无法熟练的使用一些工具&#xff1b;本文将介绍几个比较常用分析工具的使用方法&#xff0c;带着大家一步步定位分析问题。 1、top 查…

【大模型】LLaMA-1 模型介绍

文章目录 一、背景介绍二、模型介绍2.1 模型结构2.2 模型超参数2.3 SwiGLU 三、代码分析3.1 模型结构代码3.2 FairScale库介绍 四、LLaMA家族模型4.1 Alpaca4.2 Vicuna4.3 Koala(考拉)4.4 Baize (白泽)4.5 Luotuo (骆驼&#xff0c;Chinese)4.6 其他 参考资料 LLaMA&#xff08…

05-08 周三 FastBuild FastAPI 引入并发支持和全局捕获异常

时间版本修改人描述2024年5月8日20:41:03V0.1宋全恒新建文档 简介 由于FastBuild之前花费了大概5天的时间优化&#xff0c;但最近重新部署&#xff0c;又发现了一些问题&#xff0c;就很痛苦&#xff0c;五一之后&#xff0c;自己又花了三天的时间系统的进行了优化。 上一波优…

历代著名画家作品赏析-东晋顾恺之

中国历史朝代顺序为&#xff1a;夏朝、商朝、西周、东周、秦朝、西楚、西汉、新朝、玄汉、东汉、三国、曹魏、蜀汉、孙吴、西晋、东晋、十六国、南朝、刘宋、南齐、南梁、南陈、北朝、北魏、东魏、北齐、西魏、北周、隋&#xff0c;唐宋元明清&#xff0c;近代。 一、东晋著名…

qt开发解压缩zip文件实现

作者开发环境&#xff1a;Qt5.8 &#xff0c;win10 总体思路&#xff1a;首先我们编译zip源码&#xff0c;生成zip的动态库&#xff1b;然后再编译quazip源码&#xff0c;得到quazip的动态库&#xff1b;最后在我们的程序中去调用。 详细步骤&#xff1a; 1、编译zlib zlib…

sourceTree push失败

新电脑选择commit and push&#xff0c;报错了&#xff0c;不过commit成功&#xff0c;只不过push失败了。 原因是这个&#xff0c;PuTTYs cache and carry on connecting. 这里的ssh选择的是 PuTTY/Plink&#xff0c;本地没有这个ssh密钥&#xff0c;改换成openSSH&#xff…