C++关于vector的详细介绍

一、vector的介绍

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

在这里插入图片描述

二、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 <iostream>
#include <vector>
#include <string>

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 <iostream>
#include <vector>
#include <string>

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 <iostream>
#include <vector>
#include <string>

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 <iostream>
#include <vector>
#include <string>

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 <iostream>
#include <vector>
#include <string>

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倍增长的。

在这里插入图片描述

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

5.insert函数和erase函数

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

在这里插入图片描述

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

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 <iostream>
#include <vector>

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,会出现两种情况的迭代器失效:

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

#include <iostream>
#include <vector>

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 <iostream>
#include <vector>

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()的后一个位置,所以就会陷入死循环

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

#include <iostream>
#include <vector>

using namespace std;

#include <iostream>
#include <vector>

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

  • 25
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
C++中的vector是一个动态数组,可以根据需要随时调整大小。它是标准库中最常用的容器之一,提供了许多方便的成员函数和操作符重载。 使用vector容器需要包含头文件`<vector>`。 下面是一些vector容器的特点和常用操作: 1. 动态大小:vector可以根据需要动态调整大小,可以在任意位置插入或删除元素。 2. 快速随机访问:vector支持通过索引快速访问元素,时间复杂度为O(1)。 3. 连续存储:vector的元素在内存中是连续存储的,这样可以提高访问效率。 4. 自动内存管理:vector会自动管理内部的动态内存分配和释放,无需手动管理。 5. 范围检查:vector会在访问操作时进行边界检查,确保不越界。 以下是一些常用的vector操作: - `push_back(value)`:在vector末尾添加一个元素。 - `pop_back()`:删除vector末尾的元素。 - `size()`:返回vector中元素的个数。 - `empty()`:判断vector是否为空。 - `clear()`:清空vector中的所有元素。 - `at(index)`:返回指定索引位置的元素,并进行范围检查。 - `front()`:返回第一个元素。 - `back()`:返回最后一个元素。 - `insert(iterator, value)`:在指定位置插入一个元素。 - `erase(iterator)`:删除指定位置的元素。 - `begin()`和`end()`:返回指向vector第一个元素和最后一个元素之后的迭代器,用于循环遍历。 vector容器提供了丰富的功能,并且易于使用,适合在需要动态大小和快速访问的情况下使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值