C++ 在循环内声明变量,好的做法还是坏的做法?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7959573/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 17:48:03  来源:igfitidea点击:

Declaring variables inside loops, good practice or bad practice?

c++loopsvariable-declaration

提问by JeramyRR

Question #1:Is declaring a variable inside a loop a good practice or bad practice?

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

I've read the other threads about whether or not there is a performance issue (most said no), and that you should always declare variables as close to where they are going to be used. What I'm wondering is whether or not this should be avoided or if it's actually preferred.

我已经阅读了关于是否存在性能问题的其他线程(大多数人说没有),并且您应该始终将变量声明为靠近它们将要使用的位置。我想知道的是是否应该避免这种情况,或者它是否确实是首选。

Example:

例子:

for(int counter = 0; counter <= 10; counter++)
{
   string someString = "testing";

   cout << someString;
}

Question #2:Do most compilers realize that the variable has already been declared and just skip that portion, or does it actually create a spot for it in memory each time?

问题#2:大多数编译器是否意识到变量已经被声明而只是跳过那部分,还是每次都在内存中为它创建一个位置?

回答by Cyan

This is excellentpractice.

这是极好的做法。

By creating variables inside loops, you ensure their scope is restricted to inside the loop. It cannot be referenced nor called outside of the loop.

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

This way:

这边走:

  • If the name of the variable is a bit "generic" (like "i"), there is no risk to mix it with another variable of same name somewhere later in your code (can also be mitigated using the -Wshadowwarning instruction on GCC)

  • The compiler knows that the variable scope is limited to inside the loop, and therefore will issue a proper error message if the variable is by mistake referenced elsewhere.

  • Last but not least, some dedicated optimization can be performed more efficiently by the compiler (most importantly register allocation), since it knows that the variable cannot be used outside of the loop. For example, no need to store the result for later re-use.

  • 如果变量的名称有点“通用”(如“i”),则没有将其与代码后面某个地方的另一个同名变量混合的风险(也可以使用-WshadowGCC 上的警告指令来缓解)

  • 编译器知道变量范围仅限于循环内部,因此如果该变量被错误地引用到其他地方,则会发出正确的错误消息。

  • 最后但并非最不重要的是,编译器可以更有效地执行一些专用优化(最重要的是寄存器分配),因为它知道变量不能在循环外使用。例如,无需存储结果供以后重用。

In short, you are right to do it.

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

Note however that the variable is not supposed to retain its valuebetween each loop. In such case, you may need to initialize it every time. You can also create a larger block, encompassing the loop, whose sole purpose is to declare variables which must retain their value from one loop to another. This typically includes the loop counter itself.

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

{
    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 */
}

For question #2: The variable is allocated once, when the function is called. In fact, from an allocation perspective, it is (nearly) the same as declaring the variable at the beginning of the function. The only difference is the scope: the variable cannot be used outside of the loop. It may even be possible that the variable is not allocated, just re-using some free slot (from other variable whose scope has ended).

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

With restricted and more precise scope come more accurate optimizations. But more importantly, it makes your code safer, with less states (i.e. variables) to worry about when reading other parts of the code.

有限的和更精确的范围带来了更准确的优化。但更重要的是,它使您的代码更安全,在阅读代码的其他部分时需要担心的状态(即变量)更少。

This is true even outside of an if(){...}block. Typically, instead of :

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

    int result;
    (...)
    result = f1();
    if (result) then { (...) }
    (...)
    result = f2();
    if (result) then { (...) }

it's safer to write :

写这样更安全:

    (...)
    {
        int const result = f1();
        if (result) then { (...) }
    }
    (...)
    {
        int const result = f2();
        if (result) then { (...) }
    }

The difference may seem minor, especially on such a small example. But on a larger code base, it will help : now there is no risk to transport some resultvalue from f1()to f2()block. Each resultis strictly limited to its own scope, making its role more accurate. From a reviewer perspective, it's much nicer, since he has less long range state variablesto worry about and track.

差异可能看起来很小,尤其是在这么小的例子上。但在更大的代码库,它会帮助:现在有没有风险运送一些result从价值f1()f2()块。每个result都严格限制在自己的范围内,使其作用更加准确。从审阅者的角度来看,这要好得多,因为他需要担心和跟踪的远程状态变量更少。

Even the compiler will help better : assuming that, in the future, after some erroneous change of code, resultis not properly initialized with f2(). The second version will simply refuse to work, stating a clear error message at compile time (way better than run time). The first version will not spot anything, the result of f1()will simply be tested a second time, being confused for the result of f2().

甚至编译器也会提供更好的帮助:假设将来在对代码进行一些错误更改后,result未正确使用f2(). 第二个版本将简单地拒绝工作,在编译时(比运行时更好)声明一个明确的错误消息。第一个版本不会发现任何东西, 的结果f1()将简单地进行第二次测试,对 的结果感到困惑f2()

