首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >在循环中声明变量,是好做法还是坏做法?

在循环中声明变量,是好做法还是坏做法?
EN

Stack Overflow用户
提问于 2011-11-01 04:50:42
回答 9查看 192.7K关注 0票数 346

问题#1:在循环中声明变量是好做法还是坏做法?

我已经阅读了其他关于是否有性能问题的帖子(大多数人说没有),并且你应该总是在接近使用它们的地方声明变量。我想知道的是,这是应该避免的,还是实际上是首选的。

示例:

代码语言:javascript
复制
for(int counter = 0; counter <= 10; counter++)
{
   string someString = "testing";

   cout << someString;
}

问题#2:大多数编译器是意识到变量已经声明并跳过了这一部分,还是每次都在内存中为它创建一个点?

EN

回答 9

Stack Overflow用户

回答已采纳

发布于 2011-11-01 04:57:38

这是优秀的 practice。

通过在循环内创建变量,可以确保它们的作用域被限制在循环内。不能在循环外部引用或调用它。

这样:

  • 如果变量的名称有点“泛型”(如"i"),那么在稍后的代码中将其与另一个同名变量混合没有风险(也可以使用GCC上的-Wshadow warning指令缓解)
  • 编译器知道变量的作用域被限制在循环内,因此如果变量被错误地引用为elsewhere.
  • Last,则会发出正确的错误消息,但同样重要的是,编译器可以更有效地执行一些专用的优化(最重要的是寄存器分配)。因为它知道变量不能在循环之外使用。例如,不需要存储结果以供以后重用。

简而言之,你这样做是正确的。

但是请注意,变量不应该在每个循环之间保留它的值。在这种情况下,您可能每次都需要初始化它。您还可以创建一个包含循环的更大的块,其唯一目的是声明变量,这些变量必须从一个循环到另一个循环保留它们的值。这通常包括循环计数器本身。

代码语言:javascript
复制
{
    int i, retainValue;
    for (i=0; i<N; i++)
    {
       int tmpValue;
       /* tmpValue is uninitialized */
       /* retainValue still has its previous value from previous loop */

       /* Do some stuff here */
    }
    /* Here, retainValue is still valid; tmpValue no longer */
}

对于问题#2:当调用函数时,变量被分配一次。事实上,从分配的角度来看,它(几乎)等同于在函数开始时声明变量。唯一的区别是作用域:变量不能在循环之外使用。甚至有可能变量没有被分配,只是重用了一些空闲的空位(来自其他作用域已经结束的变量)。

有了受限和更精确的作用域,就会有更精确的优化。但更重要的是,它使您的代码更安全,在读取代码的其他部分时,需要担心的状态(即变量)更少。

即使在if(){...}块之外也是如此。通常,不是:

代码语言:javascript
复制
    int result;
    (...)
    result = f1();
    if (result) then { (...) }
    (...)
    result = f2();
    if (result) then { (...) }

更安全的做法是:

代码语言:javascript
复制
    (...)
    {
        int const result = f1();
        if (result) then { (...) }
    }
    (...)
    {
        int const result = f2();
        if (result) then { (...) }
    }

差异可能看起来很小,特别是在这样一个小的例子中。但在更大的代码库中,它会有所帮助:现在将一些result值从f1()传输到f2()块是没有风险的。每个result都严格限制在它自己的作用域内,这使得它的作用更加准确。从审查者的角度来看,这要好得多,因为他需要担心和跟踪的长范围状态变量更少。

甚至编译器也会提供更好的帮助:假设在将来,在一些错误的代码更改之后,不能用f2()正确地初始化result。第二个版本将简单地拒绝工作,在编译时声明一个明确的错误消息(比运行时好得多)。第一个版本不会发现任何东西,f1()的结果将简单地进行第二次测试,与f2()的结果混淆。

互补信息

