STL序列容器详解之vector

本文详细介绍了C++标准库中的STL向量容器(vector),包括其动态数组特性、创建与初始化方式、内存管理如reserve()和shrink_to_fit()、成员函数如push_back()、insert()和erase(),以及迭代器的使用和失效情况。此外,还探讨了emplace_back()和push_back()的区别以及如何高效地添加和删除元素。

参考链接

vector 容器

vector 容器是 STL 中最常用的容器之一。vector 实现的是一个动态数组,即可以进行元素的插入和删除,在此过程中,vector 会动态调整所占用的内存空间,整个过程无需人工干预。

vector 常被称为向量容器,因为该容器擅长在尾部插入或删除元素,在常量时间内就可以完成,时间复杂度为O(1);而对于在容器头部或者中部插入或删除元素,则花费时间要长一些(移动元素需要耗费时间),时间复杂度为线性阶O(n)。

vector 容器以类模板 vector<T>( T 表示存储元素的类型)的形式定义在 <vector> 头文件中,并位于 std 命名空间中。

#include <vector>
using namespace std;

创建vector容器的几种方式

  • 创建存储 double 类型元素的一个 vector 容器:

    std::vector<double> values;
    

    这是一个空的 vector 容器,因为容器中没有元素,所以没有为其分配空间。当添加第一个元素(比如使用 push_back() 函数)时,vector 会自动分配内存。

  • 在创建好空容器的基础上,通过调用 reserve() 成员函数来增加容器的容量:

    values.reserve(20);
    

    这样就设置了容器的内存分配,即至少可以容纳 20 个元素。注意,如果 vector 的容量在执行此语句之前,已经大于或等于 20 个元素,那么这条语句什么也不做;另外,调用 reserve() 不会影响已存储的元素,也不会生成任何元素,即 values 容器内此时仍然没有任何元素。

    如果调用 reserve() 来增加容器容量,之前创建好的任何迭代器(例如开始迭代器和结束迭代器)都可能会失效,这是因为,为了增加容器的容量,vector 容器的元素可能已经被复制或移到了新的内存地址。所以后续再使用这些迭代器时,最好重新生成一下。

  • 创建的同时指定初始值以及元素个数:

    std::vector<int> primes {2, 3, 5, 7, 11, 13, 17, 19};
    
    std::vector<double> values(20);
    

    values 容器开始时就有 20 个元素,它们的默认初始值都为 0。
    如果不想用 0 作为默认值,也可以指定一个其它值:

    std::vector<double> values(20, 1.0);
    

    圆括号 () 中的 2 个参数,既可以是常量,也可以用变量来表示:

    int num=20;
    double value =1.0;
    std::vector<double> values(num, value);
    
  • 通过存储元素类型相同的其它 vector 容器,也可以创建新的 vector 容器

    std::vector<char>value1(5, 'c');
    std::vector<char>value2(value1);
    

    value2 容器中也具有 5 个字符 ‘c’。在此基础上,如果不想复制其它容器中所有的元素,可以用一对指针或者迭代器来指定初始值的范围:

    int array[]={1,2,3};
    std::vector<int>values(array, array+2);//values 将保存{1,2}
    std::vector<int>value1{1,2,3,4,5};
    std::vector<int>value2(std::begin(value1),std::begin(value1)+3);//value2保存{1,2,3}
    

vector容器的成员函数

