C++STL详解(三):vector的介绍及其使用

文章目录

        • vector的介绍及使用
          • vector的介绍
          • vector的定义
          • vector的三种遍历方式
        • vector的空间增长问题
          • size和capacity
          • empty
          • resize
          • reserve
        • vector迭代器的使用
          • begin与end
          • rbegin与rend
        • vector增删查改
          • push_back和pop_back
          • find
          • insert和erase
          • swap
          • operator[]
        • vector迭代器失效问题(重点)
          • 示例一:
          • 示例二:
          • 示例三:
          • 迭代器失效解决办法
          • 示例一解决方案:
          • 示例二解决方案:
          • 示例三解决方案:

vector的介绍及使用

vector的介绍
  1. vector是表示可变大小数组的序列容器。
  2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
  3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
  4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
  5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
  6. 与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起lists和forward_lists统一的迭代器和引用更好。
vector的定义

C++STL详解(三):vector的介绍及其使用_第1张图片

构造函数声明 接口说明
vector()(重点) 无参构造,构造某个类型的空容器
vector(size_type n, const value_type& val = value_type()) 构造并初始化n个val
vector (const vector& x); (重点) 拷贝构造
vector (InputIterator first, InputIterator last); 使用迭代器进行初始化构造
int main()
{
    //无参构造
	vector<int>v1;
	//构造并初始化n个val
	vector<int>v2(10, 0);
	//迭代器区间初始化
	vector<int>v3(v2.begin(), v2.end());
    
    vector<string> v5;
	string s3("sort");
	v5.push_back(s3);
	v5.push_back(string("insert"));

	// 推荐
	v5.push_back("erase");
    //拷贝构造
    vector<string> copy(v5);
}

我们使用使用迭代器进行初始化构造,还可以使用其他容器的迭代器区间来初始化

    string s("hello world");
	vector<char>v4(s.begin(), s.end());

但是需要我们注意的是,vector不管是在实现方式上还是在用法上和string都非常的像,不过他们之间还是有一些区别的,string类是保存字符的动态数组,由于其中有一个接口c_str,可以将其转化成C语言的字符串,要以\0结尾,所以string类最后会有一个\0,但是这里vector也是保存字符的动态数组,但是不它不会以\0结尾,不保存\0.

vector的三种遍历方式

一、下标+[]

因为vector中重载了[],因此我们可以用下标+[]的方式来遍历vector

    //1.下标+[]    
    vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1[0] = 10;

	//三种遍历方式
	//1.下标+[]
	for (size_t i = 0; i < v1.size(); i++)
	{
		cout << v1[i] << " ";
	}
	cout << endl;

二、迭代器

使用迭代器区间进行遍历,需要注意的是我们迭代器区间一般都是左闭右开的,[begin,end)。

    //2.迭代器
	vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		*it += 1;
		cout << *it << " ";
		++it;
	}
	cout << endl;

三、范围for

范围for(自动判断结束,自动迭代++)->看起来很智能,
实际上呢,其实就是被编译器替换成了迭代器

    for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

vector的空间增长问题

容量空间 接口说明
size 获取数据个数
capacity 获取容量大小
empty 判断是否为空
resize(重点) 改变vector的size
reserve (重点) 改变vector的capacity
size和capacity

通过size函数获取当前容器中的有效元素个数,通过capacity函数获取当前容器的容量大小

void test_vector5()
{
	vector<int>v1(10, 5);
	cout << v1.size() << endl;
	cout << v1.capacity() << endl;

}
empty

通过empty函数判断当前容器是否为空

    vector<int>v1(10, 1);
	cout << v1.empty() << endl;
resize

C++STL详解(三):vector的介绍及其使用_第2张图片

resize是用来改变当前容器中有效元素个数的

resize使用规则:

1.n如果大于当前容器的size,则将有效元素个数扩大到n,扩大的元素为val,如果val未给值,那么val就是当前容器类型的缺省值(比如int就是0)

2.如果n小于当前容器的size,则将有效元素个数缩小成n。

    vector<int>v(10, 1);
	cout << v.size() << endl;//10
	v.reserve(20);

	v.resize(15);//将有效元素个数变成15
	PrintVector(v);
	cout << v.size() << endl;//15

	v.resize(5);//将有效元素个数变成5
	PrintVector(v);
	cout << v.size() << endl;//5

C++STL详解(三):vector的介绍及其使用_第3张图片

reserve

C++STL详解(三):vector的介绍及其使用_第4张图片

reserve是用来改变当前容器的最大容量的

reserve的使用规则:

1.如果n大于当前容器的capacity,则将当前容量扩大到n

