目录
前言
下面接着来介绍C++中容器vector的使用,本文章重点介绍一些常用的接口,不是全部接口哦,完整版文档大家可以参照vector文档(建议PC端打开哦!)
一、关于数组
想必在C语言阶段是大家经常使用的数组都是静态的,就像下面这段代码一样
int a[10]={1,2,3,4,5,6,7,8,9,10};
上面就是我们平时喜欢定义的数组,实际上在C++STL库里面也存在着一个容器,array,只不过它是C++11后才提出的,它的出现主要是类比于上面这段数组!
这是它在文档中的介绍,它是一个模板类,参数是非类型模板参数,下面的解释是:它是一个固定大小的序列容器:它们按照严格的线性顺序保存特定数量的元素。说白了就是一个静态数组!
那么它和我们C语言中的静态数组有什么区别呢?其实就是对越界的处理方式不同,来看这两段代码
int a[10];//C语言中的静态数组 array<int,10> a2;//C++的 //越界访问是否可行? a[10]; a[11]=11; //可行? a2[12]; a2[12]=1;
其实将其放入编译器中C语言的a数组,没有问题正常运行,而C++的这个a2数组程序会直接崩溃!
原因:
①对于C语言的a数组,编译器对其的处理是:越界读没有问题,越界写只是一种抽查,不一定会报错!具有局限性!
②而对于C++提供的array这个容器,对于任意位置的越界读写,程序都会直接结束,它的底层实现用了assert断言!
单从这个角度,C++提供的这个容器很香喷喷,其实不然,它存在的问题就是----开空间过大,会导致栈溢出!而且还不能初始化!又不香了!所以实际中我们一般喜欢使用vector容器,因为它能解决的vector也能解决,vector能解决它不一定能解决!
二、vector的介绍
vector是表示可变大小数组的序列容器。
就像数组一样,vector也采用的连续存储空间来存储元素,也就是说它可以采用下标对元素进行访问,唯一一点与数组的不同在vector它是动态的,大小会自动的去调整
本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小 为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是 一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
与其它动态序列容器相比(deque, list and forward_list),vector在访问元素的时候更加高效,在末 尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起list和forward_list 统一的迭代器和引用更好ps:这些容器后面都会讲解 !
三、vector的使用
Ⅰ、默认成员函数
1.构造函数
vector的构造函数常见的有四个:
vector(); //无参构造 vector(size_type n, const value_type& val = value_type()); //构造并初始化n个val vector (const vector& x);//拷贝构造 vector (InputIterator first, InputIterator last);//迭代器区间构造
具体使用:
vector<int> v1;//无参 vector<int> v2(10, 1);//初始化元素为10个1 vector<int> v3(v2);//将v2的内容拷贝给v3,并初始化v3 vector<int> v4(v2.begin(), v2.begin() + 5);//迭代器区间构造
2.赋值重载
vector& operator= (const vector& x); //使用 vector<int> v2(10, 1);//初始化元素为10个1 vector<int> v5=v2;//赋值
3.析构函数
~vector();
一般这个采用自带的就行!
Ⅱ、容量
1.size()
//原型,获取数据个数
size_type size() const;
//使用
vector<int> v2(10,1);
cout << v2.size();
2.capacity()
v2.capacity();//获取容量大小
3.empty()
v2.empty();//判空
4.resize()
void resize(size_type n, value_type val = value_type());
调整size的大小,规则如下:
①如果n比当前的size小,那么就会缩小当前的size直至和n相等,其余部分删除
②如果n比当前的size大,那么就会扩充到n,扩充的部分如果没有给出具体的值,则用0代替,反之用具体的值代替!
③如果n大于当前的capacity,则会自动重新分配空间,在将原始数据拷贝到新空间!
①比size小的情况:
②比size大的情况:
5.reserve()
void reserve(size_type n);
调整capacity的大小,规则如下:
①如果n小于当前容量,什么都不做
②如果n大于当前容量,就扩充到和n一样大
Ⅲ、遍历操作
1.迭代器
-
begin() +end()(正向迭代器)
实际上和前面的string类一样的迭代器!
begin():指向的是第一个元素的位置!
iterator begin();
const_iterator begin() const;//为const对象提供的end(): 指向的是最后一个元素的下一个位置!
iterator end();
const_iterator end() const;
代码实现:
-
rbegin()+rend()(反向迭代器)
rbegin:指向最后一个元素的位置!
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
rend:指向的是第一个元素的前一个位置!
reverse_iterator rend();
const_reverse_iterator rend() const;
代码实现:
2.operator【】
有了它就能想普通数组那样通过下标去访问特定的元素了!
reference operator[] (size_type n);
const_reference operator[] (size_type n) const;
直接看代码,没啥好说的哥们!
要注意:有了它我们不仅仅是读元素,还可以进行修改与赋值!
Ⅳ、增删查改
1.push_back()
一个简单的尾插操作!
void push_back(const value_type & val);
vector<int> v; for (int i = 1; i <= 9; i++) { v.push_back(i); }
2.pop_back()
简单的尾删操作!
void pop_back();
vector<int> v; for (int i = 1; i <= 9; i++) { v.push_back(i); } v.pop_back();//尾删
3.find()
查找操作!需要注意:这个不是vector成员函数的接口,这个是算法库里面的,把它放在这里是因为它可以和下面的insert、erase操作配合使用!
它在算法库面实际上是一个模板
可以看到它的功能是:在一个迭代区间里面去寻找特定的val,如果找到这个元素,那就返回当前位置的迭代器;如果找不到,那就返回迭代区间的最后一个位置迭代器!
4.insert
插入操作:在pos位置之前插入一个元素!
可以看到它需要传入对应位置的迭代器!所以就可以配合find()去使用!
直接看操作
①特定位置前插入一个值
②特定位置前插入n个val
③在特定位置前插入一段迭代区间(左闭右开)
一定要注意区间是左闭右开的。vector的动态数组下标都是从0开始的
5.erase
删除操作:删除特定位置或者区间的值!
iterator erase(iterator position);
iterator erase(iterator first, iterator last);来吧,展示!同样也需要配合find()使用
①删除特定位置的值
②删除特定区间的值(左闭右开)
6.swap
主要是交换两个vector的数据空间!这个是vector内部的成员函数,不是那个算法库里面的全局的vector
void swap(vector & x);
直接上代码:
四、vector细节问题-迭代器失效
首先需要知道一点,迭代器的作用就是让算法不关心底层的设计实现,而行为却像指针那样进行数据的访问和操作。对于vector来说,它的迭代器的底层设计就是原生的指针T*。所以迭代器失效就是指:迭代器底层对应指针所指向的空间被销毁,而却还在使用这块已经释放的空间,最终的结果会导致程序崩溃!
Ⅰ、失效案例
1.会引起底层结构发生改变的操作!
比如:resize、reserve、insert、assgin、push_back等,因为这些操作都可能会存在扩容这一操作!
vector<int> v; for (int i = 1; i <= 9; i++) { v.push_back(i); } vector<int>::iterator it; it = find(v.begin(), v.end(), 5); v.insert(it, 100, 1); while (it != v.end()) { cout << *it << " "; ++it; }//可行?
结果如下:
很明显,程序崩溃了!原因就是insert操作导致vector扩容了,开辟了新空间,也就是说vector旧的底层空间已经释放,可是it仍然指向那块空间,在对它进行访问时,实际上就是非法访问一块已经释放的空间,那程序必然崩溃!
2.指定位置元素的删除操作--erase
vector<int> v; for (int i = 1; i <= 5; i++) { v.push_back(i); } vector<int>::iterator it; it = find(v.begin(), v.end(), 3); v.erase(it); while (it != v.end()) { cout << *it << endl; it++; }
直接上结果:
和上面一样的结局,但是原因不同。erase它实际上是不改变底层的结构的,因为删除pos位置的元素后,pos位置后面的元素会向前挪动,理论上来讲是不会失效的!但是:如果pos刚好是最后一个元素,删完之后pos刚好是end的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素时,vs就认为该位置迭代器失效 了。
Ⅱ、解决方案
最好的解决方案就是在上述操作的后面不要再使用迭代器了,如果硬是要使用的话,那应该要将迭代器进行更新了再去使用即可!
Ⅲ、总结
- 迭代器失效的情况在不同的环境下处理结果不一样,上述崩溃的情况是在VS的下的情况,实际上在Linux下没有那么的严格,但是还是应该注意,要用前最好重新去赋值
- 对于迭代器失效的问题,并不是所有的容器都会存在这个问题,迭代器的失效取决于该容器的底层结构是怎么样的,比如list的insert就没有这样的问题,因为链表底层是不连续的,new出来的,但是它的erase就有存在失效的问题!注意:链表的迭代器失效只会导致当前节点的迭代器失效!(了解底层后就可以理解了)
今天就分享到这里,希望对大家有所帮助!