函数成员函数功能
begin()返回指向容器中第一个元素的迭代器。
end()返回指向容器最后一个元素所在位置后一个位置的迭代器,通常和 begin() 结合使用。
rbegin()返回指向最后一个元素的迭代器。
rend()返回指向第一个元素所在位置前一个位置的迭代器。
cbegin()和 begin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
cend()和 end() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crbegin()和 rbegin() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
crend()和 rend() 功能相同,只不过在其基础上,增加了 const 属性,不能用于修改元素。
size()返回实际元素个数。
max_size()返回元素个数的最大值。这通常是一个很大的值,一般是 2^32-1,所以我们很少会用到这个函数。
resize()改变实际元素的个数。
capacity()返回当前容量。
empty()判断容器中是否有元素,若无元素,则返回 true;反之,返回 false。
reserve()增加容器的容量。
shrink _to_fit()将内存减少到等于当前元素实际所使用的大小。
operator[ ]重载了 [ ] 运算符,可以向访问数组中元素那样,通过下标即可访问甚至修改 vector 容器中的元素。
at()使用经过边界检查的索引访问元素。
front()返回第一个元素的引用。
back()返回最后一个元素的引用。
data()返回指向容器中第一个元素的指针。
assign()用新元素替换原有内容。
push_back()在序列的尾部添加一个元素。
pop_back()移出序列尾部的元素。
insert()在指定的位置插入一个或多个元素。
erase()移出一个元素或一段元素。
clear()移出所有的元素,容器大小变为 0。
swap()交换两个容器的所有元素。
emplace()在指定的位置直接生成一个元素。
emplace_back()在序列尾部生成一个元素。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    //初始化一个空vector容量
    vector<char>value;
    //向value容器中的尾部依次添加 S、T、L 字符
    value.push_back('S');
    value.push_back('T');
    value.push_back('L');
    //调用 size() 成员函数容器中的元素个数
    printf("元素个数为:%d\n", value.size());
    //使用迭代器遍历容器
    for (auto i = value.begin(); i < value.end(); i++) {
        cout << *i << " ";
    }
    cout << endl;
    //向容器开头插入字符
    value.insert(value.begin(), 'C');
    cout << "首个元素为:" << value.at(0) << endl;
    return 0;
}

vector容器迭代器

vector 容器的迭代器也是随机访问迭代器,并且 vector 模板类提供的操作迭代器的成员函数也和 array 容器一样.

遍历访问容器中存储的元素

begin() 和 end() 成员函数
#include <iostream>
//需要引入 vector 头文件
#include <vector>
using namespace std;
int main()
{
    vector<int>values{1,2,3,4,5};
    auto first = values.begin();
    auto end = values.end();
    while (first != end)
    {
        cout << *first << " ";
        ++first;
    }
    return 0;
}
cbegin()/cend() 成员函数
#include <iostream>
//需要引入 vector 头文件
#include <vector>
using namespace std;
int main()
{
    vector<int>values{1,2,3,4,5};
    auto first = values.cbegin();
    auto end = values.cend();
    while (first != end)
    {
        //*first = 10;不能修改元素
        cout << *first << " ";
        ++first;
    }
    return 0;
}
rbegin() 和 rend() 成员函数
#include <iostream>
//需要引入 vector 头文件
#include <vector>
using namespace std;
int main()
{
    vector<int>values{1,2,3,4,5};
    auto first = values.rbegin();
    auto end = values.rend();
    while (first != end)
    {
        cout << *first << " ";
        ++first;
    }
    return 0;
}

vector 容器可以随着存储元素的增加,自行申请更多的存储空间。因此,在创建 vector 对象时,我们可以直接创建一个空的 vector 容器,并不会影响后续使用该容器。在初始化空的 vector 容器时,不能使用迭代器。

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int>values;
    int val = 1;
    for (auto first = values.begin(); first < values.end(); ++first, val++) {
        *first = val;
        //初始化的同时输出值
        cout << *first;
    }
    return 0;
}

vector 容器在申请更多内存的同时,容器中的所有元素可能会被复制或移动到新的内存地址,这会导致之前创建的迭代器失效。

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int>values{1,2,3};
    cout << "values 容器首个元素的地址:" << values.data() << endl;
    auto first = values.begin();
    auto end = values.end();
    //增加 values 的容量
    values.reserve(20);
    cout << "values 容器首个元素的地址:" << values.data() << endl;
    while (first != end) {
        cout << *first;
        ++first;
    }
    return 0;
}

为了保险起见,每当 vector 容器的容量发生变化时,我们都要对之前创建的迭代器重新初始化一遍:

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int>values{1,2,3};
    cout << "values 容器首个元素的地址:" << values.data() << endl;
    auto first = values.begin();
    auto end = values.end();
    //增加 values 的容量
    values.reserve(20);
    cout << "values 容器首个元素的地址:" << values.data() << endl;
    first = values.begin();
    end = values.end();
    while (first != end) {
        cout << *first ;
        ++first;
    }
    return 0;
}

vector容器访问元素的几种方式

