C++STL:顺序容器之vector

news2024/11/14 19:05:54

文章目录

  • 1. 概述
  • 2. 成员函数
  • 3. 创建 vector 容器的几种方式
  • 4. 迭代器
    • vector容器迭代器的基本用法
    • vector容器迭代器的独特之处
  • 5. 访问元素
    • 5.1 访问vector容器中单个元素
    • 5.2 访问vector容器中多个元素
  • 6. 添加元素
    • 6.1 push_back()
    • 6.2 emplace_back()
    • 6.3 emplace_back()和push_back()的区别
  • 7. 插入元素
    • 7.1 insert()
    • 7.2 emplace()
  • 8. 删除元素

1. 概述

vector 容器是 STL 中最常用的容器之一,它和 array 容器非常类似,都可以看做是对 C++ 普通数组的“升级版”。不同之处在于,array 实现的是静态数组(容量固定的数组),而 vector 实现的是一个动态数组,即可以进行元素的插入和删除,在此过程中,vector 会动态调整所占用的内存空间,整个过程无需人工干预。

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

vector 容器以类模板 vector<T>( T 表示存储元素的类型)的形式定义在 <vector> 头文件中,并位于 std 命名空间中。因此,在创建该容器之前,代码中需包含如下内容:

#include <vector>
using namespace std;

2. 成员函数

成员函数功能
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()在序列尾部生成一个元素。

除此之外,C++ 11 标准库还新增加了 begin() 和 end() 这 2 个函数,和 vector 容器包含的 begin() 和 end() 成员函数不同,标准库提供的这 2 个函数的操作对象,既可以是容器,还可以是普通数组。当操作对象是容器时,它和容器包含的 begin() 和 end() 成员函数的功能完全相同;如果操作对象是普通数组,则 begin() 函数返回的是指向数组第一个元素的指针,同样 end() 返回指向数组中最后一个元素之后一个位置的指针(注意不是最后一个元素)。

vector 容器还有一个 std::swap(x , y) 非成员函数(其中 x 和 y 是存储相同类型元素的 vector 容器),它和 swap() 成员函数的功能完全相同,仅使用语法上有差异。

3. 创建 vector 容器的几种方式

创建 vector 容器的方式有很多,大致可分为以下几种。

  1. 如下代码展示了如何创建存储 double 类型元素的一个 vector 容器:
std::vector<double> values;

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

在创建好空容器的基础上,还可以像下面这样通过调用 reserve() 成员函数来增加容器的容量:

values.reserve(20);

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

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

  1. 除了创建空 vector 容器外,还可以在创建的同时指定初始值以及元素个数,比如:
std::vector<int> primes{2, 3, 5, 7, 11, 13, 17, 19};

这样就创建了一个含有 8 个素数的 vector 容器。

  1. 在创建 vector 容器时,也可以指定元素个数:
std::vector<double> values(20);

如此,values 容器开始时就有 20 个元素,它们的默认初始值都为 0。

注意,圆括号 () 和大括号 {} 是有区别的,前者(例如 (20) )表示元素的个数,而后者(例如 {20} ) 则表示 vector 容器中只有一个元素 20。

如果不想用 0 作为默认值,也可以指定一个其它值,例如:

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

第二个参数指定了所有元素的初始值,因此这 20 个元素的值都是 1.0。

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

int num=20;
double value =1.0;
std::vector<double> values(num, value);
  1. 通过存储元素类型相同的其它 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}

4. 迭代器

vector 支持迭代器的成员函数和功能如下表所示:

成员函数功能
begin()返回指向容器中第一个元素的正向迭代器;如果是 const 类型容器,在该函数返回的是常量正向迭代器。
end()返回指向容器最后一个元素之后一个位置的正向迭代器;如果是 const 类型容器,在该函数返回的是常量正向迭代器。此函数通常和 begin() 搭配使用。
rbegin()返回指向最后一个元素的反向迭代器;如果是 const 类型容器,在该函数返回的是常量反向迭代器。
rend()返回指向第一个元素之前一个位置的反向迭代器。如果是 const 类型容器,在该函数返回的是常量反向迭代器。此函数通常和 rbegin() 搭配使用。
cbegin()和 begin() 功能类似,只不过其返回的迭代器类型为常量正向迭代器,不能用于修改元素。
cend()和 end() 功能相同,只不过其返回的迭代器类型为常量正向迭代器,不能用于修改元素。
crbegin()和 rbegin() 功能相同,只不过其返回的迭代器类型为常量反向迭代器,不能用于修改元素。
crend()和 rend() 功能相同,只不过其返回的迭代器类型为常量反向迭代器,不能用于修改元素。