Complementary information

补充资料

The open-source tool CppCheck(a static analysis tool for C/C++ code) provides some excellent hints regarding optimal scope of variables.

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

In response to comment on allocation: The above rule is true in C, but might not be for some C++ classes.

回应关于分配的评论:上述规则在 C 中是正确的,但可能不适用于某些 C++ 类。

For standard types and structures, the size of variable is known at compilation time. There is no such thing as "construction" in C, so the space for the variable will simply be allocated into the stack (without any initialization), when the function is called. That's why there is a "zero" cost when declaring the variable inside a loop.

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

However, for C++ classes, there is this constructor thing which I know much less about. I guess allocation is probably not going to be the issue, since the compiler shall be clever enough to reuse the same space, but the initialization is likely to take place at each loop iteration.

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

回答by justin

Generally, it's a very good practice to keep it very close.

一般来说,保持非常接近是一个很好的做法。

In some cases, there will be a consideration such as performance which justifies pulling the variable out of the loop.

在某些情况下,会考虑性能,这证明将变量拉出循环是合理的。

In your example, the program creates and destroys the string each time. Some libraries use a small string optimization (SSO), so the dynamic allocation could be avoided in some cases.

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

Suppose you wanted to avoid those redundant creations/allocations, you would write it as:

假设您想避免那些冗余的创建/分配,您可以将其写为:

for (int counter = 0; counter <= 10; counter++) {
   // compiler can pull this out
   const char testing[] = "testing";
   cout << testing;
}

or you can pull the constant out:

或者你可以拉出常数:

const std::string testing = "testing";
for (int counter = 0; counter <= 10; counter++) {
   cout << testing;
}

Do most compilers realize that the variable has already been declared and just skip that portion, or does it actually create a spot for it in memory each time?

大多数编译器是否意识到该变量已经被声明并跳过该部分,或者它实际上每次都在内存中为它创建一个位置?

It can reuse the space the variableconsumes, and it can pull invariants out of your loop. In the case of the const char array (above) - that array could be pulled out. However, the constructor and destructor must be executed at each iteration in the case of an object (such as std::string). In the case of the std::string, that 'space' includes a pointer which contains the dynamic allocation representing the characters. So this:

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

for (int counter = 0; counter <= 10; counter++) {
   string testing = "testing";
   cout << testing;
}

would require redundant copying in each case, and dynamic allocation and free if the variable sits above the threshold for SSO character count (and SSO is implemented by your std library).

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

Doing this:

这样做:

string testing;
for (int counter = 0; counter <= 10; counter++) {
   testing = "testing";
   cout << testing;
}

would still require a physical copy of the characters at each iteration, but the form could result in one dynamic allocation because you assign the string and the implementation should see there is no need to resize the string's backing allocation. Of course, you wouldn't do that in this example (because multiple superior alternatives have already been demonstrated), but you might consider it when the string or vector's content varies.

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

So what do you do with all those options (and more)? Keep it very close as a default -- until you understand the costs well and know when you should deviate.

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

回答by Nobby

For C++ it depends on what you are doing. OK, it is stupid code but imagine

对于 C++,这取决于你在做什么。好吧,这是愚蠢的代码,但想象一下

class myTimeEatingClass
{
 public:
 //constructor
      myTimeEatingClass()
      {
          sleep(2000);
          ms_usedTime+=2;
      }
      ~myTimeEatingClass()
      {
          sleep(3000);
          ms_usedTime+=3;
      }
      const unsigned int getTime() const
      {
          return  ms_usedTime;
      }
      static unsigned int ms_usedTime;
};
class myTimeEatingClass
{
 public:
 //constructor
      myTimeEatingClass()
      {
          sleep(2000);
          ms_usedTime+=2;
      }
      ~myTimeEatingClass()
      {
          sleep(3000);
          ms_usedTime+=3;
      }
      const unsigned int getTime() const
      {
          return  ms_usedTime;
      }
      static unsigned int ms_usedTime;
};
myTimeEatingClass::ms_CreationTime=0; 
myFunc()
{
    for (int counter = 0; counter <= 10; counter++) {

        myTimeEatingClass timeEater();
        //do something
    }
    cout << "Creating class took "<< timeEater.getTime() <<"seconds at all<<endl;

}
myOtherFunc()
{
    myTimeEatingClass timeEater();
    for (int counter = 0; counter <= 10; counter++) {
        //do something
    }
    cout << "Creating class took "<< timeEater.getTime() <<"seconds at all<<endl;

}

You will wait 55 seconds until you get the output of myFunc. Just because each loop contructor and destructor together need 5 seconds to finish.

您将等待 55 秒,直到获得 myFunc 的输出。仅仅因为每个循环构造函数和析构函数一起需要 5 秒才能完成。

