为什么`for`语句范围的特殊规则?

时间:2014-10-15 08:11:44

标签: c++

我最近在这个问题上跌跌撞撞

for(int i=0,n=v.size(); i<n; i++) {
   ...
   P2d n = ...   <<<--- error here
}

编译器抱怨已经定义了n局部变量,尽管open括号看起来应该开始一个新的范围。

事实上,该标准对此有一个特殊的措辞,虽然使用g ++ 4.6.3编译好的代码,但它会抱怨更新的版本和其他编译器。

这条特殊规则背后的理由是什么(如果有的话)?

更清楚:标准解释说这是不允许的,我对 技术 的原因没有任何疑问。错误:我只是想知道为什么委员会决定使用特殊的额外规则而不是仅仅在看到开头括号时创建另一个嵌套范围(就像在其他地方发生的那样)。

例如,为了使代码合法,你可以用两个大括号对包裹身体而不是一个......

请注意for/while/if之后的大括号,虽然被认为是良好做法,不是强制性的而不是语法的一部分,但仍然存在包含循环变量的范围(因此使用函数)定义为另一个示例,其中locals的范围是函数的主体是不相关的:函数体不是语句,括号是强制性的。)

在C ++语法中,for的主体只是一个声明;但是,如果这个语句碰巧是一个支撑组,那么它会在for/while/if中得到一个特殊的处理(当你使用支撑组作为语言中的其他语句时不会发生这种情况)。

将这种额外的复杂功能添加到语言中的原因是什么?它显然是不需要的,只是将括号视为另一个内部范围似乎(对我而言)更简单。

是否存在这种更简单,更常规的方法无效的用例?

请注意我不会提出意见。要么你知道为什么委员会做出了这个决定(在标准中也要求一个非常详细的措辞,而不仅仅是将正文作为常规声明,并在用作声明时定期处理支撑封闭块)或者你不要

修改

&#34;单一范围&#34;查看语法对我来说是不自然的但在技术上可以用于for语句,可以合理化为具有向后goto语句的单个块,但是在非常相似的情况下难以防御if语句的大小写:

if (int x = whatever()) {
    int x = 3; // Illegal
} else {
    int x = 4; // Illegal here too
}

但这是合法的

if (int x = whatever()) {
    int z = foo();
} else {
    int z = bar();
}

then语句的条件,else部分和if部分的范围是否相同?不,因为您可以声明两个z变量。它们是独立的范围吗?不,因为您无法声明x

我能看到的唯一合理化是thenelse部分确实是单独的范围,但是添加了(奇怪的)规则,条件中声明的变量不能在范围内声明。为什么存在这个额外的奇怪限制规则是我所要问的。

8 个答案:

答案 0 :(得分:19)

int i = 0;
for (MyObject o1; i<10; i++) {
   MyObject o2;
}

可以从最近编译器的角度转换为:

int i = 0;
{
    MyObject o1;
    Label0:
    MyObject o2; //o2 will be destroyed and reconstructed 10 times, while being with the same scope as o1
    i++;
    if (i < 10)
        goto Label0;
}

这是你最后一个问号的答案,它们没有添加复杂的东西,只是在同一范围内使用goto标记,而不是转到范围之外然后再次输入。我没有看到为什么它更好的明确原因。 (虽然它会与旧代码不兼容)

答案 1 :(得分:18)

for循环的语义并不特殊! if (bool b = foo()) { }的工作方式相同。奇怪的是它本身就是一个{ }块。如果没有引入新范围,那将毫无用处。因此,明显的不一致是由于特殊情况下的错误概括。

<强> [编辑] 另一种观点是考虑一个假设的可选关键字:

// Not a _conditional_ statement theoretically, but grammatically identical
always()
{
    Foo();
}

这统一了规则,你不会指望三个范围(内部,中间,外部)。

[编辑2] (请不要将此作为移动目标来回答)

你想知道

中的生命和范围(两种不同的东西)
int i = 0;
for (MyObject o1; i<10; i++) {
   MyObject o2;
}

让我们概括一下:

MyObject o2; // Outer scope
int i = 0;
for (MyObject o1; i<o1.fooCount(); i++) {
   std::cout << o2.asString();
   MyObject o2;
}

