HOWTO · C++

在 C++ 中比较数组

本文介绍了如何在 C++ 中比较数组。

本页内容

根据输入和所需结果选择比较方法。对于长度相同的定长数组,可以使用循环或 std::equal 返回布尔值。对于字节序列,std::memcmp 比较对象表示。对于向量,std::vector 的 operator== 会比较大小和值。如果需要找到第一个不同的位置,请先检查范围长度,再使用 std::mismatch。

以下示例都是完整的 C++ 程序,应分别编译。提供的运行结果显示,六个示例均成功执行。原始数组参数会退化为指针:接收此类参数的函数无法得知数组长度。请显式传入长度,并确保每个指针指向的范围至少有这么长。

使用循环比较定长数组

循环能明确展示比较规则:逐一检查对应元素,一旦发现一对元素不同,就立即返回 false。如果所有对应元素都相同,则返回 true。调用者会提供两个长度,因此应在按下标访问前先检查长度;此示例遇到长度不同的情况时会直接处理,不会读取任一范围。

本节中的代码以及显示的标签或输出仍为英文。

#include <cstddef>
#include <iostream>

bool equalArrays(const int* left, std::size_t leftSize,
                 const int* right, std::size_t rightSize) {
  if (leftSize != rightSize) return false;
  for (std::size_t i = 0; i < leftSize; ++i) {
    if (left[i] != right[i]) return false;
  }
  return true;
}

int main() {
  const int first[] = {2, 4, 6, 8};
  const int same[] = {2, 4, 6, 8};
  const int different[] = {2, 4, 6, 9};
  constexpr std::size_t count = sizeof(first) / sizeof(first[0]);

  std::cout << std::boolalpha
            << equalArrays(first, count, same, count) << '\n'
            << equalArrays(first, count, different, count) << '\n';
}
true
false

第一次调用会比较所有对应的值并返回 true;第二次调用会在最后一对元素处发现差异并返回 false。只要元素类型的 != 运算符合你的比较需求,就可以采用这种逐元素比较方式。它不会自动比较数组大小,因此应传入实际长度,不要假定两个指针指向的数组长度相同。

编写可复用的比较函数

对于支持不等比较的类型,可以用模板复用循环逻辑。和整数版本一样,函数会显式接收长度,并在读取元素前检查长度。此示例是独立的,可以单独编译。

本节中的代码以及显示的标签或输出仍为英文。

#include <cstddef>
#include <iostream>
#include <string>

template <typename T>
bool equalRanges(const T* left, std::size_t leftSize,
                 const T* right, std::size_t rightSize) {
  if (leftSize != rightSize) return false;
  for (std::size_t i = 0; i < leftSize; ++i) {
    if (left[i] != right[i]) return false;
  }
  return true;
}

int main() {
  const std::string namesA[] = {"Ada", "Lin"};
  const std::string namesB[] = {"Ada", "Lin"};
  std::cout << std::boolalpha
            << equalRanges(namesA, 2, namesB, 2) << '\n';
}
true

模板参数 T 会根据元素类型推导。函数要求 T 支持 !=,但不要求使用特定容器。请让指针与对应长度保持匹配,并确保所指范围仍然有效且长度足够。对于标准容器,如果使用迭代器或容器自身的操作能更清楚地表达边界,就优先采用这些方式。

使用 std::equal 比较范围

std::equal 定义于 <algorithm>,会比较对应元素并返回布尔值。三迭代器重载接收第一个范围的起始迭代器和结束迭代器,以及第二个范围的起始迭代器。调用前应先比较两个范围的长度:第二个范围至少要和第一个范围一样长。若要判断整个范围是否相等,应检查长度相同,这样也能避免读取越过第二个范围。

本节中的代码以及显示的标签或输出仍为英文。

#include <algorithm>
#include <iostream>
#include <vector>

int main() {
  const std::vector<int> left{3, 1, 4};
  const std::vector<int> same{3, 1, 4};
  const std::vector<int> longer{3, 1, 4, 1};

  const bool firstEqual = left.size() == same.size() &&
      std::equal(left.begin(), left.end(), same.begin());
  const bool secondEqual = left.size() == longer.size() &&
      std::equal(left.begin(), left.end(), longer.begin());

  std::cout << std::boolalpha << firstEqual << '\n' << secondEqual << '\n';
}
true
false

长度检查是比较过程的一部分,不是可有可无的优化。std::equal 按顺序比较元素;相同位置的元素必须相等。请确保迭代器范围在使用期间有效,且边界正确。在支持四迭代器重载的语言版本中,可以直接指定两个范围的起止位置;不过,上面的长度检查方式无需依赖该重载。