2.如果n小于当前容器的capacity,则什么也不做。

    vector<int>v(10, 1);
	cout << v.capacity() << endl;//10

	v.reserve(5);//10
	cout << v.capacity() << endl;//因为n小于当前容量,所以容量不会发生变化

	v.reserve(20);//将空间容量扩大到20
	cout << v.capacity() << endl;//20

C++STL详解(三):vector的介绍及其使用_第5张图片

vector迭代器的使用

iterator的使用 接口说明
begin + end(重点) 获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
rbegin + rend 获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator

C++STL详解(三):vector的介绍及其使用_第6张图片

begin与end

begin函数是获取该容器第一个数据位置的迭代器,而end函数是获取该容器最后一个数据的下一个位置的迭代器

#include
#include
using namespace std;


int main()
{
    // 使用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 != v1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
    
    //使用迭代器进行修改
    it = v.begin();
    while (it != v1.end())
	{   *it+=1
		++it;
	}
    
}
rbegin与rend

rbegin函数是获取最后一个数据位置的反向迭代器,rend是获取第一个数据前一个位置的反向迭代器

#include
#include
using namespace std;

int main()
{
    // 使用push_back插入4个数据
    vector<int> v;
    v.push_back(1);
    v.push_back(2); 
    v.push_back(3);
    v.push_back(4);
    
    // 使用反向迭代器进行遍历再打印
    vector<int>::reverse_iterator rit = v.rbegin(); 
    //auto rit = v.rbegin();//这里可以使用auto自动推到类型
    while (rit != v.rend())
    {
        cout << *rit << " ";
        ++rit; 
    } 
    cout << endl;
    
}

vector增删查改

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

push_back函数是在当前容器的尾部插入一个数据,而pop_back函数是在当前容器的尾部删除一个数据

int main()
{
   vector<int> v1;
	//尾插
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	//尾删
	v1.pop_back();
	v1.pop_back();
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

C++STL详解(三):vector的介绍及其使用_第7张图片

find

注意:vector里面并不像string那样实现了find接口,我们这个find函数接口是algorithm算法头文件里面的,它是一个函数模板,因此只要包含了这个头文件,其他任何容器都可以使用。

C++STL详解(三):vector的介绍及其使用_第8张图片

我们这个find函数是通过给一块迭代器区间,然后在这块迭代器区间里面去查找某个值,如果找到了是返回该位置的迭代器,未找到的话就返回的是last位置的迭代器。

insert和erase

C++STL详解(三):vector的介绍及其使用_第9张图片

在这里插入图片描述

使用insert函数可以在给定的迭代器位置插入一个或者多个元素,使用erase函数可以删除给定的迭代器位置的元素,或者删除所给迭代器区间内的所有元素(区间左闭右开)

int main()
{
    vector<int> v1;
	//尾插
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);

	//使用find查找3所在位置的迭代器
	vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);

	//在3的位置插入30
	v1.insert(pos, 30);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	//在容器的起始位置插入3个2
	v1.insert(v1.begin(), 3, 2);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	//使用find查找4所在位置的迭代器
	vector<int>::iterator pos1 = find(v1.begin(), v1.end(), 4);
	//删除pos1迭代器位置下的元素
	v1.erase(pos1);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	//删除该迭代器区间的所有元素
	v1.erase(v1.begin(), v1.begin()+3);//string和vector这两个容器才可以这样子使用
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
    
}

C++STL详解(三):vector的介绍及其使用_第10张图片

swap

C++STL详解(三):vector的介绍及其使用_第11张图片

通过swap函数可以交换两个容器(同类型)的数据空间,从而达到两个容器的交换

vector<int>v1(5, 1);
	string s1("hello world");
	vector<int>v2(5, 2);

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	for (auto e : v2)
	{
		cout << e << " ";
	}
	cout << endl;

	//支持两个同类型的容器之间进行交换
	swap(v1, v2);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	for (auto e : v2)
	{
		cout << e << " ";
	}
	cout << endl;

C++STL详解(三):vector的介绍及其使用_第12张图片

operator[]

因为vector重载了[]操作符,因此vector就可以像数组一样去遍历和访问了

int main()
{
    vector<int> v1;
	//尾插
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
    //通过[]+下标的方式遍历
    for (size_t i = 0; i < v1.size(); i++)
	{
		cout << v1[i] << " ";
	}
	cout << endl;
    
    //通过[]+下标的方式改变元素的值
    v1[2] = 7;
    for (size_t i = 0; i < v1.size(); i++)
	{
		cout << v1[i] << " ";
	}
	cout << endl;
}

C++STL详解(三):vector的介绍及其使用_第13张图片

vector迭代器失效问题(重点)

迭代器的主要作就是让我们在使用各个容器的时候不用去关心底层数据结构,而vector的迭代器底层实际就是原生态指针T*,迭代器失效就是指迭代器底层对应指针所指向的空间被销毁了,而指向一块已经被释放的空间,如果继续使用已经失效的迭代器,程序可能会崩溃。

