C++ 教程:std::vector 初学者指南,第 1 部分

438 阅读13分钟

C++ 教程:std::vector 初学者指南,第 1 部分

C++ 教程旨在帮助初级和中级C++ 程序员更好地理解 C++ 编程语言中的标准模板类。

使用 C++ 向量

**C++ 标准**的最终技术投票于 1997 年 11 月 14 日进行;那是很久以前的事了。然而,标准的重要部分,尤其是标准库,在许多 C++ 用户中仍然不是很受欢迎。经常阅读 CodeGuru 的 C++ 论坛的读者可能会注意到,许多问题和答案仍然暗示着可以使用标准库非常优雅地解决的手工解决方案。

经常出现的一个问题是使用 C 风格的数组,以及它们的所有问题和缺点。人们似乎害怕标准vector和它的兄弟deque。一个原因可能是标准库文档大多是非常省略和深奥的。

在本文中,我将讨论 C++向量并尝试以更易于理解和理解的方式解释它们。我并不声称这篇文章是完整的;它旨在让您开始在C++中使用向量并帮助您避免最常见的陷阱。我们将从小处着手,不会试图非常学术地处理这个话题。

C++中的向量介绍

Vector是一个模板类,它完美地替代了旧的 C 样式数组。它允许使用与普通数组相同的自然语法,但提供了一系列服务,使C++ 程序员无需处理分配的内存,并有助于对包含的对象进行一致的操作。

使用vector 的第一步是包含适当的标题:

#include <vector>

注意头文件名没有任何扩展名;这适用于所有标准库头文件。要知道的第二件事是所有标准库都位于命名空间std 中。这意味着您必须通过在它们前面加上*std::*来解析名称:

std::vector<int> v;    // declares a vector of integers

对于小项目,可以使整个命名空间STD在您的顶部插入using指令到范围。cpp文件:

#include <vector>
using namespace std;
//...
vector<int> v;         // no need to prepend std:: any more

这对于小型项目是可以的,只要您在 .cpp 文件中编写 using 指令即可。切勿将 using 指令写入头文件!这会将整个命名空间std膨胀到每个包含该标头的 .cpp 文件中。对于较大的项目,最好相应地明确限定每个名称。我不喜欢这种捷径。在本文中,我将相应地限定每个名称。我将在示例中适当地引入一些typedef以提高可读性。

什么是 C++ 中的 std::vector

什么是std::vector v; ? 它是一个模板类,它将包装一个 T 数组。在这种广泛使用的表示法中,“T”代表任何数据类型、内置类或用户定义类。该向量会将 T 存储在它会为您处理的连续内存区域中,并让您只需通过编写v[0]v[1] 等来访问各个 T ,就像您对 C 所做的一样样式数组。

请注意,对于较大的项目,重复写出向量的显式类型可能会很乏味。如果需要,您可以使用typedef

typedef std::vector<int> int_vec_t;    // or whatever you
                                       // want to name it
//...
int_vec_t v;

千万不能使用宏!

#define int_vec_t std::vector<int> ;    // very poor style!

首先,让我们看看向量可以为我们做什么。让我们从小处开始,以整数数组为例。如果您使用普通数组,则您有一个静态或动态数组:

size_t size = 10;
int sarray[10];
int *darray = new int[size];
// do something with them:
for(int i=0; i<10; ++i){
    sarray[i] = i;
    darray[i] = i;
}
// don't forget to delete darray when you're done
delete [] darray;

让我们使用vector做同样的事情:

#include <vector>
//...
size_t size = 10;
std::vector<int&gt array(size);    // make room for 10 integers,
                                 // and initialize them to 0
// do something with them:
for(int i=0; i<size; ++i){
    array[i] = i;
}
// no need to delete anything

如您所见,vector结合了静态和动态数组的优点,因为它采用动态数组等非常量大小参数,并像静态数组一样自动删除已使用的内存。

标准向量定义了运算符 [],以允许“自然”语法。出于性能考虑,*运算符 []*不会检查索引是否有效。与 C 风格的数组类似,使用无效索引通常会导致访问冲突。

除了operator [] 之外vector 还定义了成员函数at()。此函数与运算符 []执行相同的操作,但会检查索引。如果索引无效,它将抛出类std::out_of_range的对象。