显然,对o2.asString()的调用在所有迭代中都引用了外o2。它不像内部o2幸存循环迭代。当名称尚未在内部作用域中定义时,名称查找不会使用外部作用域中的名称 - 并且“尚未定义”是编译时的事物。内部o2的重复构造和破坏是运行时的事情。

答案 2 :(得分:5)

这样看:
一对大括号允许您隐藏封闭大括号(或全局)中可见的变量:

void foo(int n)
{
    // the containing block
    for (int i = 0; i < n; ++i)
    {
        int n = 5;  // allowed: n is visible inside the containing { }
        int i = 5;  // not allowed: i is NOT visible inside the containing { }
    }
}

如果以这种方式思考,你会发现这里没有特殊的规则。

答案 3 :(得分:4)

括号({})将代码段划分为块。这个区块中的所有内容都在其本地范围内:

int main(int argc, char** argv)
{
   int a  = 5;
   std::cout<<a<<std::endl      // 5
   {
       int a = 10;
       std::cout<<a<<std::endl  //10
   }
  std::cout<<a<<std::endl       // 5
}

但等等,那段代码中还有别的......

int main(int argc, char** argv)
{
}

这类似于for循环的结构:

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

函数定义的代码也在{...}块之外!
在这种情况下,定义了argcargv它们是函数范围的本地,就像上面i的定义一样for 1}}循环。

实际上,您可以将语法概括为:

definition { expression }

如果上述全部内容属于范围 在这种情况下,&#39; raw&#39;括号({})形成相同的结构,但带有空的定义语句。

编辑: 在:

中回答您的编辑
int i = 0;
for (MyObject o1; i<10; i++) {
   MyObject o2;
}

o2的构造函数为每个循环循环,而o1的构造函数不是。

for循环行为如下(其中XXX是正在执行的当前块:

  1. 初始化
    for(XXX; ; ){ }
  2. 测试循环exp
    for( ;XXX; ){ }
  3. 执行块
    for( ; ; ){XXX}
  4. 最终操作
    for( ; ;XXX){ }
  5. 回到2。

答案 4 :(得分:2)

循环控制变量(本例中为in)被认为是 for loop 的一部分。 并且因为它们已经在循环的初始化语句中声明,所以大多数尝试(除了通过使用嵌套大括号重新定义)在循环中重新定义它们都会导致错误!< / p>

答案 5 :(得分:2)

时,我会从那个角度回答标签。这是一个例子:

#include <stdio.h>

int main(void) {
    int a[] = {1, 2, 3, 4, 5, 6, 7, 8};

    for (int i = 0, n = 8; i < n; i++) {
        int n = 100;
        printf("%d %d\n", n, a[i]);
    }

    return 0;
}

它编译没有问题,看它在ideone工作(C99严格模式,4.8.1)。

C标准很明显,两个范围都被认为是独立的,N1570 6.8.5 / p5(强调我的):

  

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

有警告,但只有-Wshadow选项,正如所料:

$ gcc -std=c99 -pedantic -Wall -Wextra -Wshadow check.c
check.c: In function ‘main’:
check.c:7: warning: declaration of ‘n’ shadows a previous local
check.c:6: warning: shadowed declaration is here

答案 6 :(得分:1)

我无法告诉你为什么for循环只打开了一个范围,而不是第二个范围由于括号。但我可以说当时给出的是改变单一范围的原因:地点。采用这种非常标准的代码:

void foo(int n) {
  int s=0;
  for (int i=0; i<n; ++i) {
    s += global[i];
  }
  // ... more code ...
  for (int i=0; i<n; ++i) {
    global[i]--;
  }
}

根据旧规则,这将是非法代码,在同一范围内定义i两次,即函数。 (在C语言中,它甚至是非法的,因为你必须在块的开头声明变量。)

这通常意味着你要在第二个循环中省略声明 - 如果第一个循环的代码被删除则会遇到问题。无论你做了什么,你都有很长时间的变量,这通常会让你的代码更难理解。 (那是在每个人和他们的兄弟开始考虑十行作为一个长函数之前。)在变量声明之前更改for以启动它自己的作用域使得代码更容易维护。

答案 7 :(得分:-2)

你的问题是for的定义部分被认为是for的范围。

         // V one definition
for(int i=0,n=v.size(); i<n; i++) {
   ...
    // V second definition
   P2d n = ...   <<<--- error here
}