内部函数范围错误

Error with ranged for inside function

我在使用 C++ 中的范围 for 时遇到了一些麻烦。我试图用它来显示元素和 int 数组 (int[]),当我在 main 函数上这样做时它工作得很好,比如:

int main(int argc, char const *argv[]) {

  int v[] = {3, 4, 6, 9, 2, 1};

  for (auto a : v) {
      std::cout << a << " ";
  }
  std::cout << std::endl;

  return 0;
}

我得到了我想要的和预期的输出:

3 4 6 9 2 1

但是当我尝试在函数内部使用范围 for 时,事情变得有点奇怪,例如,我遇到了以下代码的问题:

void printList(int *v);

int main(int argc, char const *argv[]) {

  int v[] = {3, 4, 6, 9, 2, 1};

  printList(v);

  return 0;
}

void printList(int *v) {
  for (auto a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}

这对我来说与我在 main 中所做的相同,并且使用正常的 for 也完全正常。奇怪的错误如下:

p4.cpp: In function ‘void printList(int*)’:
p4.cpp:15:17: error: ‘begin’ was not declared in this scope
   for (auto a : v) {
                 ^
p4.cpp:15:17: note: suggested alternative:
In file included from /usr/include/c++/5/string:51:0,
                 from /usr/include/c++/5/bits/locale_classes.h:40,
                 from /usr/include/c++/5/bits/ios_base.h:41,
                 from /usr/include/c++/5/ios:42,
                 from /usr/include/c++/5/ostream:38,
                 from /usr/include/c++/5/iostream:39,
                 from p4.cpp:1:
/usr/include/c++/5/bits/range_access.h:105:37: note:   ‘std::begin’
   template<typename _Tp> const _Tp* begin(const valarray<_Tp>&);
                                     ^
p4.cpp:15:17: error: ‘end’ was not declared in this scope
   for (auto a : v) {
                 ^
p4.cpp:15:17: note: suggested alternative:
In file included from /usr/include/c++/5/string:51:0,
                 from /usr/include/c++/5/bits/locale_classes.h:40,
                 from /usr/include/c++/5/bits/ios_base.h:41,
                 from /usr/include/c++/5/ios:42,
                 from /usr/include/c++/5/ostream:38,
                 from /usr/include/c++/5/iostream:39,
                 from p4.cpp:1:
/usr/include/c++/5/bits/range_access.h:107:37: note:   ‘std::end’
   template<typename _Tp> const _Tp* end(const valarray<_Tp>&);
                                     ^

我想知道为什么会发生这个错误,我认为这可能发生的原因是,因为我是数组的指针表示,所以一些信息丢失了,但为什么这些信息会丢失我不知道不知道。有人深入了解吗?我也在寻找这个替代解决方案:

template <std::size_t len>
void printList(int (&v)[len]) {
  for (int a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}

效果很好,但如果我使用类似的东西:

template <std::size_t len>
void printList(int (&v)[len]);

int main(int argc, char const *argv[]) {
           .........
}

void printList(int (&v)[len]) {
  for (int a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}

我收到错误:

p4.cpp:15:25: error: ‘len’ was not declared in this scope
 void printList(int (&v)[len]) {
                         ^
p4.cpp: In function ‘void printList(...)’:
p4.cpp:16:16: error: ‘v’ was not declared in this scope
   for (int a : v) {

为什么会这样?有没有不使用模板格式的简单解决方案?有没有一种方法可以使用参数来传递数组和隐式大小信息?

此类 for 循环使用 beginend 成员函数来确定序列的起点和终点。

例如,std::vector确实有这些功能,因此支持这种迭代。事实上,指针只是表示内存地址的整数,没有这些函数,这使得无法遍历指针(这本身没有多大意义)以这种方式。

您可以像这样进行迭代:

void printList(int *begin, int *end) {
    for(; begin < end; ++begin)
        std::cout << *begin;
    std::cout << std::endl;
}

这在你的情况下 main 内有效,因为 数组 确实有 beginend 作为 的大小该数组是已知的 。但是,将数组传递给函数会使它衰减为指针。

数组和指针的区别很大

您可以使用基于范围的 for 迭代数组,因为数组的大小在编译时是已知的。但是,您传递给函数的是指向数组第一个元素的指针。此步骤的大小未知,这就是基于范围的 for 失败的原因。

在你的第二个模板示例中,问题是,你在 printList 的定义中忘记了 template <std::size_t len>,所以你有 2 个不同的函数,模板化的和非模板化的,它被调用。

在这种情况下 - 我建议使用更现代的 std::array

main 中的数组大小已知。

一旦传递给 printList 函数,它就会 衰减 指向 int 的指针,因此会出现错误。

"the reason I think that this be may happening is, since I'm the pointer representation of the array some information is lost, but why this information is lost I don't know."

是的。数组很容易退化为指针,而指针不知道数组长度。需要基于范围的 for 循环来评估数据类型的 begin()end()
我的建议是避免使用 C 样式数组,而是使用 std::array

#include <iostream>
#include <array>

void printList(std::array<int,6> const& v);

int main(int argc, char const *argv[]) {

  std::array<int,6> v{{3, 4, 6, 9, 2, 1}};
  printList(v);

  return 0;
}

void printList(std::array<int,6> const& v) {
  for (auto a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}

当将数组传递给函数时,它会退化为指针,因此它失去了与 std::begin 和 std::end 一起使用的能力。做你想做的事情的现代方法是使用 std::array(如果可能,你不应该在 C++ 中使用 C 风格的数组):

#include <iostream>
#include <array>

template <typename T, size_t ArraySize>
void printList(const std::array<T, ArraySize>& v)
{
    for (auto a : v) {
        std::cout << a << " ";
    }
    std::cout << std::endl;
}

int main(int argc, char const *argv[]) {

  std::array<int,6> v = {3, 4, 6, 9, 2, 1};

  printList(v);

  return 0;
}    
int v[] = {3, 4, 6, 9, 2, 1};

得到了 int[6] 的类型,而 int *v.. 它的类型是 int *。你可以使用指向 int 的指针来访问数组,但它不携带有关的信息该数组的大小。 你可以像这样传递数组,但你会受到数组大小的限制:

void foo(int (&p)[6])

或制作模板:

template <std::size_t size> void foo( int (&p)[size] )

如果由于某种原因你不能使用自动 for() 循环(例如为了移植到 C++11 支持可疑的平台)你需要使用 std::array\std::向量或传递指针和数组大小

您可以将固定大小的数组传递给函数:

void printList(int (&v)[6]) {        // pass by reference
    for (auto& a : v) {              // use reference to avoid making a copy
        std::cout << a << " ";
    }
}

但是,我们当然不想编写仅适用于特定大小数组的函数。这是使用模板有意义的地方:

template <int size>
void printList(int (&v)[size]) {
    for (auto& a : v) {
        std::cout << a << " ";
    }
}

好的是当你调用这个函数时你甚至不会注意到它是一个模板,因为编译器可以推断出模板参数(它当然知道大小):

int main() {
    int v[] = {3, 4, 6, 9, 2, 1};
    printList(v); 
}

打印:

3 4 6 9 2 1

基于范围的 for 循环本质上只是语法糖,即从 cppreference

中检索

for ( range_declaration : range_expression ) loop_statement (until C++20)

for ( init-statement(optional) range_declaration : range_expression ) loop_statement (since C++20)

在功能上等同于以下内容:

{
    auto && __range = range_expression ;
    for (auto __begin = begin_expr, __end = end_expr;
            __begin != __end; ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
}

或者,如果您使用 c++17 或更高版本,这实际上允许 __begin__end.

的不同类型
{
    init-statement // only since C++20
    auto && __range = range_expression ;
    auto __begin = begin_expr ;
    auto __end = end_expr ;
    for ( ; __begin != __end; ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
}

其中begin_exprend_expr组成如下

  1. If range_expression is an expression of array type, then begin_expr is __range and end_expr is (__range + __bound), where __bound is the number of elements in the array (if the array has unknown size or is of an incomplete type, the program is ill-formed)

  2. If range_expression is an expression of a class type C that has a member named begin and/or a member named end (regardless of the type or accessibility of such member), then begin_expr is __range.begin() and end_expr is __range.end();

  3. Otherwise, begin_expr is begin(__range) and end_expr is end(__range), which are found via argument-dependent lookup (non-ADL lookup is not performed).


让我们看看这如何适用于您的案例:

第一种情况v肯定是一个数组类型的表达式或者确切地说是int(&)[6]类型的表达式,所以我们使用情况(1)where __bound = 6等(省略完整为简洁起见扣除替换)

在第二种情况下,当你有一个函数时,v 的类型为 int*,因为它不是数组类型,指针也没有成员,我们默认情况 (3)它使用 ADL 来确定调用 begin(__range) 的函数,它不会产生指针类型的结果,因此编译器会抱怨 error: ‘begin’ was not declared in this scope.

在第三种情况下,您在尝试定义函数模板时出错printList。您必须保留声明中包含的 template<...> 部分,否则它只是函数的定义。这就是编译器告诉您 error: ‘len’ was not declared in this scope 的原因。因此,正确且有效的代码是

template <std::size_t len>
void printList(int (&v)[len]);

int main(int argc, char const *argv[]) {
  int v[] = {3, 4, 6, 9, 2, 1};
  printList(v);
  return 0;
}

template <std::size_t len>
void printList(int (&v)[len]) {
  for (int a : v) {
    std::cout << a << " ";
  }
  std::cout << std::endl;
}

其他答案已经建议使用不同的容器类型,例如 std::array<int, 6> 提出了一个有效的观点。一定要看看它们,尤其是大括号初始化,您几乎可以免费升级到它们。