You will need 5 seconds until you get the output of myOtherFunc.

您需要 5 秒钟才能获得 myOtherFunc 的输出。

Of course, this is a crazy example.

当然,这是一个疯狂的例子。

But it illustrates that it might become a performance issue when each loop the same construction is done when the constructor and / or destructor needs some time.

但它说明,当构造函数和/或析构函数需要一些时间时,当每个循环都完成相同的构造时,它可能会成为性能问题。

回答by Fearnbuster

I didn't post to answer JeremyRR's questions (as they have already been answered); instead, I posted merely to give a suggestion.

我发帖不是为了回答 JeremyRR 的问题(因为他们已经回答了);相反,我发布只是为了提供建议。

To JeremyRR, you could do this:

对于 JeremyRR,你可以这样做:

{
  string someString = "testing";   

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

  // The variable is in scope.
}

// The variable is no longer in scope.

I don't know if you realize (I didn't when I first started programming), that brackets (as long they are in pairs) can be placed anywhere within the code, not just after "if", "for", "while", etc.

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

My code compiled in Microsoft Visual C++ 2010 Express, so I know it works; also, I have tried to to use the variable outside of the brackets that it was defined in and I received an error, so I know that the variable was "destroyed".

我的代码是在 Microsoft Visual C++ 2010 Express 中编译的,所以我知道它可以工作;此外,我尝试在定义它的括号之外使用该变量,但收到错误消息,因此我知道该变量已“销毁”。

I don't know if it is bad practice to use this method, as a lot of unlabeled brackets could quickly make the code unreadable, but maybe some comments could clear things up.

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

回答by KhanJr

It's a very good practice, as all above answer provide very good theoretical aspect of the question let me give a glimpse of code, i was trying to solve DFS over GEEKSFORGEEKS, i encounter the optimization problem...... If you try to solve the code declaring the integer outside the loop will give you Optimization Error..

这是一个很好的实践,因为以上所有答案都提供了问题的很好的理论方面,让我瞥见代码,我试图通过 GEEKSFORGEEKS 解决 DFS,我遇到了优化问题......如果你尝试解决在循环外声明整数的代码会给你优化错误..

stack<int> st;
st.push(s);
cout<<s<<" ";
vis[s]=1;
int flag=0;
int top=0;
while(!st.empty()){
    top = st.top();
    for(int i=0;i<g[top].size();i++){
        if(vis[g[top][i]] != 1){
            st.push(g[top][i]);
            cout<<g[top][i]<<" ";
            vis[g[top][i]]=1;
            flag=1;
            break;
        }
    }
    if(!flag){
        st.pop();
    }
}

Now put integers inside the loop this will give you correct answer...

现在把整数放在循环中,这会给你正确的答案......

stack<int> st;
st.push(s);
cout<<s<<" ";
vis[s]=1;
// int flag=0;
// int top=0;
while(!st.empty()){
    int top = st.top();
    int flag = 0;
    for(int i=0;i<g[top].size();i++){
        if(vis[g[top][i]] != 1){
            st.push(g[top][i]);
            cout<<g[top][i]<<" ";
            vis[g[top][i]]=1;
            flag=1;
            break;
        }
    }
    if(!flag){
        st.pop();
    }
}

this completely reflect what sir @justin was saying in 2nd comment.... try this here https://practice.geeksforgeeks.org/problems/depth-first-traversal-for-a-graph/1. just give it a shot.... you will get it.Hope this help.

这完全反映了@justin 先生在第二条评论中所说的话......在这里试试这个 https://practice.geeksforgeeks.org/problems/depth-first-traversal-for-a-graph/1。试一试....你会明白的。希望能有所帮助。

回答by sof

Chapter 4.8 Block Structurein K&R's The C Programming Language 2.Ed.:

第 4.8 章K&R 的The C Programming Language 2.Ed 中的块结构。:

An automatic variable declared and initialized in a block is initialized each time the block is entered.

每次进入块时都会初始化在块中声明和初始化的自动变量。

I might have missed seeing the relevant description in the book like:

我可能没有看到书中的相关描述,例如:

An automatic variable declared and initialized in a block is allocated only one time before the block is entered.

在块中声明和初始化的自动变量仅在进入块之前分配一次。

But a simple test can prove the assumption held:

但是一个简单的测试可以证明这个假设成立:

 #include <stdio.h>                                                                                                    

 int main(int argc, char *argv[]) {                                                                                    
     for (int i = 0; i < 2; i++) {                                                                                     
         for (int j = 0; j < 2; j++) {                                                                                 
             int k;                                                                                                    
             printf("%p\n", &k);                                                                                       
         }                                                                                                             
     }                                                                                                                 
     return 0;                                                                                                         
 }