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

vector类(一)

vector的介绍

    vector是表示可变大小数组的序列容器。就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理,有点像顺序表。

    当vector需要重新分配大小时,其做法是,分配一个新的数组,然后将全部元素移到这个数组当中,并释放原来的数组空间。

    vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因此存储空间比实际需要的存储空间一般更大。

    与其他动态序列容器相比(deque, list and forward_list),vector 的访问元素更加高效,在其末尾添加和删除元素的时候效率很高,但是对于不在末尾的操作,效率就很低。

vector的定义

有四大类构造函数:

(constructor)构造函数声明接口说明
vector()无参构造
vector(size_type n, const value_type& val = value_type())构造并初始化n个val
vector (const vector& x); 拷贝构造
vector (InputIterator first, InputIterator last);使用迭代器进行初始化构造

例1:构造一个某类型的空容器。

vector<int> v1;  //构造int类型的空容器

例2:构造一个含有n个val的某类型容器。

vector<int> v2(10, 2);  //构造含有10个2的int类型容器

例3: 拷贝构造某类型容器的复制品。

vector<int> v3(v2); //拷贝构造int类型的v2容器的复制品

例4:使用迭代器拷贝构造某一段内容。

vector<int> v4(v2.begin(), v2.end()); //使用迭代器拷贝构造v2容器的某一段内容

注意:该方式也可用于拷贝其他容器的某一段内容。

string s("hello world");
vector<char> v5(s.begin(), s.end()); //拷贝构造string对象的某一段内容

vector iterator 的使用:

    vector 当中的迭代器和string 当中迭代器的时候使用一样的,主要使用的有以下几种:

begin +
end
获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置
的iterator/const_iterator
rbegin + rend获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的
reverse_iterator

    还是和string一样,有正向和反向的迭代器。

    关于 vector当中迭代器的使用可以参考string当中迭代器的使用。

vector容量空间

 接口也是string一样的:

容量空间接口说明
size获取有效个数
capacity获取最大容量
empty判断是否为空
resize改变vector的size,初始化
reserve 改变vector的capacity

复习:

reserve:

  1. 当所给值大于容器当前的capacity时,将capacity扩大到该值。
  2. 当所给值小于容器当前的capacity时,什么也不做。

resize:

  1. 当所给值大于容器当前的size时,将size扩大到该值,扩大的元素为第二个所给值,若未给出,则默认为0。
  2. 当所给值小于容器当前的size时,将size缩小到该值。

注意点:

  • capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
  • reserve只负责开辟空间。如果确定知道需要用多少空间,reserve可以缓解vector增容的代价缺陷问题。
  • resize在开空间的同时还会进行初始化,影响size。

vs下测试扩容:

void TestVectorExpand()
{size_t sz;vector<int> v;sz = v.capacity();cout << "making v grow:\n";for (int i = 0; i < 100; ++i){v.push_back(i);if (sz != v.capacity()){sz = v.capacity();cout << "capacity changed: " << sz << '\n';}}
}

 VS下输出:

我们还要注意,不要误用reserve(),例如如下代码:

	vector<int> v1;v1.reserve(10);for (size_t i = 0; i < 10; i++){v1[i] = i;}

运行报错,断言:

    reserve() 修改的是 _capacity容量,并没有修改有效字符长度,上述代码的有效字符长度还是0。类似于你可以在银行存10万(代码原来的容量),但是你存款0元(有效字符长度为0),后来你可以在银行存100万(reserve修改更大容量),但是你用不了钱啊,你用钱是犯法的(调用v1[i] = i 是不允许的),因为你的存款一直没变啊,一直都是0(有效字符长度为0),你要把你的存款提上去才能用啊。这时候推荐使用resize() ,因为resize() 也会进行扩容,而且会修改 _size。

vector 增删查改

vector增删查改接口说明
push_back尾插
pop_back尾删
find查找 (注意这个是算法模块实现,不是vector的成员接口)
insert在position之前插入val
erase删除position位置的数据
swap交换两个vector的数据空间
operator[]像数组一样访问

    我们注意到,修改当中,vector并没有提供头插头删,因为头插和头删是需要挪动数据的,这样的话效率不高,不是vector的适用范围。但是也是可以头插头删的,使用 insert() 和erase() 函数就可以了(如下例子)。

	int a[] = { 16,86,64,42 };vector<int> v1(a, a + 4);for (auto val : v1){cout << val << " ";}//16 86 64 42v1.erase(v1.begin());for (auto val : v1){cout << val << " ";}//86 64 42v1.insert(v1.begin(), 1000);for (auto val : v1){cout << val << " ";}//1000 86 64 42

    我们还发现在vector类当中是没有实现 find() 函数的,但是string类当中就实现了find() 函数。其实 find() 函数在STL当中是算法模块(<algorithm>)当中已经实现了模版,我们可以直接使用其中的 find() 函数:

	int a[] = { 16,86,64,42 };vector<int> v1(a, a + 4);for (auto val : v1){cout << val << " ";}//16 86 64 42vector<int>::iterator pos = find(v1.begin(), v1.end(), 86);if (pos != v1.end()){v1.erase(pos);}for (auto val : v1){cout << val << " ";}//16 64 42

    为什么在string当中就实现了find()函数,而在vector当中就没有实现呢?其实是因为string当中的find() 不仅要求查找字符,还要要求查找字符子串,而vector当中就没有这么多功能,这需要遍历查找即可。

