问题描述
如果被迭代的数组来自一个参数并在另一个作用域中声明,则 foreach 循环类型不起作用。
// C++ program to demonstrate use of foreach
#include <iostream>
using namespace std;
void display(int arr[])
{
for (int x : arr)
cout << x << endl;
}
int main()
{
int arr[] = { 10,20,30,40 };
display(arr);
}
错误如下:
什么是开始和结束? 导致此错误的幕后发生了什么? for 循环是否在运行前隐式更改为迭代器声明?这是因为 C 风格的数组不是容器对象,因此没有开始和结束吗?如果这是真的,那么为什么这段代码有效:
#include <iostream>
using namespace std;
int main()
{
int arr[] = { 10,40 };
for (int x : arr)
cout << x << endl;
}
作为参数传递时是否会发生一些信息衰减?
解决方法
那个 for 循环被称为基于范围的 for 循环。编译器正在将该代码转换为其他内容。您可以在此处查看所有详细信息 https://en.cppreference.com/w/cpp/language/range-for(C++11、C++17 和 C++20 不同)。
但是,原则上,你的代码变成这样:
{
auto && __range = arr;
auto __begin = begin_expr;
auto __end = end_expr;
for ( ; __begin != __end; ++__begin) {
x = *__begin;
cout << x << endl;
}
}
这里的问题是 begin_expr
和 end_expr
可以是不同的东西。如果 arr
是一个数组,如 int arr[3]
,__range
将是 __range + 3
。如果 arr
是一个具有成员 begin()
和 end()
的类,它将是 __range.begin()
。否则,它将是 begin(__range)
。
最后一种情况是您的代码正在登陆,因为 arr
不是数组而是指向 int
的指针(int [] arr
表示 int* arr
)。并且 begin()
没有 end()
和 int*
。
它在这里有效,因为在这个例子中,arr
是一个包含 4 个整数的数组:
int arr[] = { 10,20,30,40 };
for (int x : arr)
cout << x << endl;
有不同的方法可以做到这一点。这是我更喜欢自己做的方式:
template <typename T>
void display(std::vector<T> const & arr)
{
for (int x : arr)
cout << x << endl;
}
int main()
{
std::vector<int> arr { 10,40 };
display(arr);
}
您可以检查 https://cppinsights.io/ 以了解编译器如何转换您的代码(在某种程度上)。例如,你的代码变成这样(注意,如果代码不编译它就不起作用,所以我不得不注释掉循环):
#include <iostream>
using namespace std;
void display(int * arr) // <-- notice int*
{
}
int main()
{
int arr[4] = {10,40}; // <-- notice int[4]
display(arr);
}
如果你使用这个代码:
int main()
{
int arr[] = { 10,40 };
for (int x : arr)
cout << x << endl;
}
结果如下:
int main()
{
int arr[4] = {10,40};
{
int (&__range1)[4] = arr;
int * __begin1 = __range1;
int * __end1 = __range1 + 4L;
for(; __begin1 != __end1; ++__begin1)
{
int x = *__begin1;
std::cout.operator<<(x).operator<<(std::endl);
}
}
}
,
当你给一个函数一个 C 风格的数组时,你还需要给出它的大小,因为函数不知道数组的大小。数组参数 int x[]
被视为数组第一个元素上的指针(因此类似于 int* x
)。因此,该函数无法判断数组有多大,因为它只有指针(第一个元素的地址)。您需要指定函数从提供的地址中读取多少个 int(或其他类型)以获得有效元素。
#include <iostream>
using namespace std;
void display(int arr[],int nsize)
{
int i=0;
while (i < nsize){
cout << *arr << endl;
// you move the pointer to the next element to be read
arr++;
i++;
}
}
int main()
{
int arr[] = { 10,40 };
int n= sizeof(arr)/sizeof(int);
display(arr,n);
}
,
第一段代码不起作用,因为
void display(int arr[])
实际上相当于
void display(int *arr)
并且您不能使用单个指针进行迭代,因为它没有任何长度信息。另见What is array to pointer decay。
第二段代码有效,因为基于范围的 for 中的范围表达式必须是
表示合适序列的任何表达式(数组或定义了开始和结束成员函数或自由函数的对象,见下文)或花括号初始化列表。
所以代替不存在的成员函数arr.begin()
和arr.end()
,它可以使用重载的自由函数std::begin()
和std::end()
用于数组。
首先,让我建议您像@Ted 在评论中提到的那样编辑您的问题。
我必须承认错误信息不是很直观。缺失 begin
和 end
的出现源于 range-based for loop 的定义。如您所见,您将一个未知大小的数组作为 range_expression 传递,对吗?链接页面对此案有进一步说明:
如果 range_expression 是数组类型的表达式,则 begin_expr 是 __range 和 end_expr 是 (__range + __bound),其中 __bound 是数组中元素的数量(如果数组大小未知或者是 类型不完整,程序格式错误)
你的第二个代码片段实际上使用了一个已知大小的数组,因为它的使用与它的初始化在同一范围内。 Array initialization 表示:
初始化一个未知大小的数组时,最大下标为 指定的初始化程序决定了数组的大小 正在声明。
让我为您提供一个标准库模板,以便更好地掌握数组。这是.. 错误.. std::array
:)。你得到的基本上是值语义和与其他类型的一致性:
- 按值接受参数会复制整个内容,
- 通过(常量)引用接受参数具有熟悉的语法,
- 传递数组对象时不会隐式丢失大小信息。
所以你的代码会看起来像这样:
#include <array>
#include <iostream>
using namespace std;
void display(const array<int>& arr)
{
for (int x : arr)
cout << x << endl;
}
int main()
{
std::array arr{ 10,40 };
display(arr);
}