问题#1:在循环中声明变量是好做法还是坏做法?
我已经阅读了其他关于是否有性能问题的帖子(大多数人说没有),并且你应该总是在接近使用它们的地方声明变量。我想知道的是,这是应该避免的,还是实际上是首选的。
示例:
for(int counter = 0; counter <= 10; counter++)
{
string someString = "testing";
cout << someString;
}
问题#2:大多数编译器是意识到变量已经声明并跳过了这一部分,还是每次都在内存中为它创建一个点?
发布于 2011-11-01 04:57:38
这是优秀的 practice。
通过在循环内创建变量,可以确保它们的作用域被限制在循环内。不能在循环外部引用或调用它。
这样:
-Wshadow
warning指令缓解)简而言之,你这样做是正确的。
但是请注意,变量不应该在每个循环之间保留它的值。在这种情况下,您可能每次都需要初始化它。您还可以创建一个包含循环的更大的块,其唯一目的是声明变量,这些变量必须从一个循环到另一个循环保留它们的值。这通常包括循环计数器本身。
{
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(){...}
块之外也是如此。通常,不是:
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
都严格限制在它自己的作用域内,这使得它的作用更加准确。从审查者的角度来看,这要好得多,因为他需要担心和跟踪的长范围状态变量更少。
甚至编译器也会提供更好的帮助:假设在将来,在一些错误的代码更改之后,不能用f2()
正确地初始化result
。第二个版本将简单地拒绝工作,在编译时声明一个明确的错误消息(比运行时好得多)。第一个版本不会发现任何东西,f1()
的结果将简单地进行第二次测试,与f2()
的结果混淆。
互补信息
开放源码工具CppCheck (一种针对C/C++代码的静态分析工具)提供了一些关于最佳变量范围的极佳提示。
作为对分配的回应:上面的规则在C中是正确的,但可能不适用于某些C++类。
对于标准类型和结构,变量的大小在编译时是已知的。在C中没有“构造”这样的东西,所以当调用函数时,变量的空间将被简单地分配到堆栈中(没有任何初始化)。这就是为什么在循环内声明变量时有“零”成本的原因。
然而,对于C++类,还有构造函数这件事,我对此知之甚少。我猜分配可能不是问题,因为编译器应该足够聪明,可以重用相同的空间,但初始化可能会在每次循环迭代时发生。
发布于 2013-08-02 05:28:27
一般来说,这是一个非常好的做法,使它非常接近。
在某些情况下,会有诸如性能之类的考虑因素,以证明将变量从循环中拉出是合理的。
在您的示例中,程序每次都会创建和销毁字符串。一些库使用小字符串优化(SSO),因此在某些情况下可以避免动态分配。
假设你想避免那些多余的创建/分配,你可以这样写:
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;
}
大多数编译器是否意识到变量已经声明并跳过了这一部分,还是每次都会在内存中为它创建一个点?
它可以重用变量消耗的空间,并且可以将不变量从循环中提取出来。在const char数组的情况下(如上)-该数组可以被拉出。但是,对于对象(例如std::string
),构造函数和析构函数必须在每次迭代中执行。在std::string
的情况下,“空格”包括一个指针,该指针包含表示字符的动态分配。所以这就是:
for (int counter = 0; counter <= 10; counter++) {
string testing = "testing";
cout << testing;
}
在每种情况下都需要冗余的复制,如果变量超过SSO字符数的阈值,则需要动态分配和释放(并且SSO是由您的std库实现的)。
执行此操作:
string testing;
for (int counter = 0; counter <= 10; counter++) {
testing = "testing";
cout << testing;
}
在每次迭代时仍然需要字符的物理副本,但是表单可能会导致一个动态分配,因为您分配了字符串,并且实现应该看到没有必要调整字符串的后备分配的大小。当然,在本例中您不会这样做(因为已经演示了多个更好的替代方案),但是当字符串或向量的内容发生变化时,您可以考虑这样做。
那么,您如何处理所有这些选项(以及更多)?将其作为默认值保持非常接近--直到您很好地理解了成本并知道何时应该偏离。
发布于 2015-04-09 01:11:45
我发帖并不是为了回答JeremyRR的问题(因为他们已经被回答了);相反,我发帖只是为了给出一个建议。
对于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","while“等之后。
我的代码是在Microsoft Visual C++ 2010Express中编译的,所以我知道它可以工作;另外,我试图在定义变量的括号之外使用它,但收到了一个错误,所以我知道该变量被“销毁”了。
我不知道使用这种方法是不是一种糟糕的做法,因为许多未标记的括号可能会很快使代码变得不可读,但也许一些注释可以澄清问题。
https://stackoverflow.com/questions/7959573
复制相似问题