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

C++ vector

目录

一.Member functions

(1).constructor,destructor,operator=

 (2).Iterators

(3).Capacity 

 (4)Element access

(5)Modifiers

二.vector迭代器失效问题 

1.引起迭代器失效的操作 

2.删除指定位置的元素-erase

3.Linux下,g++编译器对迭代器的失效检查并不是很严格。

4.string中的迭代器失效


vector是C++标准模版库中的一种动态的数组容器,它是最常用的容器之一,它提供类似数组的接口,但是它的大小是可以改变的,可以看作大小可变的一种数组。

vector的使用首先需要包含头文件

#include<vector>

所参考文档: 

vector 

一.Member functions

(1).constructor,destructor,operator=

1.constructor构造函数 

在C++98中,构造函数一共有四种。 

default (1)	
explicit vector (const allocator_type& alloc = allocator_type());
fill (2)	
explicit vector (size_type n, const value_type& val = value_type(),const allocator_type& alloc = allocator_type());
range (3)	
template <class InputIterator>vector (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());
copy (4)	
vector (const vector& x);

a.default默认构造

这种构造方式可以直接创建一个空的vector容器,不存放任何数据,至于参数中的 const allocator_type& alloc = allocator_type(),对于初学者来说可以先不了解。

b.填充

用n个value_type()类型的数据来初始化这个vector容器。至于这个value_type(),就是vector中存放的元素类型。

vector<int> v(4,100);

 比如上面这个vector存放的是int类型的变量,它的value_type()就是int.这个vector中就存放了4个100.

c.迭代器构造

第三个构造函数就是利用迭代器构造

	vector<int> first;                                // empty vector of intsvector<int> second(4, 100);                       // four ints with value 100vector<int> third(second.begin(), second.end());  // iterating through second

这里的迭代器可以类似理解为string中的迭代器,二者都是随机迭代器。 

这种方式也可以使用数组指针来构造

	int myints[] = { 16,2,77,29 };vector<int> fifth(myints, myints + sizeof(myints) / sizeof(int));cout << "The contents of fifth are:";for (vector<int>::iterator it = fifth.begin(); it != fifth.end(); ++it)cout << ' ' << *it;cout << '\n';

运行上面的代码输出: 

 The contents of fifth are: 16 2 77 29

不难 发现即使使用数组指针也可以完成构造。

注意迭代器的类型是:

vector<int>::iterator it = v.begin();
vector<int>::const_iterator it = v.begin();

 上面是普通迭代器,下面的是const类型的迭代器。

对于const对象要使用const迭代器 

d.拷贝构造 

	vector<int> first;                                // empty vector of intsvector<int> second(4, 100);                       // four ints with value 100vector<int> third(second.begin(), second.end());  // iterating through secondvector<int> fourth(third);                       // a copy of third

第四种方式就是拷贝构造 

 用一个已经存在的vector容器初始化另一个要创建的vector。

e. C++11提供的新的构造函数

initializer list (6)	
vector (initializer_list<value_type> il,const allocator_type& alloc = allocator_type());

 initializer list是一个用于初始化C++容器或自定义数据类型的语法结构。它是一种用花括号{}括起来的列表形式,用于在创建对象时指定初始值,也就是类似数组的形式。

	int arr[5] = { 1,2,3,4,5 };vector<int> v({1,2,3,4,5});

 这样的初始化方式也比较方便。

2.destructor析构函数

这个函数不需要我们多关注,这函数在程序结束或参数销毁的时候会自动调用。

3.operator=

赋值重载,用一个已经存在的vector对象赋值给另一个已经存在的vector对象

copy (1)	
vector& operator= (const vector& x);initializer list (2)	
vector& operator= (initializer_list<value_type> il);

 (2).Iterators

 1.begin()和end(),rbegin(),rend()

对于这几个迭代器来说,上一篇文章已经讲过都是类似的。

begin指向第一个数据的位置,end指向最后一个数据下一个位置。