std::vector<int&gt array;
try{
    array.at(1000) = 0;
}
catch(std::out_of_range o){
    std::cout<<o.what()<<std::endl;
}

根据您使用的C++ 标准库的实现,上面的代码段将打印或多或少明确的错误消息。STLPort 打印出“vector”这个词,Visual C++ 自带的 Dinkumware 实现打印出“invalid vector”下标”。其他实现可能会打印其他内容。

请注意,vector是一个标准容器。也可以使用迭代器访问受控序列。本文稍后将详细介绍迭代器。现在,让我们保持简单。

现在,如果您不知道将拥有多少个元素怎么办?如果您使用 C 风格的数组来存储元素,您要么需要实现一个逻辑,允许开发人员不时扩展您的数组,要么您将分配一个“足够大”的数组。后者是穷人的做法,前者会让你头疼。不是那么向量

#include <vector>
#include <iostream>
//...
std::vector<char> array;
char c = 0;
while(c != 'x'){
  std::cin>>c;
  array.push_back(c);
}

在前面的示例中,push_back()一次向数组追加一个元素。这是我们想要的,但它有一个小陷阱。要了解那是什么,您必须知道向量具有所谓的“受控序列”和为该序列分配的一定数量的存储空间。受控序列只是vector 内部数组的另一个名称。为了保存这个数组,vector会分配一些内存,大部分是超出它需要的。您可以push_back()元素,直到分配的内存用完。然后,向量将触发重新分配并增加分配的内存块。这可能意味着它必须将受控序列移动(即:复制)到更大的块中。复制大量元素会显着降低应用程序的速度。请注意,重新分配对您来说是绝对透明的(除非发生灾难性故障——内存不足)。你什么都不用做;vector会做所有的事情。当然,您可以采取一些措施来避免vector过于频繁地重新分配存储。请继续阅读。

在前面的示例中,我们使用其默认构造函数声明了向量。这将创建一个空向量。根据正在使用的标准库的实现,空向量可能会或可能不会“以防万一”分配一些内存。如果我们想避免频繁地重新分配vector的存储空间,我们可以使用其*Reserve()*成员函数:

#include <vector>
#include <iostream>
//...
std::vector<char> array;
array.reserve(10);    // make room for 10 elements
char c = 0;
while(c != 'x'){
  std::cin>>c;
  array.push_back(c);
}

当然,我们传递给*Reserve()*的参数取决于上下文。在这种情况下,reserve()函数将确保我们有至少 10 个元素的空间。如果向量已经有空间容纳所需数量的元素,*reserve()*什么也不做。换句话说,如果需要reserve() 将增加 *vector* 的分配存储空间,但永远不会缩小它。

作为旁注,以下两个代码片段不是一回事:

// snip 1:
std::vector<int> v(10);
// snip 2:
std::vector<int> v;
v.reserve(10);

第一个片段定义了一个包含 10 个整数的向量,并用它们的默认值 (0) 初始化它们。如果我们没有整数而是一些用户定义的类,vector将调用默认构造函数 10 次并包含 10 个易于构造的对象。第二个片段定义了一个空的vector,然后告诉它为 10 个整数腾出空间。该向量将分配足够的内存以容纳至少 10 个整数,但不会初始化此内存。如果我们没有整数,但有一些用户定义的类,那么第二个片段将不会构造该类的任何实例。

要找出向量当前分配的存储中适合多少元素,请使用capacity()成员函数。要了解vector当前包含多少个元素,请使用*size()*成员函数:

#include <vector>
#include <iostream>
//...
std::vector<int> array;
int i = 999;          // some integer value
array.reserve(10);    // make room for 10 elements
array.push_back(i);
std::cout<<array.capacity()<<std::endl;
std::cout<<array.size()<<std::endl;

这将打印

10
1

这意味着可以在不触发重新分配的情况下添加到向量的元素数量始终是capacity() – size()

请注意,对于前面的示例,只有 0 是array的有效索引。是的,我们使用Reserve()为至少 10 个元素腾出了空间,但是内存没有初始化。因为int是一种内置类型,所以用运算符 []写入所有 10 个元素实际上是可行的,但是我们会有一个处于不一致状态的向量,因为*size()仍然会返回 1。此外,如果我们尝试访问使用array.at()*的第一个元素以外的其他元素,a