除此之外,C++ 11 新添加的 begin() 和 end() 全局函数也同样适用于 vector 容器。即当操作对象为 vector 容器时,其功能分别和上表中的 begin()、end() 成员函数相同。

这些成员函数的具体功能如下图所示:

在这里插入图片描述

从上图可以看出,这些成员函数通常是成对使用的,即 begin()/end()、rbegin()/rend()、cbegin()/cend()、crbegin()/crend() 各自成对搭配使用。其中,begin()/end() 和 cbegin/cend() 的功能是类似的,同样 rbegin()/rend() 和 crbegin()/crend() 的功能是类似的。

值得一提的是,以上函数在实际使用时,其返回值类型都可以使用 auto 关键字代替,编译器可以自行判断出该迭代器的类型。

vector容器迭代器的基本用法

vector 容器迭代器最常用的功能就是遍历访问容器中存储的元素。

  1. 首先来看 begin() 和 end() 成员函数,它们分别用于指向「首元素」和「尾元素+1」 的位置,下面程序演示了如何使用 begin() 和 end() 遍历 vector 容器并输出其中的元素:
#include <iostream>
#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 << " "; // 输出结果为:1 2 3 4 5
        ++first;
    }
    return 0;
}

可以看到,迭代器对象是由 vector 对象的成员函数 begin() 和 end() 返回的。我们可以像使用普通指针那样使用它们。比如代码中,在保存了元素值后,使用前缀++运算符对 first 进行自增,当 first 等于 end 时,所有的元素都被设完值,循环结束。

当然,还可以使用全局的 begin() 和 end() 函数来从容器中获取迭代器,比如将上面代码中第 7、8 行代码用如下代码替换:

auto first = std::begin(values);
auto end = std::end (values);
  1. cbegin()/cend() 成员函数和 begin()/end() 唯一不同的是,前者返回的是 const 类型的正向迭代器,这就意味着,由 cbegin() 和 cend() 成员函数返回的迭代器,可以用来遍历容器内的元素,也可以访问元素,但是不能对所存储的元素进行修改。比如下面的代码:
#include <iostream>
#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;
}

程序第 12 行,由于 first 是 const 类型的迭代器,因此不能用于修改容器中元素的值。

  1. vector 模板类中还提供了 rbegin() 和 rend() 成员函数,分别表示指向最后一个元素和第一个元素前一个位置的随机访问迭代器,又称它们为反向迭代器。也就是说,在使用反向迭代器进行 ++-- 运算时,++ 指的是迭代器向左移动一位,-- 指的是迭代器向右移动一位,即这两个运算符的功能也“互换”了。

反向迭代器用于以逆序的方式遍历容器中的元素。例如:

#include <iostream>
#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 << " "; // 输出结果为:5 4 3 2 1
        ++first;
    }
    return 0;
}

可以看到,从最后一个元素开始循环,遍历输出了容器中的所有元素。结束迭代器指向第一个元素之前的位置,所以当 first 指向第一个元素并 +1 后,循环就结朿了。

当然,在上面程序中,我们也可以使用 for 循环:

for (auto first = values.rbegin(); first != values.rend(); ++first) {
    cout << *first << " ";
}
  1. crbegin()/crend() 组合和 rbegin()/crend() 组合唯一的区别在于,前者返回的迭代器为 const 类型,即不能用来修改容器中的元素,除此之外在使用上和后者完全相同。

vector容器迭代器的独特之处

和 array 容器不同,vector 容器可以随着存储元素的增加,自行申请更多的存储空间。因此,在创建 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 容器来说,begin() 和 end() 成员函数返回的迭代器是相等的,即它们指向的是同一个位置。

所以,对于空的 vector 容器来说,可以通过调用 push_back() 或者借助 resize() 成员函数实现初始化容器的目的。

除此之外,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;
}

运行程序,显示如下信息并崩溃:

values 容器首个元素的地址:0096DFE8
values 容器首个元素的地址:00965560

可以看到,values 容器在增加容量之后,首个元素的存储地址发生了改变,此时再使用先前创建的迭代器,显然是错误的。因此,为了保险起见,每当 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;
}

运行结果为:

values 容器首个元素的地址:0164DBE8
values 容器首个元素的地址:01645560
123

5. 访问元素

5.1 访问vector容器中单个元素

  1. 首先,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;
}

运行结果为:

1
14

显然,vector 的索引从 0 开始,这和普通数组一样。通过使用索引,总是可以访问到 vector 容器中现有的元素。

值得一提的是,容器名[n]这种获取元素的方式,需要确保下标 n 的值不会超过容器的容量(可以通过 capacity() 成员函数获取),否则会发生越界访问的错误。幸运的是,和 array 容器一样,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;
}

运行结果为:

1
14

我们可能会有这样一个疑问,即为什么 vector 容器在重载 [] 运算符时,没有实现边界检查的功能呢?答案很简单:因为性能。如果每次访问元素,都去检查索引值,无疑会产生很多开销。当不存在越界访问的可能时,就能避免这种开销。

  1. 除此之外,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;
}

输出结果为:

values 首元素为:1
values 尾元素为:5
values 新的首元素为:10
values 新的尾元素为:20
  1. 另外,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;
}

运行结果为:

3
10

5.2 访问vector容器中多个元素

如果想访问 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] << " "; // 输出结果为: 1 2 3 4 5
    }
    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 << " "; // 输出结果为: 1 2 3 4 5
    return 0;
}

另外还可以使用 vector 迭代器遍历 vector 容器,这里以 begin()/end() 为例:

#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 << " "; // 输出结果为: 1 2 3 4 5
    }
    return 0;
}

当然,这里也可以使用 rbegin()/rend()、cbegin()/cend()、crbegin()/crend() 以及全局函数 begin()/end() ,它们都可以实现对容器中元素的访问。

6. 添加元素

6.1 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;
}

程序中,第 7 行代码表示向 values 容器尾部添加一个元素,但由于当前 values 容器是空的,因此新添加的元素 1 无疑成为了容器中首个元素;第 8 行代码实现的功能是在现有元素 1 的后面,添加元素 2。

运行程序,输出结果为:

1 2

6.2 emplace_back()

该函数是 C++ 11 新增加的,其功能和 push_back() 相同,都是在 vector 容器的尾部添加一个元素。emplace_back() 成员函数的用法也很简单,这里直接举个例子:

#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;
}

运行结果为:

1 2

我们可能会发现,以上 2 段代码,只是用 emplace_back() 替换了 push_back(),既然它们实现的功能是一样的,那么 C++ 11 标准中为什么要多此一举呢?

6.3 emplace_back()和push_back()的区别

emplace_back() 和 push_back() 的区别,就在于底层实现的机制不同。push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。

为了清楚的了解它们之间的区别,创建一个包含类对象的 vector 容器,如下所示:

#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);
}

运行结果为:

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

在此基础上,我们可以尝试将 testDemo 类中的移动构造函数注释掉,再运行程序会发现,运行结果变为:

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

由此可以看出,push_back() 在底层实现时,会优先选择调用移动构造函数,如果没有才会调用拷贝构造函数。

显然完成同样的操作,push_back() 的底层实现过程比 emplace_back() 更繁琐,换句话说,emplace_back() 的执行效率比 push_back() 高。因此,在实际使用时,建议优先选用 emplace_back()。

注意:由于 emplace_back() 是 C++ 11 标准新增加的,如果程序要兼顾之前的版本,还是应该使用 push_back()。

7. 插入元素

7.1 insert()

insert() 函数的功能是在 vector 容器的指定位置插入一个或多个元素。该函数的语法格式有多种,如下表所示:

语法格式用法说明
iterator insert(pos,elem)在迭代器 pos 指定的位置之前插入一个新元素 elem,并返回表示新插入元素位置的迭代器。
iterator insert(pos,n,elem)在迭代器 pos 指定的位置之前插入 n 个元素 elem,并返回表示第一个新插入元素位置的迭代器。
iterator insert(pos,first,last)在迭代器 pos 指定的位置之前,插入其他容器(不仅限于vector)中位于 [first,last) 区域的所有元素,并返回表示第一个新插入元素位置的迭代器。
iterator insert(pos,initlist)在迭代器 pos 指定的位置之前,插入初始化列表(用大括号{}括起来的多个元素,中间有逗号隔开)中所有的元素,并返回表示第一个新插入元素位置的迭代器。