而rbegin()指向的最后一个数据的位置,rend()指向第一个数据的前一个位置。

 如果位置上的数据不存在,那么这个函数不应该被使用。

	// 使用push_back插入4个数据vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);v.push_back(4);// 使用迭代器进行遍历打印vector<int>::iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;// 使用迭代器进行修改it = v.begin();while (it != v.end()){*it *= 2;++it;}// 使用反向迭代器进行遍历再打印// vector<int>::reverse_iterator rit = v.rbegin();auto rit = v.rbegin();while (rit != v.rend()){cout << *rit << " ";++rit;}cout << endl;

push_back跟string中的push_back类似都是尾插一个数据。 

2.const迭代器 

如果加上一个c,比如cbegin,这种就是const对象的迭代器,用法都是类似的,只不过不能修改数据而已,不过多介绍。 

(3).Capacity 

vector这个容器的底层逻辑大概是这样的:

template<class T>
class vector
{
private:T* _arr;int _size;int _capacity;
};

1.size

这个函数就是返回vector容器存放数据的个数。

2.max_size

这个函数返回的是在该环境下,vector容器可以存放的数据个数的最大值,这个取决于已知系统和库限制的。不是很常用这个函数。

3.resize

这个函数有两种使用的方式

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

改变这个容器的size值,使它可以容纳n个元素。

如果这个n值小于当前size值,那么这个容器就会只保留前n个元素,抛弃后面多余的元素。

如果这个n值大于当前size值,那么这个容器就会填充值,直到存满n个元素,如果指定了参数,也就是第二种情况,就会用第二个参数填充。如果没有指定就会使用该类型的默认构造来进行初始化再填充。

	vector<int> v({1,2,3,4,5});v.resize(10);for (auto e : v){cout << e << " ";}

 对于存放int类型的数据来说就会存放数据0。

如果这个n值甚至还大于capacity了那么就会扩容。

4.capacity

返回所分配的存储空间的容量大小,当空间不够的时候,是会自动扩容的。

5.empty

判断这个vector对象是不是空的,如果是空返回真,反之返回假。

6.reserve

void reserve (size_type n);

要求capacity改变。使这个对象至少能存储n个数据。

这个函数最主要的功能是如果你可以确定大概要存多少个数据,那么可以一次性扩完,避免在使用的过程中反复扩容

如果n大于当前vector对象的capacity,就会扩容至少大于或等于n.不同的环境下不同。

如果小于当前vector对象的capacity,就不会改变。

这个函数也是不会对vector的存放的数据做任何影响。

 (4)Element access

1.operator[]

      reference operator[] (size_type n);

 这个跟数组的方括号使用没有什么区别 ,都是访问下标为n的数据。

2.at

      reference at (size_type n);

 这个函数的功能和operator[],一摸一样的,只不过at会做边界检查,如果越界就会抛异常。

3.front和back

这两个分别返回第一个数据和最后一个数据的引用(注意不是迭代器,要区别begin和end)。

4.data

返回一个指向向量内部用于存储其自身元素的内存数组的直接指针。 

因为向量中的元素保证以与向量表示的顺序相同的顺序存储在连续的存储位置中,所以检索到的指针可以偏移以访问数组中的任何元素。

(5)Modifiers

1.assign

对vector对象分配内容,替换当前的内容。 

range (1)	
template <class InputIterator>void assign (InputIterator first, InputIterator last);
fill (2)	
void assign (size_type n, const value_type& val);
initializer list (3)	
void assign (initializer_list<value_type> il);

第一个就是迭代器,第二个是用n个val,第三个用花括号{ } 

 2.push_back

void push_back (const value_type& val);

在vector对象中尾插一个数据val.

3.pop_back

尾删一个数据

void pop_back();

如果当前vector对象是空,那么会引发未定义行为,不同的情况下结果不同。 

4.insert 

在position(迭代器)的位置前插入数据 

single element (1)	
iterator insert (const_iterator position, const value_type& val);
fill (2)	
iterator insert (const_iterator position, size_type n, const value_type& val);
range (3)	
template <class InputIterator>
iterator insert (const_iterator position, InputIterator first, InputIterator last);
initializer list (5)	
iterator insert (const_iterator position, initializer_list<value_type> il);

