首页 > 编程笔记 > C++笔记 阅读:4

C++ vector容器的用法(非常详细)

C++ 标准库提供了各种类型的容器来存储对象集合,包括序列容器(如 vector、array 和 list)、有序和无序关联容器(如 set 和 map),以及不存储数据但提供面向序列容器的适配接口(如 stack 和 queue)的容器适配器。

标准库中的容器都是作为类模板实现的,这意味着它们可以与任意类型一起使用(只要它满足容器需求)。通常,应该始终使用最适合于特定问题的容器,这不仅在插入、删除、访问元素和内存使用速度方面提供了良好的性能,而且还使代码易于阅读和维护。

本节讲解 vector 容器的用法。

C++ vector的使用方式

类模板 vector 在头文件 <vector> 的 std 命名空间中可用。

要初始化 std::vector 类模板,可以使用以下方法中的任何一种(但不限于这些):
1) 从初始化列表进行初始化:
std::vector<int> v1 { 1, 2, 3, 4, 5 };

2) 从数组初始化:
int arr[] = { 1, 2, 3, 4, 5 };
std::vector<int> v21(arr, arr + 5); // v21 = { 1, 2, 3, 4, 5 }
std::vector<int> v222(arr+1, arr+4); // v222 = { 2, 3, 4 }

3) 从另一个容器初始化:
std::list<int> l { 1, 2, 3, 4, 5 };
std::vector<int> v3(l.begin(), l.end()); // { 1, 2, 3, 4, 5 }

4) 从计数和值初始化:
std::vector<int> v4(5, 1); // {1, 1, 1, 1, 1 }

要修改 std::vector 的内容,可以使用以下方法中的任何一种(但不限于这些):
1) 使用 push_back() 在 vector 的末尾添加一个元素:
std::vector<int> v1{ 1, 2, 3, 4, 5 };
v1.push_back(6); // v1 = { 1, 2, 3, 4, 5, 6 }

2) 使用 pop_back() 从 vector 的末尾移除一个元素:
v1.pop_back();

3) 使用 insert() 函数在 vector 的任意位置插入元素:
int arr[] = { 1, 2, 3, 4, 5 };
std::vector<int> v21;
v21.insert(v21.begin(), arr, arr + 5); // v21 = { 1, 2, 3, 4, 5 }
std::vector<int> v22;
v22.insert(v22.begin(), arr, arr + 3); // v2 = { 1, 2, 3 }

4) 通过使用 emplace_back() 方法在 vector 的末尾创建一个元素来添加一个元素:
struct foo
{
    int a;
    double b;
    std::string c;

    foo(int a, double b, std::string const & c) :
        a(a), b(b), c(c) {}
};

std::vector<foo> v3;
v3.emplace_back(1, 1.0, "one"s);
// v3 = { foo{1, 1.0, "one"} }

5) 通过使用 emplace() 方法在 vector 的任意位置创建一个元素来插入一个元素:
v3.emplace(v3.begin(), 2, 2.0, "two"s);
// v3 = { foo{2, 2.0, "two"}, foo{1, 1.0, "one"} }

要修改 vector 的整个内容,可以使用以下方法中的任何一种(但不限于这些):
1) 用另外一个 vector 进行赋值(使用 operator=),这将替换容器的内容:
std::vector<int> v1{ 1, 2, 3, 4, 5 };
std::vector<int> v2{ 10, 20, 30 };
v2 = v1; // v2 = { 1, 2, 3, 4, 5 }

2) 通过 assign() 方法用 begin 和 end 迭代器定义的另一个序列进行赋值,这将替换容器的内容:
int arr[] = { 1, 2, 3, 4, 5 };
std::vector<int> v31;
v31.assign(arr, arr + 5); // v31 = { 1, 2, 3, 4, 5 }
std::vector<int> v32;
v32.assign(arr + 1, arr + 4); // v32 = { 2, 3, 4 }