使用 std::memcmp 比较字节

std::memcmp 定义于 <cstring>,用于比较指定数量的字节。若这些字节相同,它会返回零;若不同,则返回非零值。返回值的符号可以表示按字节比较的顺序,但不要依赖某个确切的非零值。请确保指针有效且指向的范围至少包含指定数量的字节,并以字节而非元素为单位计算长度。

本节中的代码以及显示的标签或输出仍为英文。

#include <cstring>
#include <iostream>

int main() {
  const unsigned char left[] = {0x10, 0x20, 0x30};
  const unsigned char same[] = {0x10, 0x20, 0x30};
  const unsigned char different[] = {0x10, 0x20, 0x31};
  constexpr std::size_t byteCount = sizeof(left);

  std::cout << (std::memcmp(left, same, byteCount) == 0) << '\n'
            << (std::memcmp(left, different, byteCount) == 0) << '\n';
}
1
0

此示例比较字节数组;对于这类数据,逐字节比较可以直接判断列出的字节是否相同。对于一般对象,值相等并不一定意味着对象表示完全相同:填充字节或同一值的多种表示形式,都可能导致 std::memcmp 不适用。普通值应优先使用元素相等比较;只有当数据格式将表示形式是否相同作为比较规则时,才使用字节比较。字节数为零时不会比较任何字节;调用方仍须按照程序需要妥善处理范围有效性。

使用 std::vector::operator== 比较向量

如果需要判断整个向量的值是否相等,可以使用 ==。它会检查大小是否相同,并比较对应元素,因此不必另行检查大小。元素顺序也很重要:即使两个向量包含相同的值,只要顺序不同,它们就不相等。

本节中的代码以及显示的标签或输出仍为英文。

#include <iostream>
#include <vector>

int main() {
  const std::vector<int> first{5, 8, 13};
  const std::vector<int> same{5, 8, 13};
  const std::vector<int> reordered{8, 5, 13};
  const std::vector<int> shorter{5, 8};

  std::cout << std::boolalpha
            << (first == same) << '\n'
            << (first == reordered) << '\n'
            << (first == shorter) << '\n';
}
true
false
false

当两个输入都是 std::vector,且只需要布尔结果时,这是最直接的选择。元素类型必须支持相等比较。如果你的任务不考虑顺序,应明确规定这一点,并采用适合该规则的方法;普通的向量相等比较会按位置比较元素。

使用 std::mismatch 查找第一个差异

如果你需要找到第一对不同的元素,而不只是得到 true 或 false,可以使用 std::mismatch。它的三迭代器重载会同时遍历两个范围,因此应先比较长度,确保两个范围会同时结束。返回的第一个迭代器指向第一个范围中不同的元素;如果它等于第一个范围的结束迭代器,则所有已比较的元素对都相同。

本节中的代码以及显示的标签或输出仍为英文。

#include <algorithm>
#include <iostream>
#include <iterator>
#include <vector>

int main() {
  const std::vector<int> left{7, 8, 9, 10};
  const std::vector<int> right{7, 8, 0, 10};

  if (left.size() != right.size()) {
    std::cout << "Different lengths\n";
    return 0;
  }

  const auto result = std::mismatch(left.begin(), left.end(), right.begin());
  if (result.first == left.end()) {
    std::cout << "Equal\n";
  } else {
    const auto index = std::distance(left.begin(), result.first);
    std::cout << "First mismatch at zero-based index " << index << '\n';
  }
}
First mismatch at zero-based index 2

这里的 std::distance 返回从零开始的偏移量。如果要展示面向读者、从一开始计数的位置,应明确标注为一基位置,并在偏移量上加一。先比较长度还能让结束条件准确表示整个范围是否相等,并确保第二个迭代器不会越过其范围。

选择比较方法

如果你需要明确控制逐元素检查过程,或需要自定义逻辑,可以使用循环。对于迭代器范围的布尔比较,可以使用 std::equal;使用它的三迭代器重载前,应确认范围长度兼容。对于 std::vector,operator== 是简洁的整体容器比较方式。如果需要知道第一个差异的位置,则使用 std::mismatch。

std::memcmp 适用于数据本身就是按字节表示的序列;它不能普遍替代值相等比较。对于所有基于指针的方法,都要确保指针与正确的长度相匹配。条件允许时,优先使用容器大小和迭代器范围;在使用会推进第二个范围迭代器的算法前,确保第二个范围足够长。