下面的例子,演示了如何使用 insert() 函数向 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;
}

运行结果为:

1 3 2 5 5 7 8 9 10 11

7.2 emplace()

emplace() 是 C++ 11 标准新增加的成员函数,用于在 vector 容器指定位置之前插入一个新的元素。

强调:emplace() 每次只能插入一个元素,而不是多个。

该函数的语法格式如下:

iterator emplace (const_iterator pos, args...);

其中,pos 为指定插入位置的迭代器;args… 表示与新插入元素的构造函数相对应的多个参数;该函数会返回表示新插入元素位置的迭代器。

简单的理解 args…,即被插入元素的构造函数需要多少个参数,那么在 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;
}

运行结果为:

3 1 2

既然 emplace() 和 insert() 都能完成向 vector 容器中插入新元素,那么谁的运行效率更高呢?答案是: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;
}

运行结果为:

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

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

可以看到,通过 insert() 函数向 vector 容器中插入 testDemo 类对象,需要调用类的构造函数和移动构造函数(或拷贝构造函数);而通过 emplace() 函数实现同样的功能,只需要调用构造函数即可。

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

8. 删除元素

前面提到,无论是向现有 vector 容器中访问元素、添加元素还是插入元素,都只能借助 vector 模板类提供的成员函数,但删除 vector 容器的元素例外,完成此操作除了可以借助本身提供的成员函数,还可以借助一些全局函数。

基于不同场景的需要,删除 vecotr 容器的元素,可以使用下表中所示的函数(或者函数组合)。

函数功能
pop_back()删除 vector 容器中最后一个元素,该容器的大小(size)会减 1,但容量(capacity)不会发生改变。
erase(pos)删除 vector 容器中 pos 迭代器指定位置处的元素,并返回指向被删除元素下一个位置元素的迭代器。该容器的大小(size)会减 1,但容量(capacity)不会发生改变。
swap(beg)、pop_back()先调用 swap() 函数交换要删除的目标元素和容器最后一个元素的位置,然后使用 pop_back() 删除该目标元素。
erase(beg,end)删除 vector 容器中位于迭代器 [beg,end)指定区域内的所有元素,并返回指向被删除区域下一个位置元素的迭代器。该容器的大小(size)会减小,但容量(capacity)不会发生改变。
remove()删除容器中所有和指定元素值相等的元素,并返回指向最后一个元素下一个位置的迭代器。值得一提的是,调用该函数不会改变容器的大小和容量。
clear()删除 vector 容器中所有的元素,使其变成空的 vector 容器。该函数会改变 vector 的大小(变为 0),但不是改变其容量。
  1. 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;
}

运行结果为:

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

可以发现,相比原 demo 容器,新的 demo 容器删除了最后一个元素 5,容器的大小减了 1,但容量没变。

  1. 如果想删除 vector 容器中指定位置处的元素,可以使用 erase() 成员函数,该函数的语法格式为:
iterator erase (pos);

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

下面的例子演示了 erase() 函数的具体用法:

#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;
}

运行结果为:

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

通过结果不能看出,erase() 函数在删除元素时,会将删除位置后续的元素陆续前移,并将容器的大小减 1。

  1. 另外,如果不在意容器中元素的排列顺序,可以结合 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;
}

运行结果为:

1 5 3 4 2
size is :4
capacity is :5
1 5 3 4
  1. 当然,除了删除容器中单个元素,还可以删除容器中某个指定区域内的所有元素,同样可以使用 erase() 成员函数实现。该函数有 2 种基本格式,前面介绍了一种,这里使用另一种:
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;
}

运行结果为:

size is :3
capacity is :5
1 4 5

可以看到,和删除单个元素一样,删除指定区域内的元素时,也会将该区域后续的元素前移,并缩小容器的大小。

  1. 如果要删除容器中和指定元素值相同的所有元素,可以使用 remove() 函数,该函数定义在 <algorithm> 头文件中。例如:
#include <vector>
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    vector<int> demo{ 1,3,3,4,3,5 };
    // 交换要删除元素和最后一个元素的位置
    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;
}

运行结果为:

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 };
    // 交换要删除元素和最后一个元素的位置
    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;
}

运行结果为:

size is :3
capacity is :6
1 4 5

remove()用于删除容器中指定元素时,常和 erase() 成员函数搭配使用。

  1. 如果想删除容器中所有的元素,则可以使用 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;
}

运行结果为:

size is :0
capacity is :6

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/735794.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[元带你学: eMMC协议 24] eMMC Packed Command CMD23读(Read) 写(write) 操作详解

依JEDEC eMMC及经验辛苦整理&#xff0c;原创保护&#xff0c;禁止转载。 专栏 《元带你学&#xff1a;eMMC协议》 内容摘要 全文 3200 字&#xff0c; 主要内容 目录 前言 1 Packed Commands 有什么用处&#xff1f; 2 Packed Commands 怎么实现&#xff1f; Packed Wri…

【爬虫】5.4 Selenium 实现用户登录

目录 任务目标 创建模拟网站 创建服务器程序 键盘输入动作 鼠标点击动作 编写爬虫程序 任务目标 Selenium 查找的 HTML 元素是一个 WebElemen t对象&#xff0c; 这个对象不但可以获取元素的属性值&#xff0c;而且还能执行一 些键盘输入send_keys()与鼠标点击click()的动…

leetcode 617. 合并二叉树

2023.7.9 这题要求合并两二叉树&#xff0c;若节点重叠则将节点值相加。 和之前不同的是需要同时对两棵树进行操作&#xff0c;我选用队列来做这题。 大致思路&#xff1a;通过遍历两棵树的对应节点&#xff0c;将节点值相加并合并到第一棵树上。如果某个节点为空&#xff0c;…

Squid 缓存服务器

Squid 缓存服务器 作为应用层的代理服务软件&#xff0c;Squid 主要提供缓存加速和应用层过滤控制的功能 ☆什么是缓存代理 当客户机通过代理来请求 Web 页面时 指定的代理服务器会先检查自己的缓存&#xff0c;如果缓存中已经有客户机需要访问的页面&#xff0c;则直接将缓…

Qt实现思维导图功能(五)

前文链接&#xff1a;Qt实现思维导图功能&#xff08;四&#xff09; 思维导图纵向分布模式&#xff1a;模式一 百度网盘体验地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1YNSBiFOUwnSSKvHsBvOT3g 提取码&#xff1a;ifyc动态演示效果 静态展示图片 前文BUG维…

Linux 网络IO管理(epoll实现)

文章目录 1、网络IO模型1.1、阻塞IO&#xff08;blocking IO&#xff09;1.2、非阻塞IO&#xff08;non-blocking IO&#xff09;1.3、多路复用 IO&#xff08;IO multiplexing&#xff09;1.4、异步 IO&#xff08;Asynchronous I/O&#xff09;1.5、信号驱动 IO&#xff08;s…

可恶的剪绳子问题

1. 剑指 Offer 14- I. 剪绳子 题目描述&#xff1a;给你一根长度为 n 的绳子&#xff0c;请把绳子剪成整数长度的 m 段&#xff08;m、n都是整数&#xff0c;n>1并且m>1&#xff09;&#xff0c;每段绳子的长度记为 k[0],k[1]…k[m-1] 。请问 k[0]k[1]…*k[m-1] 可能的最…

3.6.共享内存的学习

目录 前言1. 共享内存2. shared memory案例3. 补充知识总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习精简 CUDA 教程-共享…

基于Qt5 实现的简易慕课爬取程序

基于Qt5 实现的简易慕课爬取程序 一、项目概述二、源代码 一、项目概述 名称&#xff1a;MookScrapy 这个项目主要是使用了 Qt 里面的 QNetworkAccessManager 去下载慕课网站的数据 https://coding.imooc.com&#xff0c;也就是这个网站里面的卡片信息。然后做一定的分析和展示…

【架构设计】酒店预订应用的系统设计架构(如 Airbnb、OYO)

