基于范围的for循环在动态数组上的应用?

31

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

for(auto& i : array)

它适用于常量数组,但不适用于基于指针的动态数组,比如

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

它会提示有关替换失败的错误和警告,例如:

错误] C:\Users\Siegfred\Documents\C-Free\Temp\Untitled2.cpp:16:16:错误:没有与'begin(int *&)'匹配的函数调用

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


2
错误信息是什么?请至少发布一个错误。 - default
1
它被称为基于范围的for循环,SO和Google有大量的示例。 - stijn
1
第二个实例是一个打字错误。应该是 for (auto& i: arr) 而不是 array - hmjd
4
当我读到“新的C++动态数组循环”时,我认为“这是一个愚蠢的问题:它只是起作用!”然后我看到了问题,并意识到“哦,发帖者在输入“动态数组”时并不是指‘vector’……” - R. Martinho Fernandes
1
@Default 或许是一个“指向动态分配数组”的指针(有点冗长,但会更明显地显示出原帖中将指针视为数组的错误)。 - Christian Rau
显示剩余3条评论
6个回答

28
为了使用基于范围的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命名空间中。如果您不想篡改标准命名空间,可以简单地创建自己的小型对类并在您的命名空间中重载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;

Example


1
难道没有内置的结构来处理这个吗?任何使用动态C风格数组的人都需要编写自己的wrapped_array结构体吗? - sffc
1
@sffc 目前还没有,但在 C++20 中会有。我在下面的 新答案 中介绍了这个功能。 - Baum mit Augen
1
在命名空间std或任何嵌套在std中的命名空间中添加声明或定义是未定义行为,但以下情况除外。 - Dev Null
糟糕的。. . . . . - Andrew

18

使用动态分配数组时,不能使用范围for循环,因为编译器无法推断数组的开头和结尾。你应该始终使用容器代替它,例如std::vector

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

1
好的,它运行了!我从来不知道C++会如此类型安全,我来自C语言,所以我习惯于以相同的方式操作动态和常量数组。 - Maurice Rodriguez
@MauriceRodriguez 嗯,C 也有数组和指针之间的这种差异。例如,在 Csizeof(array) 返回的结果也完全不同,具体取决于是 int *array = malloc(N*sizeof(int)); 还是 int array[N];。因此,只是因为 C 让你更容易 错误地 忽略了这种差异,而不是这种差异不存在。 - Christian Rau
我不同意你应该总是使用std::vector而不是动态分配的数组。=)有些情况下使用动态版本非常重要。此外,既然我们已经在使用C++11,那么在使用固定大小的合理数组时考虑使用std::array是一个好主意。 - Jamin Grey
请给我一个例子,好吗?当然,std::array也很好,但你必须在编译时知道它的大小。这可能无法涵盖OP的情况。如果您不需要增加数组大小,则可以建议使用std::dynarray(自C++1y以来)作为std::vector的替代品。 - awesoon
@soon dynarray 在 C++14 ("1y") 中被投票淘汰,目前仍然只有实验性状态,因此在生产中可能不应该使用。 - underscore_d
  1. 其他答案展示了如何在动态分配数组的大小已知的情况下进行基于范围的循环,因此“你不能这样做”是一种夸张的说法。
  2. std::vector/std::array并不总是可用(例如,想象一下必须处理遗留接口的情况)。
- Dev Null

11

如果你只有指向动态分配数组第一个元素的指针,那么你无法直接执行基于范围的循环。编译器无法使用其大小信息来执行循环。解决这个问题的C++习惯用法是用std::vector替换动态分配数组:

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

或者,您可以使用一种基于指针和偏移量提供起始和结束迭代器的范围类型。请查看boost.range库中的某些类型,或参考GSL跨度提案(示例实现在此处,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;

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


1
你已经可以使用range-for循环遍历自动分配的裸数组,无需为此添加包装类。 int a[]{1, 2, 3}; for (auto it: a) std::cout << it << ' '; 每个元素计数都创建一个不同的类型,当编译循环时可以推导出其end - underscore_d
  1. 其他答案展示了如何在动态分配数组的大小已知的情况下进行基于范围的循环,因此“你不能这样做”是一种夸张的说法。
  2. std::vector/std::array并不总是可用(例如,想象一下必须处理遗留接口的情况)。
- Dev Null
@DevNull 当然,谢谢。我编辑了答案以澄清我的意思。 - juanchopanza
@DevNull 还有一节关于当你只有一个指针和大小时的替代方案。 - juanchopanza

8
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.1clang 7.0.0 及以上版本。(现场
当然,如果您有选择,最好从一开始就使用 std::vector 而不是 C 风格数组。

1

不要为指针的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;

实时示例.


0

从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';

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接