迭代器失效

    首先我们来看下面这个代码关键部分,是自己实现的 vector的 insert() 函数:

		void insert(iterator pos, const T& x) {  //iterator表示地址assert(pos >= _start && pos <= _finish); //插入在合理范围内// 扩容if (_finish == _endOfStorage) //判断扩容{int newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}// 把数据往后挪一位size_t end = finish - 1;while (pos <= end){*(end + 1) = *end; //问题所在end--;}*pos = x;++finish;}

vector 的内部指针

  • _start:指向内存块的起始位置
  • _finish:指向最后一个元素的下一个位置(即当前元素数量)
  • _endOfStorage:指向内存块的末尾(即当前容量)

上述代码有问题,示例场景

假设我们有一个包含 3 个元素的 vector,容量为 3:

步骤1,插入前的状态:

步骤2,扩容操作:

当 _finish == _endOfStorage时,触发扩容:

int newcapacity = capacity() == 0 ? 4 : capacity() * 2;
reserve(newcapacity);

扩容后:

步骤3,问题代码执行:

size_t end = finish - 1; // end = 0x2008(新内存中C的位置)
while (pos <= end) // 比较 0x1004(旧地址) 和 0x2008(新地址)
{*(end + 1) = *end; // 问题所在end--;
}

比较不同内存块的指针是​​未定义行为​。当 end递减到小于pos时,循环条件pos <= end可能仍然成立,程序会继续尝试访问已释放的内存!

修正代码:

		void insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);// 扩容if (_finish == _endOfStorage){size_t len = pos - _start; //保存偏移量int newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);pos = _start + len; //起点+偏移量 找到pos}// 把数据往后挪一位iterator end = _finish - 1;while (pos <= end){*(end + 1) = *end;end--;}*pos = x;++_finish;}

         其实上述还是没有完全解决迭代器失效,上述只是修改了内部的迭代器失效,如果迭代器在外部也是可能会失效的,如下例子:

        auto pos = v.begin() + 3;   // 获取迭代器v.insert(pos, 100);          // 插入元素(可能扩容)*pos += 10;                 // 危险!迭代器已失效

        上述 insert() 函数扩容之后,使用*pos += 10; 是错误的。我们在函数内使用完pos,pos是传值返回,在函数内部的修改不会影响到函数外pos的值。

        也不能用 “&”传参,因为如果使用的是 begin() 类似的迭代器,begin()返回 iterator的对象,返回的是值,并不是引用,所以在两者传参的时候会产生临时对象,临时对象具有常性,这里就涉及到权限的放大,就会编译报错。

        外部的迭代器失效问题 insert() 函数无法解决,所以在使用 insert()函数的时候要格外注意。

    所以,我们为pos提供一个返回值,来解决外部的迭代器失效问题。

    最终函数:

    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;}


    文章转载自:

    http://rd66Tdju.gmdtk.cn
    http://0lvLJGdz.gmdtk.cn
    http://ylv1ejRl.gmdtk.cn
    http://IsKqwPW0.gmdtk.cn
    http://WLSFaghU.gmdtk.cn
    http://PL87x16m.gmdtk.cn
    http://nGde9eGS.gmdtk.cn
    http://EvPNzEOh.gmdtk.cn
    http://c2HkhsXi.gmdtk.cn
    http://GMpEnSoZ.gmdtk.cn
    http://xQn4DLlK.gmdtk.cn
    http://62If1gG7.gmdtk.cn
    http://BdskVCWA.gmdtk.cn
    http://3xy8tvN3.gmdtk.cn
    http://KzpK6tEl.gmdtk.cn
    http://gKOY5Ri8.gmdtk.cn
    http://qaSi35b2.gmdtk.cn
    http://9zTFFmd1.gmdtk.cn
    http://2Lb5hq1s.gmdtk.cn
    http://b9GzXTOT.gmdtk.cn
    http://K9dwWCDH.gmdtk.cn
    http://ZQ3w8wJz.gmdtk.cn
    http://GymySwxY.gmdtk.cn
    http://UJJkInsG.gmdtk.cn
    http://N1jhD3jd.gmdtk.cn
    http://1nHvpkU0.gmdtk.cn
    http://Mk4Vbz4t.gmdtk.cn
    http://8B91XmAC.gmdtk.cn
    http://3MkTfANw.gmdtk.cn
    http://X27LsfQw.gmdtk.cn
    http://www.dtcms.com/a/371757.html

    相关文章:

  1. OpenLayers常用控件 -- 章节八:地图动画控件教程
  2. 在 CI/CD 管道中集成人工智能 (AI)
  3. 开源项目MusicGen技术详解
  4. 【面向对象编程——多继承】
  5. 算法题-哈希表01
  6. 云平台面试内容(二)
  7. Carlsson_HEAL-SWIN_A_Vision_Transformer_On_The_Sphere_CVPR_2024_paper_analysis
  8. 微服务的保护方式以及Sentinel详解
  9. 【jenkins】--安装部署
  10. Vue 路由传参的四种方式
  11. HTML 表格基础
  12. CD76.【C++ Dev】AVL的模拟实现(1) 以左单旋为切口,分析旋转规律
  13. 中国计算机发展史
  14. LeetCode刷题记录----20.有效的括号(Easy)
  15. 从voice和练习发声谈起
  16. 5.python——数字
  17. 数据化运营的工作流程
  18. llama_factory 安装以及大模型微调
  19. Linux | i.MX6ULL 搭建 Web 服务器(第二十章)
  20. 量子電腦組裝之三
  21. 适配器详细
  22. GD32自学笔记:5.定时器中断
  23. 前端三件套简单学习:HTML篇1
  24. Android --- SystemUI 导入Android Studio及debug
  25. 服务器为什么会选择暴雨?
  26. Spring Boot + Apache Tika 从文件或文件流中提取文本内容
  27. day26|学习前端之算法学习
  28. 数据结构之二叉树(2)
  29. Mac设置中的安全性缺少“任何来源”
  30. 样式化你的 Next.js 应用:CSS 模块、Tailwind CSS 和全局样式