Airbnb、Booking.com 和 OYO 等酒店预订应用程序如何提供从酒店列表到预订再到付款的流畅流程&#xff1f;而且都没有一个小故障&#xff01;在此博客中&#xff0c;您将获得对此的详细解释。由于它们非常庞大&#xff0c;以至于它们需要处理大量的用户流量。所以要管理这些&am…

计算机视觉:卷积核的参数可以通过反向传播学习到吗?

本文重点 在深度学习中,卷积神经网络(Convolutional Neural Networks, CNN)是一种常用的神经网络结构,其中卷积核是CNN的核心组件之一。卷积核是一个小矩阵,用于对输入数据进行卷积操作。卷积操作可以提取输入数据的特征,通过不同的卷积核可以提取不同的特征。 在前面课…

Wireshark TS | 二谈访问网页失败

前言 又一个访问网页失败的案例&#xff0c;该案例来自于 Wireshark sharkfest 2018 - Point And ShootPacket&#xff0c;其中的 Case 2 Cannot see homepage&#xff0c;描述的是来自 OSAKA 的用户抱怨访问一些网站页面不能显示&#xff0c;但是另外一些网站页面可以&#x…

软件测试岗位新标准:ISTQB认证与软件测试工程师职业发展

随着信息技术的飞速发展&#xff0c;软件测试行业也变得越来越重要。软件测试是保证软件质量的关键环节&#xff0c;因此&#xff0c;软件测试工程师的岗位也越来越受到重视。 ISTQB认证成为了衡量软件测试工程师职业能力的标准。 下面领测国际ISTQB考试认证中心就带您了解一下…

depot_tools问题记录 - 执行fetch/gclient命令无响应

文章目录 前言开发环境问题描述问题分析解决方案最后 前言 在研究将Dart dill文件序列化为可读文本时遇到的问题。 开发环境 macOS: 13.4 问题描述 之前使用depot_tools中的fetch/gclient命令还是正常的&#xff0c;今天想实测--no-history参数时突然遇到命令无响应的情况…

Redis 删除 key用 del 和 unlink 有啥区别?

问题 del 和 unlink 有啥区别啊&#xff1f;为什么String类型删除不会做异步删除&#xff1f; 彬彬回答 DEL 和 UNLINK 都是同步的释放 key 对象&#xff0c;区别是怎么释放后面的 value 对象 DEL 每次都是同步释放 value 部分&#xff0c;如果 value 很大&#xff0c;例如一…

Openssh升级方法详解

项目组linux服务器被绿盟扫描出openssh 1.0.2版本有漏洞&#xff0c;需要升级到7.5版本&#xff0c;以下是升级过程&#xff1a; 第一步 安装Telnet服务 先下Openssh软件包 看你需要什么版本http://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/ 1.查看当前的ssh服务版本 …

【前端】网页开发精讲与实战 HTML Day 2

&#x1f680;Write In Front&#x1f680; &#x1f4dd;个人主页&#xff1a;令夏二十三 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f4e3;系列专栏&#xff1a;前端 &#x1f4ac;总结&#xff1a;希望你看完之后&#xff0c;能对你有…

看完这篇 教你玩转渗透测试靶机Vulnhub——The Planets:Mercury

Vulnhub靶机The Planets:Mercury渗透测试详解 Vulnhub靶机介绍&#xff1a;Vulnhub靶机下载&#xff1a;Vulnhub靶机安装&#xff1a;Vulnhub靶机漏洞详解&#xff1a;①&#xff1a;信息收集&#xff1a;②&#xff1a;漏洞发现&#xff1a;③&#xff1a;SSH登入&#xff1a;…

架构师进阶之路 - 微服务怎么划分

目录 微服务划分目标 业务、技术、团队导向规划服务 领域检查 依赖DAG检查 分布式事务检查 性能分布检查 稳定&#xff08;易变&#xff09;性检查 调用链检查 微服务划分目标 我们常说服务的合理划分是微服务成功的重中之重&#xff0c;一个合理的服务划分应该符合一下…

SQL中如何用快照,恢复被误删的数据?

什么是快照 数据库快照是sql server 2005的一个新功能。MSDN上对它的定义是&#xff1a; 数据库快照是数据库&#xff08;称为“源数据库”&#xff09;的只读静态视图。在创建时&#xff0c;每个数据库快照在事务上都与源数据库一致。在创建数据库快照时&#xff0c;源数据库…