动态数组上基于范围的for循环

Range-based for loop on a dynamic array?

本文关键字:范围 for 循环 数组 于范围 动态      更新时间:2023-10-16

有一个基于范围的for循环,语法为:

for(auto& i : array)

可用于常量数组,但不能用于基于指针的动态数组,如

int *array = new int[size];
for(auto& i : array)
   cout<< i << endl;

给出替换失败的错误和警告,例如:

错误]C:UsersSiegfredDocumentsC-FreeTemp untitlet2 .cpp:16:16:错误:调用'begin(int*&)'没有匹配的函数

如何在动态数组中使用这种新语法?

要使用基于范围的for循环,您必须提供begin()end()成员函数或重载非成员begin()end()函数。在后一种情况下,您可以将范围包装在std::pair中,并为那些重载begin()end():

    namespace std {
        template <typename T> T* begin(std::pair<T*, T*> const& p)
        { return p.first; }
        template <typename T> T* end(std::pair<T*, T*> const& p)
        { return p.second; }
    }

现在你可以像这样使用for循环:

    for (auto&& i : std::make_pair(array, array + size))
        cout << i << endl;

注意,非成员begin()end()函数必须在这里的std命名空间中重载,因为pair也驻留在命名空间std中。如果您不想篡改标准名称空间,您可以简单地创建自己的小pair类,并在名称空间中重载begin()end()

或者,在动态分配的数组周围创建一个薄包装器,并提供begin()end()成员函数:

    template <typename T>
    struct wrapped_array {
        wrapped_array(T* first, T* last) : begin_ {first}, end_ {last} {}
        wrapped_array(T* first, std::ptrdiff_t size)
            : wrapped_array {first, first + size} {}
        T*  begin() const noexcept { return begin_; }
        T*  end() const noexcept { return end_; }
        T* begin_;
        T* end_;
    };
    template <typename T>
    wrapped_array<T> wrap_array(T* first, std::ptrdiff_t size) noexcept
    { return {first, size}; }

您的呼叫站点看起来像这样:

    for (auto&& i : wrap_array(array, size))
         std::cout << i << std::endl;
示例

不能对动态分配的数组使用range-for-loop,因为编译器不能推断该数组的开始和结束。您应该始终使用容器而不是它,例如std::vector

std::vector<int> v(size);
for(const auto& elem: v)
    // do something

你不能在动态分配的数组上直接执行基于范围的循环,因为你只有一个指向第一个元素的指针。没有关于它的大小的信息,编译器可以使用它来执行循环。惯用的c++解决方案是将动态分配的数组替换为std::vector:

std::vector<int> arr(size);
for(const auto& i : arr)
  std::cout<< i << std::endl;

也可以使用range类型,该类型提供基于指针和偏移量的开始和结束迭代器。看看助推中的一些类型。range库,或GSL span建议(此处为示例实现,此处为c++ 20建议类型的参考)。


请注意,基于范围的for循环适用于固定大小的普通数组的std::array对象:

std::array<int,10> arr;
for(const auto& i : arr)
  std::cout<< i << std::endl;
int arr[10] = .... ;
for(const auto& i : arr)
  std::cout<< i << std::endl;

,但在这两种情况下,大小都需要是编译时常量

c++ 20增加了std::span,允许这样的循环:

#include <iostream>
#include <span>
int main () {
    auto p = new int[5];
    for (auto &v : std::span(p, 5)) {
        v = 1;
    }
    for (auto v : std::span(p, 5)) {
        std::cout << v << 'n';
    }
    delete[] p;
}

当前编译器支持,例如gcc 10.1和clang 7.0.0及更高版本。(生活)

当然,如果可以选择,最好从一开始就使用std::vector而不是c风格的数组。

代替为指针的std::pair定义std::beginstd::end(顺便说一下,在std::中定义它们是未定义的行为)并推出您自己的包装器,如前所述,您可以使用boost::make_iterator_range:

size_t size = 16;
int *dynamic_array = new int[size];
for (const auto& i : boost::make_iterator_range(dynamic_array, dynamic_array + size))
    std::cout << i << std::endl;

生活例子。

在c++ 20视图中,我们也可以使用子范围(std::ranges::subrange)

auto p = new int[5];
//INPUT DATA TO TEST
int i = 0;
for (auto& v : std::ranges::subrange(p, p + 5)) {
    v = ++i;
}
//USE SUBRANGE
for (auto v : std::ranges::subrange(p, p + 5))
    std::cout << v << 'n';