3) 使用 swap() 方法交换两个 vector 的内容:
std::vector<int> v4{ 1, 2, 3, 4, 5 };
std::vector<int> v5{ 10, 20, 30 };
v4.swap(v5); // v4 = { 10, 20, 30 }, v5 = { 1, 2, 3, 4, 5 }

4) 使用 clear() 方法删除所有元素:
std::vector<int> v6{ 1, 2, 3, 4, 5 };
v6.clear(); // v6 = { }

5) 使用 erase() 方法删除一个或多个元素(这需要一个迭代器或一对迭代器来定义 vector 中要删除的元素的范围):
std::vector<int> v7{ 1, 2, 3, 4, 5 };
v7.erase(v7.begin() + 2, v7.begin() + 4); // v7 = { 1, 2, 5 }

要获取 vector 中第一个元素的地址,通常需要将 vector 的内容传递给类 C API,可以使用以下方法中的任意一种:
1) 使用 data() 方法,它返回指向第一个元素的指针,提供对存储 vector 元素的底层连续内存块的直接访问,这只在 C++11 之后可用:
void process(int const * const arr, size_t const size)
{ /* do something */ }
std::vector<int> v { 1, 2, 3, 4, 5 };
process(v.data(), v.size());

2) 获取第一个元素的地址:
process(&v[0], v.size());

3) 获取 front() 方法引用的元素的地址:
process(&v.front(), v.size());

4) 获取由 begin() 返回的迭代器所指向的元素的地址:
process(&*v.begin(), v.size());

深度解读vector

std::vector 类被设计成与数组最相似且可与数组互操作的 C++ 容器。vector 是一个大小可变的元素序列,保证连续地存储在内存中,这使得 vector 的内容可以很容易地传递给一个类 C 的函数——该函数接受一个指向数组元素的指针(通常是一个大小)。

使用 vector 代替 array 有很多好处,包括:
注意,vector 用于存储对象实例。如果需要存储指针,请不要存储原始指针,而要存储智能指针。否则,需要管理指向对象的生命周期。

vector 类是一个非常高效的容器,它的所有实现都提供了许多优化,而大多数开发人员无法对 array 进行这些优化。对其元素的随机访问以及在 vector 末尾的插入和删除是常数 0(1) 操作(前提是不需要重新分配内存),而在其他任何地方的插入和删除是线性 0(n) 操作。

与其他标准容器相比,vector 具有很多优点:
std::vector 在语义上与数组非常相似,但其大小是可变的,可以增大,也可以减小。以下两个属性定义了 vector 的大小:
大小总是小于或等于容量,当大小等于容量时,并且需要添加一个新元素时,就需要修改容量,以便 vector 有容纳更多元素的空间。在这种情况下,vector 会分配一个新的内存块,并在释放已分配的内存之前将已有的内容移动到新的内存块。虽然这听起来很耗费时间,事实上也确实如此,但在每次需要更改时,vector 的实现都会成倍数地增加容量。平均而言,vector 的每个元素只需要移动一次(这是因为在增加容量的过程中,vector 的所有元素都被移动了,但之后可以添加相同数量的元素,而不会引起更多的移动,因为插入是在 vector 的末尾执行的)。

如果预先知道将在 vector 中插入多少个元素,那么可以首先调用 reserve() 方法将容量增加到至少指定的数量(如果指定的大小小于当前的容量,则该方法不执行任何操作),然后再插入元素。

如果需要释放额外的预留内存,那么可以使用 shrink_to_fit() 方法来请求释放,但是否释放内存是由实现决定的。这个非绑定方法的另一种替代方法(自 C++11 起可用)是用一个临时的空 vector 进行交换:
std::vector<int> v{ 1, 2, 3, 4, 5 };
std::vector<int>().swap(v); // v.size = 0, v.capacity = 0

此外,调用 clear() 方法只会从 vector 中移除所有元素,但不会释放任何内存。

应该注意的是,vector 类实现了一些特定于其他类型容器的操作:
C++ 容器的一条经验法则是使用 std::vector 作为默认容器,除非有充分的理由使用其他容器。

相关文章