std::out_of_range*将被抛出。乍一看,这似乎很不方便,但仔细观察就会发现为什么会这样:如果向量*包含用户定义类的对象,*reserve()*不会调用任何 ctor。访问尚未构造的对象会产生未定义的结果,并且在任何情况下都是禁忌。要记住的重要一点是,*reserve()的作用是最小化潜在重新分配的数量,并且它不会影响受控序列中的元素数量。使用小于当前容量()的参数调用reserve()*是良性的——它只是什么都不做。

扩大包含元素数量的正确方法是调用vector的成员函数resize()。成员函数*resize()*具有以下属性:

  • 如果新的大小大于vector的旧大小,它将保留控制序列中已经存在的所有元素;其余的将根据第二个参数进行初始化。如果新大小小于旧大小,它将只保留第一个 new_size 元素。其余的被丢弃,不应再使用——认为这些元素无效。
  • 如果新大小大于capacity(),它将重新分配存储空间,以便所有 new_size 元素都适合。resize()永远不会缩小capacity()

例子:

std::vector<int> array;   // create an empty vector
array.reserve(3);         // make room for 3 elements
                          // at this point, capacity() is 3
                          // and size() is 0
array.push_back(999);     // append an element
array.resize(5);          // resize the vector
                          // at this point, the vector contains
                          // 999, 0, 0, 0, 0
array.push_back(333);     // append another element into the vector
                          // at this point, the vector contains
                          // 999, 0, 0, 0, 0, 333
array.reserve(1);         // will do nothing, as capacity() > 1
array.resize(3);          // at this point, the vector contains
                          // 999, 0, 0
                          // capacity() remains 6
                          // size() is 3
array.resize(6, 1);       // resize again, fill up with ones
                          // at this point the vector contains
                          // 999, 0, 0, 1, 1, 1

扩大受控元素数量的另一种方法是使用push_back()。在某些情况下,这可能比调用resize()然后写入元素更有效。通过查看以下示例,让我们仔细看看vector的底层结构:

class X
{
public:
    X():val_(0){}
    X(int val):val_(val){}
    int get(){return val_;}
    void set(int val){val_=val;}
private:
    int val_;
};
//....
std::vector<X> ax;         // create an empty vector containing
                           // objects of type class X
// version 1:
ax.resize(10);             // resize the controlled sequence
for(int i=0; i<10; ++i){
    ax[i].set(i);          // set each element's value
}
//...
// version 2:
ax.reserve(10);            // make room for 10 elements
for(int i=0; i<10; ++i){
    ax.push_back(X(i));    // insert elements using the second ctor
}

这两个版本是等效的,这意味着它们将产生相同的结果。在这两种情况下,我们都从一个空的vector开始。在第一个版本中,我们使用resize()将受控序列的大小增加到 10 个元素。这不仅会重新分配vector的存储空间,还会使用 X 的默认 ctor 构造一个 10 个元素的序列。当resize()完成后,我们的vector中将有 10 个 X 类型的有效对象,所有这些对象有val_ == 0,因为这就是 X 的默认 ctor 所做的。第二步,我们选择序列中的每个 X 并使用X::set()更改其val_

在第二个版本中,我们调用reserve()来为 10 个元素腾出空间。该载体将重新分配它的存储,什么也不做不止于此。尚未构造任何元素。第二步,我们使用第二个构造函数创建 10 个 X 类型的对象,从而直接为它们提供正确的值,并将它们*push_back()*放入向量中。

哪种方法更有效?这可能还取决于标准库的实现,但第二个版本可能会稍微高效一些,因为它不会为每个元素调用X::set()

现在我们已经看到了如何声明一个向量以及如何填充它,让我们看看我们如何对其进行操作。我们将从对 C 风格数组的类比开始,并逐渐发现其他更好或更安全的可能性。

有两种访问 C 样式数组的方法:使用下标运算符或使用指针。此外,将 C 样式数组传递给函数意味着传递指向第一个元素的指针。我们可以用向量做同样的事情吗?答案是肯定的。让我们举一个小例子:

#include <iostream>

double mean(double *array, size_t n)
{
    double m=0;
    for(size_t i=0; i<n; ++i){
        m += array[i];
    }
    return m/n;
}

int main()
{
    double a[] = {1, 2, 3, 4, 5};
    std::cout<<mean(a, 5)<<std::endl;    // will print 3
    return 0;
}

