HOWTO · C++

如何在 C++ 中创建向量的向量

本文介绍如何在 C++ 中创建指定行列数的向量的向量,并填充和修改其中的元素。

本页内容

向量的向量是一个外层 std::vector,其中每一行都是一个 std::vector 对象。若要创建行数和列数已知的二维结构,可以用行数构造外层向量,并用列数和初始值构造每个内层向量。下面的示例会演示如何创建指定尺寸的网格、填入样例值,以及更新其中的元素。当程序需要每一行都是一个向量,且行数或列数由程序决定时,这种结构很实用。

使用大小和值构造函数在 C++ 中创建向量的向量

大小和值构造函数会明确指定两个维度以及初始内容。例如,std::vector<std::vector<int>> grid(rows, std::vector<int>(columns, 0)); 会创建 rows 个内层向量,每个向量包含 columns 个初始值为零的整数。内层向量先构造一次,再作为用于初始化外层向量各行的值。这是同时指定大小和初始值的定尺寸构造,而不是默认构造。

下面的示例创建四行、每行六个整数。向量索引从零开始:[2][2] 表示第三行第三列;嵌套的 .at() 调用则访问第四行第四列。.at() 会分别检查每一层索引是否小于对应向量的大小;如果索引越界,就会报告越界访问。已知索引有效时,方括号写法更简洁。

#include <iostream>
#include <vector>

int main() {
  constexpr int LENGTH = 4;
  constexpr int WIDTH = 6;
  std::vector<std::vector<int>> grid(LENGTH, std::vector<int>(WIDTH, 0));

  grid[2][2] = 12;
  std::cout << grid[2][2] << '\n';

  grid.at(3).at(3) = 99;
  std::cout << grid[3][3] << '\n';
  return 0;
}
12
99

输出由这两次赋值决定。行索引和列索引应分别对应各自的维度:外层向量用于选择行,该行的内层向量用于选择列。如果尺寸并非固定值,也可以从程序数据中取得行数以及每一行的大小。

使用 rand 函数在 C++ 中为向量的向量填入样例值

网格创建好后,可以用嵌套循环遍历每个整数并为它赋一个样例值。下面的程序以当前时间作为 rand 的种子,并使用 % 100 生成 0 到 99 之间的值。每次运行打印出的值都可能不同,因此没有一份固定输出可供照抄。示例包含了 rand 和 srand 所需的 <cstdlib>,以及 std::time 所需的 <ctime>。

#include <cstdlib>
#include <ctime>
#include <iostream>
#include <vector>

int main() {
  constexpr int LENGTH = 4;
  constexpr int WIDTH = 6;
  std::vector<std::vector<int>> grid(LENGTH, std::vector<int>(WIDTH, 0));

  std::srand(static_cast<unsigned int>(std::time(nullptr)));
  for (auto& row : grid) {
    for (int& value : row) {
      value = std::rand() % 100;
      std::cout << value << ' ';
    }
    std::cout << '\n';
  }
  return 0;
}

这里生成的只是用于演示和小型非敏感任务的样例数据。rand 不适合用于加密场景。取余运算也可能使结果分布产生偏差,除非生成器可能产生的值的数量能够被 100 整除。对于模拟或应用程序中需要更好分布特性的场景,应选择符合需求的随机数工具,不要把这个简短示例当作通用随机数生成器。

使用嵌套范围 for 循环修改 C++ 向量的向量中的每个元素

外层范围 for 循环遍历每一行,内层循环遍历该行中的每个整数。在 int& value 中,& 表示 value 是对已存储元素的引用,因此赋值会修改向量中的元素。如果不使用引用,循环变量就是副本;修改它不会改变向量中保存的整数。下面的程序会分别打印网格在每个元素乘以三之前和之后的内容。

#include <iostream>
#include <vector>

void print(const std::vector<std::vector<int>>& grid) {
  for (const auto& row : grid) {
    for (int value : row) {
      std::cout << value << ' ';
    }
    std::cout << '\n';
  }
}

int main() {
  std::vector<std::vector<int>> grid{{1, 2, 3}, {4, 5, 6}};
  std::cout << "Before:\n";
  print(grid);

  for (auto& row : grid) {
    for (int& value : row) {
      value *= 3;
    }
  }

  std::cout << "After:\n";
  print(grid);
  return 0;
}

每个内层向量管理自己的一组整数,外层向量则存储这些行向量。同一行中的整数在内存中连续,但整个嵌套结构并不是一个连续的二维内存块。这只是对存储布局的说明,并不意味着可以笼统地比较速度:性能取决于具体操作和工作负载。当各行天然独立,或需要使用普通向量操作时,嵌套向量很实用。如果任务明确要求所有元素位于同一连续内存块中,应选择专为这种布局设计的表示方式。