开放源码工具CppCheck (一种针对C/C++代码的静态分析工具)提供了一些关于最佳变量范围的极佳提示。

作为对分配的回应:上面的规则在C中是正确的,但可能不适用于某些C++类。

对于标准类型和结构,变量的大小在编译时是已知的。在C中没有“构造”这样的东西,所以当调用函数时,变量的空间将被简单地分配到堆栈中(没有任何初始化)。这就是为什么在循环内声明变量时有“零”成本的原因。

然而,对于C++类,还有构造函数这件事,我对此知之甚少。我猜分配可能不是问题,因为编译器应该足够聪明,可以重用相同的空间,但初始化可能会在每次循环迭代时发生。

票数 429
EN

Stack Overflow用户

发布于 2013-08-02 05:28:27

一般来说,这是一个非常好的做法,使它非常接近。

在某些情况下,会有诸如性能之类的考虑因素,以证明将变量从循环中拉出是合理的。

在您的示例中,程序每次都会创建和销毁字符串。一些库使用小字符串优化(SSO),因此在某些情况下可以避免动态分配。

假设你想避免那些多余的创建/分配,你可以这样写:

代码语言:javascript
复制
for (int counter = 0; counter <= 10; counter++) {
   // compiler can pull this out
   const char testing[] = "testing";
   cout << testing;
}

或者,您可以将常量提取出来:

代码语言:javascript
复制
const std::string testing = "testing";
for (int counter = 0; counter <= 10; counter++) {
   cout << testing;
}

大多数编译器是否意识到变量已经声明并跳过了这一部分,还是每次都会在内存中为它创建一个点?

它可以重用变量消耗的空间,并且可以将不变量从循环中提取出来。在const char数组的情况下(如上)-该数组可以被拉出。但是,对于对象(例如std::string),构造函数和析构函数必须在每次迭代中执行。在std::string的情况下,“空格”包括一个指针,该指针包含表示字符的动态分配。所以这就是:

代码语言:javascript
复制
for (int counter = 0; counter <= 10; counter++) {
   string testing = "testing";
   cout << testing;
}

在每种情况下都需要冗余的复制,如果变量超过SSO字符数的阈值,则需要动态分配和释放(并且SSO是由您的std库实现的)。

执行此操作:

代码语言:javascript
复制
string testing;
for (int counter = 0; counter <= 10; counter++) {
   testing = "testing";
   cout << testing;
}

在每次迭代时仍然需要字符的物理副本,但是表单可能会导致一个动态分配,因为您分配了字符串,并且实现应该看到没有必要调整字符串的后备分配的大小。当然,在本例中您不会这样做(因为已经演示了多个更好的替代方案),但是当字符串或向量的内容发生变化时,您可以考虑这样做。

那么,您如何处理所有这些选项(以及更多)?将其作为默认值保持非常接近--直到您很好地理解了成本并知道何时应该偏离。

票数 32
EN

Stack Overflow用户

发布于 2015-04-09 01:11:45

我发帖并不是为了回答JeremyRR的问题(因为他们已经被回答了);相反,我发帖只是为了给出一个建议。

对于JeremyRR,您可以这样做:

代码语言:javascript
复制
{
  string someString = "testing";   

  for(int counter = 0; counter <= 10; counter++)
  {
    cout << someString;
  }

  // The variable is in scope.
}

// The variable is no longer in scope.

我不知道你是否意识到(我刚开始编程时没有意识到),括号(只要它们是成对的)可以放在代码中的任何地方,而不仅仅是在" if ","for","while“等之后。

我的代码是在Microsoft Visual C++ 2010Express中编译的,所以我知道它可以工作;另外,我试图在定义变量的括号之外使用它,但收到了一个错误,所以我知道该变量被“销毁”了。

我不知道使用这种方法是不是一种糟糕的做法,因为许多未标记的括号可能会很快使代码变得不可读,但也许一些注释可以澄清问题。

票数 18
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/7959573

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档