当我们说mean(a, 5) 时,第一个参数实际上是数组*&a[0] 中第一个元素的地址。我们知道向量需要按顺序将其元素保存在连续的内存块中。这意味着我们可以将向量的第一个元素的地址传递给函数mean()*并且它会起作用:

int main()
{
    std::vector<double> a;
    a.push_back(1);
    a.push_back(2);
    a.push_back(3);
    a.push_back(4);
    a.push_back(5);
    std::cout<<mean(&a[0], 5)<<std::endl;    // will print 3
    return 0;
}

这很好,但它仍然不完全相同。我们能够直接初始化 C 风格的数组,但我们必须将元素push_back()放入向量中。我们能做得更好吗?嗯,是。我们不能直接vector使用初始化列表,但我们可以使用一个中间数组:

double p[] = {1, 2, 3, 4, 5};
std::vector<double> a(p, p+5);

这里我们使用vector提供的另一个构造函数。它需要两个参数:一个指向 C 样式数组第一个元素的指针和一个指向该数组最后一个元素之后的指针。它将使用数组中每个元素的副本初始化向量。有两件事需要注意:数组被复制,它不会以某种方式进入新创建的vector的拥有,我们提供的范围是从数组中的第一个元素到最后一个元素之后的一个元素。

在使用vector或任何其他标准容器时,理解第二点至关重要。受控序列总是用**[first, one-past-last)**表示——不仅对于 ctors,而且对于对一系列元素进行操作的每个函数。

在获取vector 中包含的元素的地址时,您必须注意一些事项:vector的内部重新分配将使您持有的指向其元素的指针无效。

std::vector<int> v(5);
int *pi = &v[3];
v.push_back(999); // <-- may trigger a reallocation
*pi = 333;        // <-- probably an error, pi isn't valid any more

在前面的示例中,我们将向量的第四个元素的地址存储在pi 中。然后我们push_back()另一个元素到vector的末尾。然后我们尝试使用pi。繁荣!原因是push_back()可能会触发v内部存储的重新分配,如果这也不足以容纳额外的元素。pi将指向一个刚刚被删除的内存地址,使用它会产生未定义的结果。坏消息是向量可能会或可能不会重新分配内部存储——您无法判断一般情况。解决方案要么不使用可能已失效的指针,要么确保向量不会重新分配。后者意味着明智地使用Reserve()以便在定义的时间让向量处理内存(重新)分配。

从我们目前看到的成员函数来看,只有push_back()resize()可以使指向vector 的指针无效。还有其他使指针无效的成员函数;我们将在本教程后面讨论它们。

请注意,下标运算符和成员函数at()都不会使指向vector 的指针无效。

说到向量中的指针,我们可以在这一点上引入一个标准概念:迭代器。迭代器是标准库为所有容器(vector、list、set、deque 等)建模通用接口的方式。原因是对一个容器“自然”的操作(如vector 的下标)对其他容器没有意义。标准库需要一种通用的方法来将迭代、查找、排序等算法应用于所有容器——因此有了迭代器的概念。

迭代器是包含元素的句柄。如果需要,您可以在您最喜欢的教科书中找到确切的定义。迭代器的内部表示在这一点上是无关紧要的。重要的是,如果你有一个迭代器,你可以取消引用它以获得它“指向”的元素(对于vector最自然的迭代器实现确实是一个普通的普通指针——但不要指望这一点)。让我们通过一个小例子来了解迭代器:

#include <vector>
#include <iostream>

int main()
{
    std::vector<double> a;
    std::vector<double>::const_iterator i;
    a.push_back(1);
    a.push_back(2);
    a.push_back(3);
    a.push_back(4);
    a.push_back(5);
    for(i=a.begin(); i!=a.end(); ++i){
        std::cout<<(*i)<<std::endl;
    }
    return 0;
}

让我们一步一步来看看这个小程序:

std::vector<double>::const_iterator i;

这为向量声明了一个常量迭代器i. 我们使用 const 迭代器是因为我们不打算修改vector的内容。

...i=a.begin();...

成员函数*begin()*返回一个“指向”序列中第一个元素的迭代器。

...i!=a.end();...

成员函数*end()返回一个“指向”序列中最后一个元素的迭代器。请注意,取消引用end()*返回的迭代器是非法的并且具有未定义的结果。

...++i

您可以通过递增迭代器从一个元素前进到下一个元素。

