【问题标题】:Error redeclaring a for loop variable within the loop在循环中重新声明 for 循环变量时出错
【发布时间】:2018-10-11 20:22:52
【问题描述】:

考虑一下这个 C 程序的 sn-p:

for(int i = 0; i < 5; i++)
{
    int i = 10;  // <- Note the local variable

    printf("%d", i); 
}    

它编译没有任何错误,并且在执行时会给出以下输出:

1010101010

但是如果我用 C++ 写一个类似的循环:

for(int i = 0; i < 5; i++)
{
     int i = 10;

     std::cout << i; 
}

编译失败并出现以下错误:

prog.cc:7:13: error: redeclaration of 'int i'  
     int i = 10;  
         ^  
prog.cc:5:13: note: 'int i' previously declared here  
     for(int i = 0; i < 5; i++)  
             ^   

为什么会这样?

【问题讨论】:

  • 这应该是 C 还是 C++?
  • 问题还不错。这完全取决于变量的范围以及它在 c 和 c++ 中的不同行为方式。
  • "在 C++ 中,init-statement 的范围和 statement 的范围是相同的,而在 C 中,statement 的范围嵌套在 init-statement 的范围内: ..." 参见引用下方的示例:en.cppreference.com/w/cpp/language/for
  • @Bob__ 您的编辑使问题得到 +9 :) 让我们保持原样。
  • 问题在问什么非常清楚。投票重新开放。

标签: c++ c for-loop


【解决方案1】:

这是因为 C 和 C++ 语言对于在嵌套在 for 循环中的范围内重新声明变量有不同的规则:

  • C++i 放在循环体的范围内,所以第二个int i = 10 是重新声明,这是禁止的
  • C 允许在 for 循环内的范围内重新声明;最里面的变量“wins”

这是running C programC++ program failing to compile 的演示。

在正文中打开嵌套范围可修复编译错误 (demo):

for (int i =0 ; i != 5 ; i++) {
    {
        int i = 10;
        cout << i << endl;
    }
}

现在for标头中的iint i = 10处于不同的作用域,所以程序可以运行了。

【讨论】:

  • 有一个疑问for(int i=0;i&lt;5;i++){ int i=10; cout&lt;&lt;i; } 是否属于重新声明。它的C++ 代码不会产生任何错误。
  • @achal 应该会产生重新声明错误,因为在 C++ 中,for 循环的主体和标头中声明的变量只有一个作用域。
  • 实际上只有旧 C 允许这样做,这是因为循环迭代器属于全局范围。 Ofc,您可以在下面的范围内声明相同的 id。这在 C99 中有所改变。
  • @Swift;这根本不是真的。
  • @AndriyTylychko 一直到 TurboC++、QuickC(不是 C++)和之前,ofc,现在人们说“不是这样”,因为如果您使用适当的 -std 标志,clang 和 gcc 实现方式不同并且行为不同。这是标准文本中可疑定义的结果,它在高于循环体的范围内定义 init 语句,然后在注释中的某处添加:但你不能使用相同的标识符。注意,C89 不允许声明变量在 for() 标头内部。但编译器已经有了。for() 循环、初始化程序和虚拟关键字的混乱是历史问题。
【解决方案2】:

与 C 不同,C++ 有规则,
C++11-§6.5.3/1:

for 声明

for (for-init-statement 条件opt ; 表达式opt ) 语句

等价于

{
    for-init-statement 
    while ( condition ) {
        statement 
        expression ;
    } 
}

除了在 for-init-statement 中声明的名称与在条件中声明的名称在 相同的声明区域 [...]

这意味着for-init-statementstatement的作用域是一样的*下面的代码会报错

for(int i = 0; i < 5; i++){
     int i = 10;    // Invalid. 
     // ...
}

在 C 中,
C11-§6.8.5/5:

迭代语句是一个块,其范围是其封闭块范围的严格子集。 循环体也是一个块,其范围是迭代语句范围的严格子集

因此,statement 有自己的作用域,上面的代码是有效的,等价于

for(int i = 0; i < 5; i++){
    {
         int i = 10;    // Valid. 
         // ...
    }
}

建议阅读:n3337: 6.5.1 while 语句/p(2)。在第 9.5.1 节和第 9.5.3 节的 c++17 草案 (n4659) 中可以找到相同的参考。

【讨论】:

  • N3242 是 C++11 之前的草案;当然建议阅读应该是最新的标准和/或最新的草案
  • @MM;最新是指 n4741 草案?
  • 我认为 C++ 标准语言实际上并不能证明将问题中的示例标记为错误是合理的。实际上有两个级别将 for-init-statementint i=0 与循环体中包含的 int i=10; 分开。首先,“等效于”之后的替换文本在 statement 周围引入了额外的大括号,在单词 中声明的异常不涉及,因为它只谈论for-init-statementcondition(我的斜体)处于同一声明级别;它根本没有提到循环体。(...)
  • 其次,在示例中,statement(循环体)实际上是一个compound-statement(即,它的最外层标记是大括号),并且这些大括号保留在替换文本中,因此具体变为{ int i=0; while(i&lt;5) { {int i=10; cout &lt;&lt; i &lt;&lt; endl;} i++; } }i=10 周围的大括号明显多于i=0 周围的两个级别。即使有人将措辞不好的例外解释为旨在有效地剥离一层大括号,也会留下一对。请注意,在循环体周围放置双括号会使报告的错误消失。
  • 在写 cmets 之前,我确实阅读了这些部分;是什么让你认为我没有?顺便说一句,我还在 6.5 中读到“如果迭代语句中的子语句是单个语句而不是复合语句,就好像它被重写为包含原始语句的复合语句”,这意味着你有效即使您编写了一个没有任何大括号的循环体(但这在示例中不适用),并且 6.3 “复合语句定义块范围”,也没有任何例外或修改,也可以获得一对大括号。所以每个循环体都是一个单独的块作用域
【解决方案3】:

这不是重新声明。

仔细看...

for(int i = 0; i < 7; i++)
{
    printf("i = %d\n", i);
    int i = 5;
    printf("new i = %d\n", i);
}

以上代码的输出:-

i = 0
new i = 5
i = 1
new i = 5
i = 2
new i = 5
i = 3
new i = 5
i = 4
new i = 5
i = 5
new i = 5
i = 6
new i = 5

显然,i 有两个不同的

较新的i 具有更多的本地范围。

这是一个错误吗?

没有

目的是什么?

如果不允许,维护大型项目可能会非常困难,因为您会经常遇到命名冲突。

一般来说,在不同的范围内为不同的变量赋予相同的名称被认为是非常糟糕的做法,您应该尽可能避免这样做。

为什么没有警告信息?

使用gcc file_name.c -Wshadow编译。


编辑: 您还可以通过在 for 循环中重新声明最初声明的变量来本地锁定它们。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-13
    • 2021-08-29
    • 2011-05-28
    • 1970-01-01
    相关资源
    最近更新 更多