C++关于vector的详细介绍

文章目录

  • 一、vector的介绍
  • 二、vector的使用
    • 1.vector的定义方式
    • 2.vector的遍历
    • 3.利用vector实现二维数组
    • 4.vector的扩容机制
    • 5.insert函数和erase函数
    • 6.迭代器失效问题

一、vector的介绍

vector底层本质就是一个顺序表,它是一个可变长的数组,采用连续存储的空间来存储数据,它的元素类型也可以是任意的内置类型或者自定义类型。

C++关于vector的详细介绍_第1张图片

二、vector的使用

1.vector的定义方式

第一种方式:定义一个任意类型的空vector

vector<int> v1;
vector<double> v2;
vector<string> v3;

第二种方式:定义一个任意类型的vector,并用n个val来初始化vector

vector<int> v4(10, 5);// 用10个5来初始化vector

第三种方式:定义一个任意类型的vector,并用迭代器区间来初始化vector

vector<int> v5(v4.begin(), v4.end());// 用v4的迭代器区间来初始化v5
string s("hello world");
vector<char> v6(s.begin(), s.end());// 用s的迭代器区间来初始化v6

2.vector的遍历

第一种方式:下标+[]循环遍历

#include 
#include 
#include 

using namespace std;

int main()
{
    vector<int> v(10, 10);
    for (size_t i = 0; i < v.size(); i++)
    {
        v[i] += i;
        cout << v[i] << " ";
    }
    cout << endl;
    return 0;
}

第二种方式:迭代器循环遍历

#include 
#include 
#include 

using namespace std;

int main()
{
    vector<int> v(10, 10);
    vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        (*it)++;
        cout << *it << " ";
    }
    cout << endl;
    return 0;
}

第三种方式:范围for遍历

#include 
#include 
#include 

using namespace std;

int main()
{
    vector<int> v(10, 10);
    for (auto i : v)
    {
        i++;
        cout << i << " ";
    }
    cout << endl;
    return 0;
}

3.利用vector实现二维数组

我们可以通过一个例子来了解如何通过vector实现二维数组,假设我们定义一个5*10的二维空数组,循环向二维数组写入值,最后循环打印这个二维矩阵。

#include 
#include 
#include 

using namespace std;

int main()
{
    vector<vector<int>> v;
    v.resize(5);// 开辟5行空间
    for (size_t i = 0; i < 5; i++)
    {
        v[i].resize(10);// 开辟10列空间
    }

    // 至此,5行10列的二维数组初始化完毕
    // 向二维数组写入
    for (size_t i = 0; i < v.size(); i++)
    {
        for (size_t j = 0; j < v[i].size(); j++)
        {
            v[i][j] = i*j;
        }
    }

    // 打印二维数组
    for (size_t i = 0; i < v.size(); i++)
    {
        for (size_t j = 0; j < v[i].size(); j++)
        {
            cout << v[i][j] << " ";
        }
        cout << endl;
    }
    return 0;
}

4.vector的扩容机制

我们可以通过实验测试一下在Linux下vector是怎么扩容的,一次扩容扩大多少倍?我们定义一个vector,利用nowCapacity来记录vector的容量大小。循环向vector尾插100次数据,当vector的容量发生变化时,打印出来观察前后容量的变化情况。

#include 
#include 
#include 

using namespace std;

int main()
{
    vector<int> v;
    size_t nowCapacity = v.capacity();
    cout << "nowCapacity:" << nowCapacity << endl;
    for (size_t i = 0; i < 100; i++)
    {
        v.push_back(i);
        if (nowCapacity != v.capacity())
        {
            nowCapacity = v.capacity();
            cout << "nowCapacity:" << nowCapacity << endl;
        }
    }
    return 0;
}

在Linux下的运行结果如下图所示,我们可以看到它是呈现2倍增长的。

C++关于vector的详细介绍_第2张图片

相同的代码在VS编译器下实验最后的结果是呈1.5倍增长。其实vector一次扩容多少倍并没有确定的数值,一般就是1.5倍增长到2倍增长这样一个范围,因为如果单次增长较多那么增长的次数就会更少,效率也更高,但是如果单次增长过多容易造成空间浪费;如果单次增长较少那么增长的次数就会更多,效率也更低,但是不容易造成空间的浪费。

5.insert函数和erase函数

vector的insert函数不支持下标的方式去插入,只提供了借助迭代器完成插入的接口。

C++关于vector的详细介绍_第3张图片

第一种方式:在指定迭代器的位置插入一个值

vector<int> v(10, 10);
v.insert(v.begin() + 3, 11);// 在第三个位置插入11

第二种方式:在指定迭代器的位置插入n个val值