第一个是在 pos位置前插入一个数据val

第二个是在 pos位置前插入n个数据val

第三个是在 pos位置前插入一个迭代器区间

第四个是在 pos位置前插入一个{ }转化而来的vector对象

并且它的返回值是一个指向新插入的元素的第一个的迭代器 

 5.erase

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

 删除pos迭代器指向的那个位置的数据,也可以删除一段迭代器区间(注意这里是前闭后开的,last是不会被删的)。

它返回的迭代器是指向被删除的元素的接下来的一个数据。所以之前的迭代器会失效就不能用了。

6.clear

清空整个vector对象,使其变成一个空的vector对象,也就是size变成0. 

二.vector迭代器失效问题 

迭代器的主要作用是让算法能够不用关心底层数据结构,其底层实际就是一个指针,只不过对指针进行了封装,比如:vector的迭代器就是原生态指针T*.因此迭代器失效,实际就是迭代器底层对应指针说指向的空间被销毁了,而使用一个已经被释放的空间,就可能会使程序崩溃。

1.引起迭代器失效的操作 

能导致底层空间改变的操作都有可能是原因:resize,reserve,insert,assign,push_back等

vector<int> v{1,2,3,4,5};
 将有效元素个数增加到100个,多出的位置使用8填充,操作期间底层会扩容v.resize(100, 8);reserve的作用就是改变扩容大小但不改变有效元素个数,操作期间可能会引起底层容量改变v.reserve(100);插入元素期间,可能会引起扩容,而导致原空间被释放v.insert(v.begin(), 0);v.push_back(8);给vector重新赋值,可能会引起底层容量改变v.assign(100, 8);

以上的所有操作都有可能导致vector扩容,也就是说vector底层原理旧空间已经被释放掉了,而在打印的时候,it还是原来的值(比如以下使用it如果有使迭代器失效的操作),就会引起代码运行时发生崩溃。

while(it != v.end()){cout<< *it << " " ;++it;}cout<<endl;

解决迭代器失效的方式也很简单:我们只需要不再使用原来的迭代器,对it重新赋值即可。

2.删除指定位置的元素-erase

	int a[] = { 1, 2, 3, 4 };vector<int> v(a, a + sizeof(a) / sizeof(int));// 使用find查找3所在位置的iteratorvector<int>::iterator pos = find(v.begin(), v.end(), 3);// 删除pos位置的数据,导致pos迭代器失效。v.erase(pos);cout << *pos << endl; // 此处会导致非法访问

首先这段代码是 找到数字为3位置的迭代器,然后通过迭代器删掉这个位置的数据。

看图我们可以简单知道这个过程是删除3后将4放到3原本的位置,pos看似还是指向原来的位置,并没有改变,但是我们并不知道底层有没有发生变化,此时的迭代器是失效的,就算底层没有改变,有些编译器也会严格检查,只要删除了后,就不让你使用这个位置的迭代器了,必须要重新对这个迭代器进行复制。 

 这里就要讲到上面学的erase了,这个函数在删除完数据后,会返回下一个位置的迭代器,也就是返回指向数据4的一个迭代器,如果是删除某个区间,那就是这个区间紧跟着的那个数据的迭代器。

所以在使用erase这个函数时,应该接受返回值。

pos = v.erase(pos);

只有这样才能避免迭代器失效的问题。

接下来再看一个迭代器失效的问题: 以下两段代码均是删除vector中偶数的程序,哪一个是对的?

//代码一:int main(){vector<int> v{ 1, 2, 3, 4 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0)v.erase(it);++it;}return 0;}
//代码二:int main(){vector<int> v{ 1, 2, 3, 4 };auto it = v.begin();while (it != v.end()){if (*it % 2 == 0)it = v.erase(it);else++it;}return 0;}