访问vector容器中单个元素

  • vector 容器可以像普通数组那样访问存储的元素,甚至对指定下标处的元素进行修改:

    #include <iostream>
    #include <vector>
    using namespace std;
    int main()
    {
        vector<int> values{1,2,3,4,5};
        //获取容器中首个元素
        cout << values[0] << endl;
        //修改容器中下标为 0 的元素的值
        values[0] = values[1] + values[2] + values[3] + values[4];
        cout << values[0] << endl;
        return 0;
    }
    
  • vector 容器也提供了at()成员函数,当传给 at() 的索引会造成越界时,会抛出std::out_of_range异常

    #include <iostream>
    #include <vector>
    using namespace std;
    int main()
    {
        vector<int> values{1,2,3,4,5};
        //获取容器中首个元素
        cout << values.at(0) << endl;
        //修改容器中下标为 0 的元素的值
        values.at(0) = values.at(1) + values.at(2) + values.at(3) + values.at(4);
        cout << values.at(0) << endl;
        //下面这条语句会发生 out_of_range 异常
        //cout << values.at(5) << endl;
        return 0;
    }
    
  • vector 容器还提供了 2 个成员函数,即front()back(),它们分别返回 vector 容器中第一个和最后一个元素的引用,通过利用这 2 个函数返回的引用,可以访问(甚至修改)容器中的首尾元素。

    #include <iostream>
    #include <vector>
    using namespace std;
    int main()
    {
        vector<int> values{1,2,3,4,5};
        cout << "values 首元素为:" << values.front() << endl;
        cout << "values 尾元素为:" << values.back() << endl;
        //修改首元素
        values.front() = 10;
        cout <<"values 新的首元素为:" << values.front() << endl;
        //修改尾元素
        values.back() = 20;
        cout << "values 新的尾元素为:" << values.back() << endl;
        return 0;
    }
    
  • vector 容器还提供了 data()成员函数,该函数的功能是返回指向容器中首个元素的指针。通过该指针也可以访问甚至修改容器中的元素。

    #include <iostream>
    #include <vector>
    using namespace std;
    int main()
    {
        vector<int> values{1,2,3,4,5};
        //输出容器中第 3 个元素的值
        cout << *(values.data() + 2) << endl;
        //修改容器中第 2 个元素的值
        *(values.data() + 1) = 10;
        cout << *(values.data() + 1) << endl;
        return 0;
    }
    

访问vector容器中多个元素

  • 借助·size() 成员函数,该函数可以返回 vector 容器中实际存储的元素个数。

    #include <iostream>
    #include <vector>
    using namespace std;
    int main()
    {
        vector<int> values{1,2,3,4,5};
        //从下标 0 一直遍历到 size()-1 处
        for (int i = 0; i < values.size(); i++) {
            cout << values[i] << " ";
        }
        return 0;
    }
    

    这里不要使用 capacity() 成员函数,因为它返回的是 vector 容器的容量,而不是实际存储元素的个数,这两者是有差别的。

  • 使用基于范围的循环

    #include <iostream>
    #include <vector>
    using namespace std;
    int main()
    {
        vector<int> values{1,2,3,4,5};
        for (auto&& value : values)
            cout << value << " ";
        return 0;
    }
    
  • 使用 vector 迭代器遍历 vector 容器

    #include <iostream>
    #include <vector>
    using namespace std;
    int main()
    {
        vector<int> values{1,2,3,4,5};
        for (auto first = values.begin(); first < values.end(); ++first) {
            cout << *first << " ";
        }
        return 0;
    }
    

vector添加元素(push_back()和emplace_back())

push_back()

该成员函数的功能是在 vector 容器尾部添加一个元素

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int> values{};
    values.push_back(1);
    values.push_back(2);
    for (int i = 0; i < values.size(); i++) {
        cout << values[i] << " ";
    }
    return 0;
}

emplace_back()

其功能和 push_back() 相同,都是在 vector 容器的尾部添加一个元素。

#include <iostream>
#include <vector>
using namespace std;
int main()
{
    vector<int> values{};
    values.emplace_back(1);
    values.emplace_back(2);
    for (int i = 0; i < values.size(); i++) {
        cout << values[i] << " ";
    }
    return 0;
}

emplace_back()和push_back()的区别

emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同

  • push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);
  • 而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。
#include <vector> 
#include <iostream> 
using namespace std;
class testDemo
{
public:
    testDemo(int num):num(num){
        std::cout << "调用构造函数" << endl;
    }
    testDemo(const testDemo& other) :num(other.num) {
        std::cout << "调用拷贝构造函数" << endl;
    }
    testDemo(testDemo&& other) :num(other.num) {
        std::cout << "调用移动构造函数" << endl;
    }
private:
    int num;
};
int main()
{
    cout << "emplace_back:" << endl;
    std::vector<testDemo> demo1;
    demo1.emplace_back(2);  
    cout << "push_back:" << endl;
    std::vector<testDemo> demo2;
    demo2.push_back(2);
}