迭代器失效问题举例

示例一:
int main()
{
    vector<int> v1;
	//尾插
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	//查找3所在位置的迭代器
	vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);
	//在3的位置插入30
	v1.insert(pos, 30);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;

	v1.erase(pos);//删除元素3?
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

C++STL详解(三):vector的介绍及其使用_第14张图片

大家可能会对打印结果有点疑惑,明明我在3的位置插入了一个30,然后我想删除pos位置的数据,按理来说删除的不应该是3嘛?为什么删除的却是30呢?

这就是迭代器失效的一种情况:pos的意义变了,插入数据前pos是指向3的,插入数据以后pos不再指向3,而是指向30,因此删除pos位置的值会删除掉30而不是3.

C++STL详解(三):vector的介绍及其使用_第15张图片

示例二:
int main()
{
    vector<int> v1;
	//尾插
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	//查找3所在位置的迭代器
	vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	//在3的位置插入10
	v1.insert(pos, 10);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	//删除pos位置的值
	v1.erase(pos);
	for (auto e : v1)
	{
		cout << e << " ";
	}

	cout << endl;
}

C++STL详解(三):vector的介绍及其使用_第16张图片

看到打印结果我们可能会感到非常奇怪,我就想在3的位置插入个10,然后再删除pos位置的值,怎么这程序就崩溃了呢?

因为这里pos失效并不仅仅是意义变了,而是因为insert出现增容以后,pos位置指向空间已经释放,pos已经是野指针了,因此我们再对pos进行操作就会导致程序崩溃。

C++STL详解(三):vector的介绍及其使用_第17张图片

示例三:
#include 
#include 
using namespace std;

int main()
{
	vector<int> v;
	for (size_t i = 1; i <= 6; i++)
	{
		v.push_back(i);
	}
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0) //删除容器当中的全部偶数
		{
			v.erase(it);
		}
		it++;
	}
	return 0;
}

乍一看可能觉得这个程序没什么问题,但是执行起来会发现程序崩溃了

C++STL详解(三):vector的介绍及其使用_第18张图片

这个时候大家可能会好奇了,为什么这个程序崩溃了呢?

下面我们来好好分析一下(画图分析)

C++STL详解(三):vector的介绍及其使用_第19张图片

通过画图我们可以发现迭代器访问到了不属于容器的内存空间,从而导致程序崩溃。

不仅如此,这个代码的迭代器在遍历容器中的元素进行判断时,并没有对3,5这两个元素进行判断,而是直接跳过了他们。

迭代器失效解决办法

使用迭代器时,记住一句话:每次使用前,对迭代器进行重新赋值

示例一解决方案:
int main()
{
    vector<int> v1;
	//尾插
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	//查找3所在位置的迭代器
	vector<int>::iterator pos = find(v1.begin(), v1.end(), 3);
	//在3的位置插入30
	v1.insert(pos, 30);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
    pos = find(v1.begin(), v1.end(), 3);
	v1.erase(pos);//删除元素3?
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
}

对于示例一,我们只需要在使用迭代器删除元素3的时候重新对pos进行赋值即可

C++STL详解(三):vector的介绍及其使用_第20张图片

示例二解决方案:
    vector v1;
	//尾插
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	//查找3所在位置的迭代器
	vector::iterator pos = find(v1.begin(), v1.end(), 3);

	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	//在3的位置插入10
	v1.insert(pos, 10);
	for (auto e : v1)
	{
		cout << e << " ";
	}
	cout << endl;
	//更新pos的值
	pos = find(v1.begin(), v1.end(), 3);
	//删除pos位置的值
	v1.erase(pos);
	for (auto e : v1)
	{
		cout << e << " ";
	}

	cout << endl;

对于示例二,我们只需要重新查找一下3位置的迭代器,然后再对其进行删除即可
C++STL详解(三):vector的介绍及其使用_第21张图片

示例三解决方案:
int main()
{
    vector<int> v;
	for (size_t i = 1; i <= 6; i++)
	{
	    v.push_back(i);
	}
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0) //删除容器当中的全部偶数
		{
			it = v.erase(it);//删除后获取下一个元素的迭代器
		}
		else
		{
			it++;//是奇数则++
		}
	}
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

对于示例三、我们可以接收erase函数的返回值(erase函数返回删除元素的后一个元素的新位置)并且控制代码逻辑,当元素被删除后,继续判断该位置的元素是属于偶数还是奇数(因为该位置的元素已经更新了需要再次进行判断)

C++STL详解(三):vector的介绍及其使用_第22张图片

以上就是本篇文章的全部内容了,如果觉得对你有所帮助的话就一键三连一波支持一下作者吧。

你可能感兴趣的:(C++,c++,开发语言,后端)