很明显第二个是对的,第一个的问题就出在,使用完erase函数后没有更新这个迭代器,继续进行++操作,此时就不对了,会导致程序崩溃。

3.Linux下,g++编译器对迭代器的失效检查并不是很严格。

int main(){vector<int> v{ 1,2,3,4,5 };for (size_t i = 0; i < v.size(); ++i)cout << v[i] << " ";cout << endl;auto it = v.begin();cout << "扩容之前,vector的容量为: " << v.capacity() << endl;// 通过reserve将底层空间设置为100,目的是为了让vector的迭代器失效    v.reserve(100);cout << "扩容之后,vector的容量为: " << v.capacity() << endl;while (it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;}

经过上述reserve之后,it迭代器肯定会失效,在vs下程序就直接崩溃了,但是linux下不会,虽然可能运行,但是输出的结果是不对的。

程序输出:

1 2 3 4 5

扩容之前,vector的容量为: 5

扩容之后,vector的容量为: 100

0 2 3 4 5 409 1 2 3 4 5

而这段程序在vs2022上一定还是运行不了的,vs的处理更加极端,直接程序报错了。所以虽然这个能在g++上运行,但是结果也不是我们所期望的。

int main()
{vector<int> v{1,2,3,4,5};vector<int>::iterator it = find(v.begin(), v.end(), 3);v.erase(it);cout << *it << endl;while(it != v.end()){cout << *it << " ";++it;}cout << endl;return 0;
}

/ 2. erase删除任意位置代码后,linux下迭代器并没有失效 // 因为空间还是原来的空间,后序元素往前搬移了,it的位置还是有效的

程序可以正常运行,并打印: 4 4 5

上述例子中可以看到:SGI STL中,迭代器失效后,代码并不一定会崩溃,但是运行 结果肯定不对,如果it不在begin和end范围内,肯定会崩溃的。 

4.string中的迭代器失效

	string s("hello");auto it = s.begin();//s.resize(20, '!');while (it != s.end()){cout << *it;++it;}cout << endl;it = s.begin();while (it != s.end()){it = s.erase(it);// s.erase(it);  ++it;}

如果将第一处注释的代码加入就会发生一个问题:

代码会崩溃,因为resize到20会string会进行扩容,扩容之后,it指向之前旧空间已经被释放了,该迭代器就失效了,接下来打印时,再访问it指向的空间程序就会崩溃 。

而第二处注释:

按照下面方式写,运行时程序会崩溃,因为erase(it)之后, it位置的迭代器就失效了

综上所述:解决迭代器失效的最简单方法就是,重新给迭代器进行赋值。 


http://www.mrgr.cn/news/60630.html

相关文章:

  • 《一人公司:失业潮中的高新技术工作者》读书笔记
  • 如何将markdown文件转换为pdf
  • 哈希表——unordered_set和unordered_map的封装
  • 入侵检测算法平台部署LiteAIServer视频智能分析平台行人入侵检测算法
  • 【spring cloud】深入探讨 集群,分布式,微服务
  • 如何找到适合的工程管理系统?9款对比
  • 西门子S7-200 SMART 多泵轮换功能库案例下载
  • 超子物联网HAL库笔记:准备篇
  • TypeScript 接口知识点详解
  • 多态的体现
  • 三维测量与建模笔记 - 2.1 坐标转换基础
  • redis学习路线和内容
  • 亿赛通与Ping32:数据安全领域的两大巨擘对比
  • 二十四、Python基础语法(变量进阶)
  • 计算机网络803-(5)运输层
  • 常见大气校正模型及6S模型安装部署【20241028】
  • 仓颉编程语言一
  • 011:软件卸载工具TotalUninstall安装教程
  • 重写(外壳不变)
  • CSS3新增长度单位
  • Python自动化测试中的Mock与单元测试实战
  • 基于vue、VantUI、django的程序设计
  • 【HeadFirst 设计模式】设计模式总结与C++案例
  • 外包干了30天,技术明显退步
  • 内网穿透之网络层ICMP隧道
  • 基于SSM的宠物猫狗商业系统设计与实现