output
emplace_back:
调用构造函数
push_back:
调用构造函数
调用移动构造函数

将 testDemo 类中的移动构造函数注释掉,再运行程序会发现,运行结果变为:

emplace_back:
调用构造函数
push_back:
调用构造函数
调用拷贝构造函数

push_back() 在底层实现时,会优先选择调用移动构造函数,如果没有才会调用拷贝构造函数
因此,在实际使用时,建议大家优先选用 emplace_back()。

vector插入元素(insert()和emplace())

insert()

nsert() 函数的功能是在 vector 容器的指定位置插入一个或多个元素。

#include <iostream> 
#include <vector> 
#include <array> 
using namespace std;
int main()
{
    std::vector<int> demo{1,2};
    //第一种格式用法
    demo.insert(demo.begin() + 1, 3);//{1,3,2}
    //第二种格式用法
    demo.insert(demo.end(), 2, 5);//{1,3,2,5,5}
    //第三种格式用法
    std::array<int,3>test{ 7,8,9 };
    demo.insert(demo.end(), test.begin(), test.end());//{1,3,2,5,5,7,8,9}
    //第四种格式用法
    demo.insert(demo.end(), { 10,11 });//{1,3,2,5,5,7,8,9,10,11}
    for (int i = 0; i < demo.size(); i++) {
        cout << demo[i] << " ";
    }
    return 0;
}

emplace()

用于在 vector 容器指定位置之前插入一个新的元素。emplace() 每次只能插入一个元素,而不是多个。

#include <vector>
#include <iostream>
using namespace std;
int main()
{
    std::vector<int> demo1{1,2};
    //emplace() 每次只能插入一个 int 类型元素
    demo1.emplace(demo1.begin(), 3);
    for (int i = 0; i < demo1.size(); i++) {
        cout << demo1[i] << " ";
    }
    return 0;
}

emplace() 在插入元素时,是在容器的指定位置直接构造元素,而不是先单独生成,再将其复制(或移动)到容器中。因此,在实际使用中,推荐大家优先使用 emplace()。

#include <vector>
#include <iostream>
using namespace std;
class testDemo
{
public:
    testDemo(int num) :num(num) {
        std::cout << "调用构造函数" << endl;
    }
    testDemo(const testDemo& other) :num(other.num) {
        std::cout << "调用拷贝构造函数" << endl;
    }
    testDemo(testDemo&& other) :num(other.num) {
        std::cout << "调用移动构造函数" << endl;
    }
    testDemo& operator=(const testDemo& other);
private:
    int num;
};
testDemo& testDemo::operator=(const testDemo& other) {
    this->num = other.num;
    return *this;
}
int main()
{
    cout << "insert:" << endl;
    std::vector<testDemo> demo2{};
    demo2.insert(demo2.begin(), testDemo(1));
    cout << "emplace:" << endl;
    std::vector<testDemo> demo1{};
    demo1.emplace(demo1.begin(), 1);
    return 0;
}

output
insert:
调用构造函数
调用移动构造函数
emplace:
调用构造函数

当拷贝构造函数和移动构造函数同时存在时,insert() 会优先调用移动构造函数。

vector删除元素的几种方式

pop_back()

#include <vector>
#include <iostream>
using namespace std;
int main()
{
    vector<int>demo{ 1,2,3,4,5 };
    demo.pop_back();
    //输出 dmeo 容器新的size
    cout << "size is :" << demo.size() << endl;
    //输出 demo 容器新的容量
    cout << "capacity is :" << demo.capacity() << endl;
    for (int i = 0; i < demo.size(); i++) {
        cout << demo[i] << " ";
    }
    return 0;
}

output
size is :4
capacity is :5
1 2 3 4

erase()

iterator erase (pos);

pos 为指定被删除元素位置的迭代器,同时该函数会返回一个指向删除元素所在位置下一个位置的迭代器。

