【C++STL详解(三)】------vector的介绍与使用

目录

前言

一、关于数组

二、vector的介绍

三、vector的使用

Ⅰ、默认成员函数

1.构造函数

2.赋值重载

3.析构函数

Ⅱ、容量

1.size()

2.capacity()

3.empty()

4.resize()

5.reserve()

Ⅲ、遍历操作

1.迭代器

begin() +end()(正向迭代器)

rbegin()+rend()(反向迭代器)

2.operator【】

Ⅳ、增删查改

1.push_back()

2.pop_back()

3.find()

4.insert

5.erase

6.swap

四、vector细节问题-迭代器失效

Ⅰ、失效案例

1.会引起底层结构发生改变的操作!

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

Ⅱ、解决方案

Ⅲ、总结


前言

下面接着来介绍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在访问元素的时候更加高效,在末 尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起listforward_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就有存在失效的问题!注意:链表的迭代器失效只会导致当前节点的迭代器失效!(了解底层后就可以理解了)

今天就分享到这里,希望对大家有所帮助!

  • 18
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值