14

我在 C++ 中的范围内遇到了一些麻烦。我正在尝试使用它在 int 数组 (int[]) 上显示元素,当我在主函数上执行此操作时,它工作得非常好,例如:

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

但是当我尝试在函数内部使用 ranged 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) {

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

4

8 回答 8

11

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

for ( range_declaration : range_expression ) loop_statement(直到C++20

for ( init-statement(optional) range_declaration : range_expression ) loop_statement( 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. 如果 range_expression 是数组类型的表达式,则 begin_expr 是 __range,end_expr 是 (__range + __bound),其中 __bound 是数组中的元素数(如果数组大小未知或类型不完整,则程序有问题-形成)

  2. 如果 range_expression 是具有名为 begin 的成员和/或名为 end 的成员(不管此类成员的类型或可访问性)的类类型 C 的表达式,则 begin_expr 是 __range.begin() 并且 end_expr 是 __range.end( );

  3. 否则,begin_expr 是 begin(__range),end_expr 是 end(__range),它们是通过参数相关查找找到的(不执行非 ADL 查找)。


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

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

在第二种情况下,当您有一个函数时,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>提高有效点。一定要看看它们,尤其是使用大括号初始化,您几乎可以免费升级到它们。

于 2016-11-24T23:19:36.120 回答
6

“我认为可能发生这种情况的原因是,由于我是数组的指针表示,因此丢失了一些信息,但我不知道为什么会丢失这些信息。”

是的。数组很容易衰减为指针,并且指针不知道数组长度。需要基于范围的 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;
}
于 2016-11-24T16:44:00.927 回答
5

数组和指针之间有很大的区别。

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

在您使用模板的第二个示例中,要注意的是,您忘记template <std::size_t len>了 的定义printList,因此您有 2 个不同的函数,模板化和非模板化一个,它们被调用。

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

于 2016-11-24T16:41:43.120 回答
4

将数组传递给函数时,它会衰减为指针,因此它失去了与 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;
}    
于 2016-11-24T16:46:12.210 回答
4

这样的for循环使用beginend成员函数来确定序列的开始位置和结束位置。

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

您可以改为这样进行迭代:

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

main在您的情况下有效,因为数组确实具有begin并且数组end大小是已知的。但是,将数组传递给函数会使其衰减为指针。

于 2016-11-24T16:40:44.363 回答
3

里面的数组main有一个已知的大小。

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

于 2016-11-24T16:42:08.063 回答
2

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

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
于 2016-11-24T16:55:23.193 回答
1
int v[] = {3, 4, 6, 9, 2, 1};

got type of int[6], while int *v.. well its type is int *.You can use pointer to int to access array, but it doesn't carry information about size of that array. You can pass array like this, but you'll limit yourself by size of array:

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

or make template:

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

If for some reason you can't use automic for() loops (e.g. for portability to platforms where C++11\14 support is dubious) you need use either std::array\std::vector or pass pointer and size of array

于 2016-11-24T16:52:37.767 回答