#include <vector>
#include <iostream>
using namespace std;
int main()
{
    vector<int>demo{ 1,2,3,4,5 };
    auto iter = demo.erase(demo.begin() + 1);//删除元素 2
    //输出 dmeo 容器新的size
    cout << "size is :" << demo.size() << endl;
    //输出 demo 容器新的容量
    cout << "capacity is :" << demo.capacity() << endl;
    for (int i = 0; i < demo.size(); i++) {
        cout << demo[i] << " ";
    }
    //iter迭代器指向元素 3
    cout << endl << *iter << endl;
    return 0;
}

output
size is :4
capacity is :5
1 3 4 5
3

erase() 函数在删除元素时,会将删除位置后续的元素陆续前移,并将容器的大小减 1。

除了删除容器中单个元素,还可以删除容器中某个指定区域内的所有元素,同样可以使用 erase() 成员函数实现.

iterator erase (iterator first, iterator last);

其中 first 和 last 是指定被删除元素区域的迭代器,同时该函数会返回指向此区域之后一个位置的迭代器。

#include <vector>
#include <iostream>
using namespace std;
int main()
{
    std::vector<int> demo{ 1,2,3,4,5 };
    //删除 2、3
    auto iter = demo.erase(demo.begin()+1, demo.end() - 2);
    cout << "size is :" << demo.size() << endl;
    cout << "capacity is :" << demo.capacity() << endl;
    for (int i = 0; i < demo.size(); i++) {
        cout << demo[i] << " ";
    }
    return 0;
}

output
size is :3
capacity is :5
1 4 5

结合 swap() 和 pop_back() 函数

同样可以实现删除容器中指定位置元素的目的。
swap() 函数在头文件 <algorithm><utility>中都有定义,使用时引入其中一个即可。

#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
    vector<int>demo{ 1,2,3,4,5 };
    //交换要删除元素和最后一个元素的位置
    swap(*(std::begin(demo)+1),*(std::end(demo)-1));//等同于 swap(demo[1],demo[4])
   
    //交换位置后的demo容器
    for (int i = 0; i < demo.size(); i++) {
        cout << demo[i] << " ";
    }
    demo.pop_back();
    cout << endl << "size is :" << demo.size() << endl;
    cout << "capacity is :" << demo.capacity() << endl;
    //输出demo 容器中剩余的元素
    for (int i = 0; i < demo.size(); i++) {
        cout << demo[i] << " ";
    }
    return 0;
}

output
1 5 3 4 2
size is :4
capacity is :5
1 5 3 4

remove()

删除容器中和指定元素值相同的所有元素,该函数定义在 <algorithm> 头文件中.

#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
    vector<int>demo{ 1,3,3,4,3,5 };
    //删除元素3
    auto iter = std::remove(demo.begin(), demo.end(), 3);
    cout << "size is :" << demo.size() << endl;
    cout << "capacity is :" << demo.capacity() << endl;
    //输出剩余的元素
    for (auto first = demo.begin(); first < iter;++first) {
        cout << *first << " ";
    }
    return 0;
}

output
size is :6
capacity is :6
1 4 5

在对容器执行完 remove() 函数之后,由于该函数并没有改变容器原来的大小和容量,因此无法使用之前的方法遍历容器,而是需要向程序中那样,借助 remove() 返回的迭代器完成正确的遍历。

remove() 的实现原理是,在遍历容器中的元素时,一旦遇到目标元素,就做上标记,然后继续遍历,直到找到一个非目标元素,即用此元素将最先做标记的位置覆盖掉。因此,如果将上面程序中 demo 容器的元素全部输出,得到的结果为1 4 5 4 3 5

既然通过 remove() 函数删除掉 demo 容器中的多个指定元素,该容器的大小和容量都没有改变,其剩余位置还保留了之前存储的元素。我们可以使用 erase() 成员函数删掉这些 “无用” 的元素。

#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
    vector<int>demo{ 1,3,3,4,3,5 };
    //删除元素3
    auto iter = std::remove(demo.begin(), demo.end(), 3);
    demo.erase(iter, demo.end());
    cout << "size is :" << demo.size() << endl;
    cout << "capacity is :" << demo.capacity() << endl;
    //输出剩余的元素
    for (int i = 0; i < demo.size();i++) {
        cout << demo[i] << " ";
    }
    return 0;
}

output
size is :3
capacity is :6
1 4 5

clear()

#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{
    vector<int>demo{ 1,3,3,4,3,5 };
    //删除所有元素
    demo.clear();
    cout << "size is :" << demo.size() << endl;
    cout << "capacity is :" << demo.capacity() << endl;
    return 0;
}

output
size is :0
capacity is :6
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值