vector<int> v(10, 10);
v.insert(v.begin() + 2, 5, 2);// 在第二个位置往后连续插入5个2

vector的erase函数也不支持下标的方式去删除,只提供了借助迭代器完成删除的接口。

在这里插入图片描述

第一种方式:删除指定迭代器位置的值

vector<int> v(10, 10);
v.erase(v.begin());// 删除第3个位置的值

第二种方式:删除迭代器区间内的所有值

vector<int> v(10, 10);
v.erase(v.begin() + 2, v.begin() + 6);// 删除第2个位置到第6个位置的所有值

6.迭代器失效问题

示例一:

#include 
#include 

using namespace std;

int main()
{
    vector<int> v;
    // 循环插入1,2,3,4,5,6到v中
    for (int i = 1; i <= 6; i++)
    {
        v.push_back(i);
    }
    // 在所有的偶数前面插入10
    vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        if ((*it) % 2 == 0)
        {
            v.insert(it, 10);
        }
        it++;
    }

    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;
    return 0;
}

上面的代码示例中,我们循环向数组中所有的偶数前插入10,会出现两种情况的迭代器失效:

第一种: 发生扩容时出现野指针问题

C++关于vector的详细介绍_第4张图片

第二种:空间足够时不会出现扩容,但会出现无限插入的死循环问题
这种情况虽然没有出现扩容造成的野指针问题,但迭代器的指向意义已经改变了,这也是迭代器失效问题。

C++关于vector的详细介绍_第5张图片

其实在标准库中vector的insert函数的实现是有返回值的,insert函数的返回值是新插入值位置的迭代器。

在这里插入图片描述

所以示例一的正确写法应该是下面这样的:每一次insert插入的时候都更新一下it的值,这样就可以避免扩容造成的野指针问题。然后每一次insert插入之后都需要对it进行加一操作,这样就可以避免死循环插入的问题。

#include 
#include 

using namespace std;

int main()
{
    vector<int> v;
    // 循环插入1,2,3,4,5,6到v中
    for (int i = 1; i <= 6; i++)
    {
        v.push_back(i);
    }
    // 在所有的偶数前面插入10
    vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        if ((*it) % 2 == 0)
        {
            it = v.insert(it, 10);// v:1,10,2,3,4,5,6
            // 此时it指向的是10的位置
            it++;
        }
        it++;
    }

    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;
    return 0;
}

示例二:

#include 
#include 

using namespace std;

int main()
{
    vector<int> v;
    // 循环插入1,2,3,4,5,6到v中
    for (int i = 1; i <= 6; i++)
    {
        v.push_back(i);
    }
    // 在所有的偶数前面插入10
    vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        if ((*it) % 2 == 0)
        {
            v.erase(it);
        }
        it++;
    }

    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;
    return 0;
}

上面的代码也是迭代器失效的问题,它同样存在两种问题:

第一种:因为最后一个数也是偶数,当最后一个数被删除以后,it加一去到了v.end()的后一个位置,所以就会陷入死循环

C++关于vector的详细介绍_第6张图片

第二种:数字3和5是没有被检测是否属于偶数的,因为删除了一个偶数之后,下一个数挪动到被删除的位置,然后执行it++操作,跳过了新数据的检测。

C++关于vector的详细介绍_第7张图片

同样的,在标准库种vector的erase函数的实现是有返回值的,erase函数的返回值是被删除数据的位置。

在这里插入图片描述

所以示例二的正确写法应该是下面这样的:如果it当前指向的位置是偶数的话,直接erase函数删除该偶数,然后将返回值给it,it此时指向的就是被删除偶数的下一个数据,所以不需要再进行加一操作。如果it当前指向的位置是奇数的话,则进行加一操作。

#include 
#include 

using namespace std;

#include 
#include 

using namespace std;

int main()
{
    vector<int> v;
    // 循环插入1,2,3,4,5,6到v中
    for (int i = 1; i <= 6; i++)
    {
        v.push_back(i);
    }
    // 在所有的偶数前面插入10
    vector<int>::iterator it = v.begin();
    while (it != v.end())
    {
        // 如果是偶数,直接删除,将返回值给it
        // 此时it指向的就是被删除数据的下一个数据,所以不需要++
        if ((*it) % 2 == 0)
        {
            it = v.erase(it);
        }
        // 如果是奇数,再进行++
        else
        {
            it++;
        }
    }

    for (auto e : v)
    {
        cout << e << " ";
    }
    cout << endl;
    return 0;
}

所以,vector的迭代器失效问题是很容易发生的,我们在使用的时候一定要非常非常小心。一定要画图动态地模拟它的变化,再针对不同的迭代器失效问题使用对应的解决方法。

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