内部函数范围错误
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
循环使用 begin
和 end
成员函数来确定序列的起点和终点。
例如,std::vector
确实有这些功能,因此支持这种迭代。事实上,指针只是表示内存地址的整数,没有这些函数,这使得无法遍历指针(这本身没有多大意义)以这种方式。
您可以像这样进行迭代:
void printList(int *begin, int *end) {
for(; begin < end; ++begin)
std::cout << *begin;
std::cout << std::endl;
}
这在你的情况下 main
内有效,因为 数组 确实有 begin
和 end
作为 的大小该数组是已知的 。但是,将数组传递给函数会使它衰减为指针。
数组和指针的区别很大
您可以使用基于范围的 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_expr
和end_expr
组成如下
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)
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();
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>
提出了一个有效的观点。一定要看看它们,尤其是大括号初始化,您几乎可以免费升级到它们。
我在使用 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
循环使用 begin
和 end
成员函数来确定序列的起点和终点。
例如,std::vector
确实有这些功能,因此支持这种迭代。事实上,指针只是表示内存地址的整数,没有这些函数,这使得无法遍历指针(这本身没有多大意义)以这种方式。
您可以像这样进行迭代:
void printList(int *begin, int *end) {
for(; begin < end; ++begin)
std::cout << *begin;
std::cout << std::endl;
}
这在你的情况下 main
内有效,因为 数组 确实有 begin
和 end
作为 的大小该数组是已知的 。但是,将数组传递给函数会使它衰减为指针。
数组和指针的区别很大
您可以使用基于范围的 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_expr
和end_expr
组成如下
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)
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();
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>
提出了一个有效的观点。一定要看看它们,尤其是大括号初始化,您几乎可以免费升级到它们。