【问题标题】:Having to redeclare an int for a second for loop必须为第二个 for 循环重新声明一个 int
【发布时间】:2020-06-11 02:51:39
【问题描述】:

我在 edabit.com 上玩 C++ 挑战以刷新自己,我遇到了一个挑战,我需要编写一个函数,如果给定一个向量,如果所有偶数索引都是偶数并且所有奇数索引都是奇怪的。否则返回假。该功能的逻辑和一切都很好。我知道有几种方法可以做到这一点,但提出了一个(我认为)创造性的解决方案,而不是通过一个 for 循环来检查索引是奇数还是偶数,而是编写了两个递增的后续 for 循环每个。一个从 0 开始,另一个从 1 开始。一旦发现不匹配,它就会返回 false。否则最后会返回true。我来自 JavaScript,您不需要重新声明索引变量(如果使用 var 而不是 let)但此代码无法编译:

bool isSpecialArray(std::vector<int> arr) {
    for(int i = 0; i < arr.size(); i = i + 2){
        if(arr[i] % 2 == 1){
            return false;
        }
    }

    for(i = 1; i < arr.size(); i = i + 2){
        if(arr[i] % 2 == 0){
            return false;
        }
    }

    return true;
}

说明:

error: use of undeclared identifier 'i'
        for(i = 1; i < arr.size(); i = i + 2){
            ^

当然,在第二个 for 循环中声明 int 会清除错误并按预期执行程序。我猜这是由于垃圾收集,但标准中是否有定义这个的特定规则?或者这可能是每个编译器的事情?我对垃圾收集的理解是,一旦声明的变量不会从内存中清除,直到不再在定义它的当前函数中使用。由于 for 循环的范围,这是否不同,类似于 JavaScript 的let ?

【问题讨论】:

  • 没有垃圾回收。有范围。无论您在for 中声明什么,都将保留在for 中。就像拉斯维加斯 :) C 主要具有所谓的词法范围,而不是动态范围。这意味着您可以静态地确定变量的范围。也就是说:看源码就知道作用域,不需要运行程序就知道,而且作用域在运行时不会改变。重要区别:variables 的范围是词法的。由于动态内存分配,对象的范围可以是动态的。
  • 规则在 C 标准中,但除非您花大量时间了解它的结构和用于描述 C 是什么的概念框架,否则该文档大多难以理解。词法作用域使事情变得简单:你看向外和向上。进入当前块直到它的开头,然后退出块,然后向上看封闭块,依此类推,直到你到达翻译单元的开头。在此搜索期间在匹配的命名空间中找到的第一个名称就是您的符号解析为的名称。
  • 顺便说一句:std::vector&lt;int&gt; arr ...arr[i] % 2 == 1arr[i] &lt; 0 时永远不会为真。代码应该是arr[i] % 2 != 0.for 偶数/奇数测试。
  • @chux-ReinstateMonica 感谢关于&lt; 0 的观点,我想我没有考虑过这一点,但挑战的规定是所有测试的输入都是&gt;= 0

标签: c++ variable-declaration


【解决方案1】:

首先,JS 和 C++ 有非常不同的作用域模型,并且 JS 因其不良做法而闻名(抱歉)。在 C++ 中,规则更加一致:只要程序退出范围,变量就会被销毁。这意味着在这段代码中:

for (int i = 0; ; ) {
}

变量i 仅针对此范围定义,它不能在另一个(非嵌套)循环中重用,在其他地方看不到,并且名称可以在内部范围内重用或重新定义。这为您提供了一个优势,即更容易控制数据缺陷并减少副作用。所以你的解决方案是在第二个循环中声明另一个变量i

    for(int i = 1; i < arr.size(); i = i + 2){
        if(arr[i] % 2 == 0){
            return false;
        }
    }

只要你来自 JS 世界,我会给你更多的建议。首先,不要按值传递向量(除非您意识到自己在做什么)。通过引用传递它,甚至更好地通过 const 引用传递:

bool isSpecialArray(const std::vector<int> &arr) {
   // ...
}

接下来,增加索引的一种可能更有效、更惯用的方法是i += 2

最后的观察:你的解决方案没有“创造力”,但也有缺点。此代码变得对缓存不太友好,这可能被视为一种不好的做法。

【讨论】:

  • 接受这个作为正确答案有几个原因。一个是关于范围的更详细的描述。另一个是关于参考的评论。这实际上是我在 C++ 中所知道的,但挑战编写了函数声明,我们只是在其中编写代码。话虽如此,如果另一个正在学习 C++ 的人来到这里并且不知道参考资料,这对他们来说是件好事。
  • 另外,不用担心调用 JS 及其不良做法。我很清楚这一点以及可执行软件和网络开发人员在这件事上的争论。没有冒犯。以前我曾经玩过 C++ 和一些 C#,但只在专业的 JS 方面工作过。并且有一段时间没有接触其他人了,所以我想拓宽自己的视野,希望在某个时候转向真正的软件开发。
【解决方案2】:

C++ 中没有垃圾收集,即使有,也无法解释您的错误。实际原因很简单:作为for 循环的一部分声明的变量在循环后超出范围。

【讨论】:

  • 是的,我想垃圾收集在这里用错了。更多指的是作用域末尾的变量的销毁,我认为是函数。我没有意识到 for 循环是它自己的作用域。
【解决方案3】:

您程序中的变量 i 在块或花括号 ({ }) 内声明。因此,变量 i 是一个局部变量,只能在该块内使用。

bool isSpecialArray(std::vector<int> arr) {
    int i;
    for(i = 0; i < arr.size(); i = i + 2){
        if(arr[i] % 2 == 1){
            return false;
        }
    }

    for(i = 1; i < arr.size(); i = i + 2){
        if(arr[i] % 2 == 0){
            return false;
        }
    }

    return true;
}

这是正确的做法。

【讨论】:

  • 谢谢。这实际上正是我想要的。不幸的是,我确实接受了另一个答案,因为它提供了有关 C++ VS JS 细微差别的更多细节。
【解决方案4】:

c++ 没有垃圾收集。相反,它会确定性地破坏对象,例如i。编译器执行此操作的方式是在对象所在的范围结束后清理该对象,然后不允许您使用该对象。

int main() 
{
 {
  int i;
 } // scope of i ends

 cout << i; // error, can't access i here
}

for 循环有自己的范围,因此在您的示例中,第一个循环中的 i 在第二个循环中不可访问。

【讨论】:

  • 这并没有解决 OP 中的问题。
  • @1201ProgramAlarm 假设您的意思是 for 案例,已编辑,谢谢。
  • 这个答案本身就很有趣。我不知道您可以在没有任何标签、名称或关键字的情况下定义匿名(不确定这里是否有更好/更合适的术语)范围。是否有一个实际的用例,或者只是语言的一个漂亮技巧?
  • 我能想到的一个例子是{ int i=0; if(b); }i 保持在if 的本地。但是从 c++17 开始,您可以执行 if (int i=0; b);,因此不需要它。每当您需要一个范围时,我猜 :) 而且,它并不是真正的技巧,它是该语言的核心特性之一。
猜你喜欢
  • 2013-09-03
  • 2013-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-01
相关资源
最近更新 更多