请注意,相同的程序,但使用指针而不是迭代器,导致非常相似的构造:

#include <vector>
#include <iostream>

int main()
{
    std::vector<double> a;
    const double *p;
    a.push_back(1);
    a.push_back(2);
    a.push_back(3);
    a.push_back(4);
    a.push_back(5);
    for(p=&a[0]; p!=&a[0]+5; ++p){
        std::cout<<(*p)<<std::endl;
    }
    return 0;
}

那么,如果我们可以使用指针以相同的方式基本上实现相同的事情,那为什么还要使用迭代器呢?答案是,如果我们想对向量应用一些标准算法,比如排序,我们必须使用迭代器。标准库没有将算法作为各种容器的成员函数来实现,而是作为可以在许多容器上运行的自由模板函数来实现。

标准容器(尤其是向量)和标准算法的结合是一个非常强大的工具;不幸的是,很多时候被程序员忽视了。通过使用它,您可以避免大量手工编写的、容易出错的代码,并且它使您能够编写紧凑、可移植和可维护的程序。

我们来看看vector提供的成员函数:

C++ 向量函数:构造函数

提供了一套完整的**C++ 构造函数C++ 析构函数**和复制运算符。让我们以标准字符串向量为例来看看它们:

typedef std::vector<std::string&gt str_vec_t;
str_vec_t v1;                       // create an empty vector
str_vec_t v2(10);                   // 10 copies of empty strings
str_vec_t v3(10, "hello");          // 10 copies of the string
                                    // "hello"
str_vec_t v4(v3);                   // copy ctor

    std::list<std::string> sl;      // create a list of strings
                                    // and populate it
    sl.push_back("cat");
    sl.push_back("dog");
    sl.push_back("mouse");

str_vec_t v5(sl.begin(), sl.end()); // a copy of the range in
                                    // another container
                                    // (here, a list)

v1 = v5;                            // will copy all elements
                                    // from v5 to v1

C++中的*assign()*函数

的*分配()*函数将重新初始化向量。我们可以使用 [first, last) 迭代器传递有效的元素范围,也可以指定要创建的元素数量和元素值。

v1.assign(sl.begin(), sl.end());    // copies the list into
                                    // the vector
v1.assign(3, "hello");              // initializes the vector
                                    // with 3 strings "hello"

赋值完全改变了向量的元素。旧元素(如果有)被丢弃,向量的大小设置为分配的元素数。当然,*assign()*可能会触发内部重新分配。

C++ 中的堆栈操作

我们已经看到了函数push_back()。它将一个元素附加到受控序列的末尾。有一个对应函数pop_back(),它删除受控序列中的最后一个元素。删除的元素无效,并且*size()递减。请注意pop_back()*不返回弹出元素的值。在你弹出它之前你必须偷看它。这样做的原因是异常安全。弹出一个空向量是一个错误,并有未定义的结果。

std::vector<int> v;
v.push_back(999);
v.pop_back();

请注意,pop_back()不会缩小capacity()

C++ 中的预定义迭代器

我们已经看到了迭代器begin()end()。它们分别指向第一个,指向受控序列中的最后一个元素。还有rbegin()rend()分别指向第一个,指向反向序列的最后一个元素。请注意,rbegin()rend() 都返回类型reverse_iterator(或const_reverse_iterator对于它们的 const 版本)——这与iterator 不同,(分别是const_iterator)。要从反向迭代器获得“正常”迭代器,请使用reverse_iteratorbase() 成员函数:

std::vector<int> v;
v.push_back(999);
std::vector<int>::reverse_iterator r = v.rbegin();
std::vector<int>::iterator i = r.base(); // will point to the last
                                         // element in the sequence

C++ 中的元素访问

我们已经看到了提供非检查访问的下标运算符 []和成员函数at(),如果传递的索引无效,它将抛出一个std::out_of_range类型的对象。存在另外两个成员函数,front()back(),它们分别返回对受控序列中第一个或最后一个元素的引用。请注意,它们不返回迭代器!

std::vector<int> v;
v.push_back(999);
// fill up the vector
//...
// following statements are equivalent:
int i = v.front();
int i = v[0];
int i = v.at(0);
int i = *(v.begin());
// following statements are equivalent:
int j = v.back();
int j = v[v.size()-1];
int j = v.at(v.size()-1);
int j = *(v.end()-1);

