Declaring variables inside loops, good practice or bad practice?
问题1:在循环中声明变量是一种好的实践还是坏的实践?
我已经阅读了其他线程,了解是否存在性能问题(大多数人说没有),并且您应该始终声明变量接近将要使用它们的位置。我想知道的是,这是否应该避免,或者它是否是真正的首选。
例子:
1 2 3 4 5 6 | for(int counter = 0; counter <= 10; counter++) { string someString ="testing"; cout << someString; } |
问题2:大多数编译器是否意识到变量已经声明并跳过该部分,或者每次都在内存中为它创建一个点?
型
这是很好的练习。
通过在循环内创建变量,可以确保将变量的作用域限制在循环内。不能在循环外部引用或调用它。
这种方式:
- 百万千克1
如果变量的名称有点"通用"(如"i"),那么在代码的后面某个地方,将它与另一个同名变量混合是没有风险的(也可以使用gcc上的
百万千克1百万千克1
编译器知道变量作用域被限制在循环内部,因此如果变量在其他地方被错误引用,编译器将发出一条正确的错误消息。
百万千克1百万千克1
最后,编译器可以更有效地执行一些专用的优化(最重要的是寄存器分配),因为它知道变量不能在循环之外使用。例如,不需要存储结果以供以后重用。
百万千克1
型
简而言之,你这样做是正确的。
但是请注意,变量不应该在每个循环之间保留其值。在这种情况下,您可能需要每次初始化它。您还可以创建一个包含循环的更大的块,循环的唯一目的是声明变量,这些变量必须保留从一个循环到另一个循环的值。这通常包括循环计数器本身。
1 2 3 4 5 6 7 8 9 10 11 12 | { 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:当调用函数时,变量被分配一次。实际上,从分配的角度来看,它(几乎)与在函数开头声明变量相同。唯一的区别是范围:变量不能在循环之外使用。甚至可能没有分配变量,只是重新使用一些空闲槽(从范围已结束的其他变量)。
受限和更精确的范围会带来更精确的优化。但更重要的是,它使您的代码更安全,在读取代码的其他部分时,需要担心的状态(即变量)更少。
即使在
1 2 3 4 5 6 7 | int result; (...) result = f1(); if (result) then { (...) } (...) result = f2(); if (result) then { (...) } |
号
写作更安全:
1 2 3 4 5 6 7 8 9 10 | (...) { int const result = f1(); if (result) then { (...) } } (...) { int const result = f2(); if (result) then { (...) } } |
这种差别似乎不大,尤其是在这样一个小例子上。但在更大的代码基础上,它将有所帮助:现在没有风险将一些
即使是编译器也会有更好的帮助:假设在将来,在一些错误的代码更改之后,
补充信息
开源工具CppCheck(C/C++代码的静态分析工具)为变量的最佳范围提供了一些极好的提示。
对分配意见的回应:上面的规则在C中是正确的,但对于某些C++类可能不是这样。
对于标准类型和结构,变量的大小在编译时是已知的。在C中没有"construction"这样的东西,所以当调用函数时,变量的空间将被简单地分配到堆栈中(不需要任何初始化)。这就是为什么在循环中声明变量时存在"零"成本的原因。
但是,对于C++类,有一个构造器的东西,我知道得少多了。我想分配可能不会成为问题,因为编译器应该足够聪明,可以重用相同的空间,但是初始化很可能在每个循环迭代中进行。
一般来说,保持密切联系是一个很好的做法。
在某些情况下,需要考虑性能等因素,以便将变量从循环中拉出。
在您的示例中,程序每次都创建和销毁字符串。有些库使用小字符串优化(SSO),因此在某些情况下可以避免动态分配。
假设您想要避免那些冗余的创建/分配,您可以将其写为:
1 2 3 4 5 | for (int counter = 0; counter <= 10; counter++) { // compiler can pull this out const char testing[] ="testing"; cout << testing; } |
或者你可以把常数拉出来:
1 2 3 4 | 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?
它可以重用变量所消耗的空间,并且可以将不变量从循环中拉出。在const char数组(上面)的情况下-可以拉出该数组。但是,对于对象(如
1 2 3 4 | for (int counter = 0; counter <= 10; counter++) { string testing ="testing"; cout << testing; } |
在每种情况下都需要冗余复制,如果变量位于SSO字符计数的阈值之上(并且SSO由您的std库实现),则需要动态分配和空闲。
这样做:
1 2 3 4 5 | string testing; for (int counter = 0; counter <= 10; counter++) { testing ="testing"; cout << testing; } |
在每次迭代时仍然需要字符的物理副本,但是表单可能会导致一个动态分配,因为您分配了字符串,并且实现应该看到不需要调整字符串的支持分配的大小。当然,在本例中不会这样做(因为已经演示了多个更好的备选方案),但是当字符串或向量的内容发生变化时,您可能会考虑这样做。
那么,你如何处理所有这些选项(以及更多)?把它作为违约保持在非常接近的位置——直到你很好地理解了成本并且知道什么时候应该偏离。
对于C++来说,这取决于你在做什么。好吧,这是愚蠢的代码,但是想象一下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 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;
};
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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; } |
您将等待55秒,直到获得myfunc的输出。仅仅因为每个循环的构造函数和析构函数一起需要5秒钟才能完成。
你将需要5秒钟,直到你得到肌热功能的输出。
当然,这是一个疯狂的例子。
但它说明,当构造函数和/或析构函数需要一段时间时,当每个循环完成相同的构造时,它可能会成为一个性能问题。
我没有发帖回答杰里迈尔的问题(因为他们已经被回答了);相反,我发帖只是为了提出建议。
对杰里迈尔来说,你可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 | { 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"等后面。
我的代码在微软Visual C++ 2010中编译,所以我知道它是有效的,而且,我试着使用它定义的括号之外的变量,并且我收到了一个错误,所以我知道变量被"销毁"了。
我不知道使用这个方法是否是一个坏的实践,因为许多未标记的括号可能会很快使代码不可读,但也许一些注释可以清除这些内容。