在循环中声明variables,好的做法或不好的做法?
问题1:在循环中声明一个variables是一种好的做法还是不好的做法?
我已经阅读了关于是否存在性能问题的其他线索(大多数说不),并且应该总是将variables声明为接近它们将要使用的位置。 我想知道的是这是否应该避免,或者如果它实际上是首选。
例:
for(int counter = 0; counter <= 10; counter++) { string someString = "testing"; cout << testing; }
问题2:大多数编译器是否意识到这个variables已经被声明过,并且只是跳过这个部分,或者每次都在内存中创build一个内存点?
这是很好的做法。
通过在循环内创buildvariables,确保它们的作用域被限制在循环内部。 它不能被引用,也不能在循环之外被调用。
这条路:
-
如果variables的名称有点“通用”(比如“i”),那么在代码中稍后的某个地方与另一个同名的variables混合就没有风险了(也可以使用GCC上的
-Wshadow
警告指令) -
编译器知道variables作用域被限制在循环内部,因此如果这个variables在其他地方被错误的调用,就会发出一个合适的错误信息
-
最后但并非最不重要的是,编译器(最重要的是寄存器分配)可以更高效地执行一些专用优化,因为它知道variables不能在循环之外使用。 例如,不需要存储结果以供以后重新使用。
总之,你是对的。
但请注意,variables不应保留每个循环之间的值 。 在这种情况下,您可能需要每次初始化它。 您也可以创build一个更大的块,包含循环,唯一的目的是声明必须保持其值从一个循环到另一个循环的variables。 这通常包括循环计数器本身。
{ 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:调用该函数时,该variables被分配一次。 实际上,从分配的angular度来看,它几乎与在函数开头声明variables相同。 唯一的区别是范围:variables不能在循环之外使用。 甚至有可能variables没有被分配,只是重新使用一些空闲槽(来自其他variables的范围已经结束)。
有限的和更精确的范围来更精确的优化。 但更重要的是,它使得你的代码更安全,在读取代码的其他部分时需要担心更less的状态(即variables)。
这甚至在if(){...}
循环之外也是如此。 通常,而不是:
int result; (...) result = f1(); if (result) then { (...) } (...) result = f2(); if (result) then { (...) }
写下更安全:
(...) { int const result = f1(); if (result) then { (...) } } (...) { int const result = f2(); if (result) then { (...) } }
这个差别似乎很小,特别是在这样一个小例子上。 但是在更大的代码基础上,这将有助于:现在没有风险将某个result
值从f1()
传输到f2()
块。 每个result
都严格限制在自己的范围内,使其angular色更加准确。 从审稿人的angular度来看,它更好,因为他有较less的远程状态variables担心和跟踪。
即使是编译器也会有所帮助:假设将来在代码错误的改变之后, result
不能用f2()
正确地初始化。 第二个版本会拒绝工作,在编译时指出一个明确的错误信息(比运行时更好)。 第一个版本不会发现任何东西, f1()
的结果将被简单地再次testing,对f2()
的结果感到困惑。
补充信息
开源工具CppCheck (C / C ++代码的静态分析工具)提供了有关最佳variables范围的一些很好的提示。
回应分配的评论:上面的规则在C中是正确的,但是对于一些C ++类可能不是。
对于标准的types和结构,variables的大小在编译时已知。 C中没有“构造”这样的东西,所以调用该函数时,variables的空间将被简单地分配到堆栈中(不需要任何初始化)。 这就是为什么在循环中声明variables的时候有一个“零”成本。
但是,对于C ++类来说,这个构造函数是我所不了解的。 我想分配可能不会是问题,因为编译器应该足够聪明以重用相同的空间,但初始化可能发生在每个循环迭代。
一般来说,保持密切联系是一个非常好的做法。
在某些情况下,会有一些考虑因素,例如性能,将variables拉出循环。
在你的例子中,程序每次创build并销毁string。 一些库使用小string优化(SSO),所以在某些情况下可以避免dynamic分配。
假设你想避免那些多余的创作/分配,你可以这样写:
for (int counter = 0; counter <= 10; counter++) { // compiler can pull this out const char testing[] = "testing"; cout << testing; }
或者你可以拉出常数:
const std::string testing = "testing"; for (int counter = 0; counter <= 10; counter++) { cout << testing; }
大多数编译器是否意识到这个variables已经被声明过,并且只是跳过这个部分,或者每次都在内存中创build一个内存点?
它可以重用variables消耗的空间,并且可以将不variables从循环中拉出。 在const char数组的情况下(上面) – 该数组可以被拉出。 但是,在对象(如std::string
)的情况下,必须在每次迭代时执行构造函数和析构函数。 在std::string
的情况下,该'space'包含一个指针,其中包含表示字符的dynamic分配。 所以这:
for (int counter = 0; counter <= 10; counter++) { string testing = "testing"; cout << testing; }
将需要在每种情况下进行冗余复制,如果variables位于SSO字符数阈值之上(SSO由std库实现),则dynamic分配和空闲。
这样做:
string testing; for (int counter = 0; counter <= 10; counter++) { testing = "testing"; cout << testing; }
仍然会在每次迭代时需要字符的物理副本,但是表单可能导致一个dynamic分配,因为您分配string,实现应该看到不需要调整string的后备分配。 当然,在这个例子中你不会这样做(因为已经certificate了多个优越的select),但是当string或向量的内容不同时,你可能会考虑它。
那么你如何处理所有这些选项(以及更多)呢? 保持非常接近的默认 – 直到你了解成本好,知道什么时候应该偏离。
对于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; };
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秒,直到你得到myOtherFunc的输出。
当然,这是一个疯狂的例子。
但是它说明了当构造函数和/或析构函数需要一些时间的情况下,每个循环的结构都是相同的,这可能会成为一个性能问题。
我没有回答JeremyRR的问题(因为他们已经回答了)。 相反,我只是提出一个build议。
对于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.
我不知道你是否意识到(当我第一次开始编程的时候没有),括号(只要它们是成对的)可以放在代码中的任何地方,而不是在“if”,“for”,“而“等等
我的代码编译在Microsoft Visual C ++ 2010 Express中,所以我知道它的工作原理; 另外,我试图使用它定义的括号之外的variables,并且我收到一个错误,所以我知道这个variables被“销毁”了。
我不知道是否使用这种方法是不好的做法,因为大量的未标记的括号可能会使代码很难读取,但也许有些意见可以清除。