请注意,我们不能写**(–v.end())因为v.end()不是左*值。

C++ 中的列表操作

vector提供的一些操作实际上是list原生的。它们由大多数容器提供,用于处理在受控序列中间插入和擦除元素。让我们通过一些例子来演示它们:

#include <vector>
#include <iostream>
int main()
{
    std::vector<int> q;
    q.push_back(10); q.push_back(11); q.push_back(12);

    std::vector<int> v;
    for(int i=0; i<5; ++i){
        v.push_back(i);
    }
    // v contains 0 1 2 3 4

    std::vector<int>::iterator it = v.begin() + 1;
    // insert 33 before the second element:
    it = v.insert(it, 33);
    // v contains 0 33 1 2 3 4
    // it points to the inserted element

    //insert the contents of q before the second element:
    v.insert(it, q.begin(), q.end());
    // v contains 0 10 11 12 33 1 2 3 4
    // iterator 'it' is invalid

    it = v.begin() + 3;
    // it points to the fourth element of v
    // insert three time -1 before the fourth element:
    v.insert(it, 3, -1);
    // v contains 0 10 11 -1 -1 -1 12 33 1 2 3 4
    // iterator 'it' is invalid

    // erase the fifth element of v
    it = v.begin() + 4;
    v.erase(it);
    // v contains 0 10 11 -1 -1 12 33 1 2 3 4
    // iterator 'it' is invalid

    // erase the second to the fifth element:
    it = v.begin() + 1;
    v.erase(it, it + 4);
    // v contains 0 12 33 1 2 3 4
    // iterator 'it' is invalid

    // clear all of v's elements
    v.clear();

    return 0;
}

请注意,insert()erase()都可能使您可能持有的任何迭代器无效。insert()的第一个版本返回一个指向插入元素的迭代器。其他两个版本返回void。插入元素可能会触发重新分配。在这种情况下,容器中的所有迭代器都将失效。如果没有发生重新分配(例如,通过在插入之前调用Reserve()),则只有在插入点和序列末尾之间打印的迭代器才会无效。

擦除元素永远不会触发重新分配,也不会影响capacity()。但是,指向第一个被擦除元素和序列末尾之间的所有迭代器都将变得无效。

调用*clear()*从受控序列中删除所有元素。但是,分配的内存不会被释放。当然,所有迭代器都将失效。

请注意,insert()erase()向量s都不是很有效。预计它们将在摊销线性时间 O(n)+ 内执行。如果您的应用程序经常使用插入和擦除,那么vector可能不是您容器的最佳选择。

C++ 中的比较操作

您可以使用运算符*==、!=<在逐个元素的基础上比较两个向量的内容。如果两个向量具有相同的size()并且元素相应地相等,则两个向量相等。请注意,两个相等向量capacity()不必相同。运算符 < 按字典顺序对向量进行*排序。

std::vector<int> v1, v2;
//...
if(v1 == v2) ...

使用 C++ 交换向量中的内容

有时,能够交换()两个向量的内容是可行的。一个常见的应用是强制向量释放它持有的内存。我们已经看到擦除元素或清除向量不会影响它的容量()(换句话说,分配的内存)。我们需要做一个小技巧:

std::vector<int> v;
//...
v.clear();
v.swap(std::vector<int>(v));

通常(见下文),vector只是交换他们的胆量。在前面的示例中,我们通过使用复制构造函数并将其内容与v**交换()来创建一个临时向量。临时对象将接收v持有的全部内存,而v将接收临时对象持有的内存——它可能在创建时不分配任何内容。临时创建的vector在上述语句结束时被销毁,v正式持有的所有内存都被释放。

向量类模板有一个第二,默认模板参数:

template<class T, class A = allocator<T> &gt
        class vector ...

分配器是供给由容器中使用的功能,以分配和释放存储器,用于其元素的类。在本教程中,我们假设我们有一个默认分配器,我们将继续假设这一点。为了完整起见,请注意如果两个分配器相同,则swap()将在恒定时间内执行(只需交换两个向量的内脏)。大多数情况下都是如此。

在第一个教程中,我们触及了标准库的表面并遇到了std::vector。在下一个教程中,我们将查看与vector相关的更高级主题,分别对其应用标准算法,并将讨论设计决策,例如何时存储对象以及何时存储指向vector 中的对象的指针的问题. 我们还将介绍vector 的近亲,即std::deque