在循环内声明变量,好的做法还是不好的做法?
问题1:在循环中声明一个变量是一种好的做法还是不好的做法?
我已经阅读了关于是否存在性能问题的其他线索(大多数是否定的),并且您应该始终将变量声明为接近它们将要使用的位置。 我想知道的是这是否应该避免,或者如果它真的是首选。
例:
for(int counter = 0; counter <= 10; counter++)
{
string someString = "testing";
cout << someString;
}
问题2:大多数编译器是否意识到变量已经被声明并且只是跳过那部分,或者每次都在内存中创建一个内存点?
这是非常好的做法。
通过在循环内创建变量,确保它们的作用域被限制在循环内部。 它不能被引用,也不能在循环之外调用。
这条路:
如果变量的名称有点“通用”(比如“i”),那么不需要在代码中稍后将其与另一个同名变量混合使用(也可以使用GCC上的-Wshadow
警告指令进行缓解)
编译器知道变量作用域被限制在循环内部,因此如果该变量在其他地方被错误地调用,则会发出适当的错误消息
最后但并非最不重要的一点是,编译器可以更高效地执行一些专用优化(最重要的是寄存器分配),因为它知道变量不能在循环之外使用。 例如,不需要存储结果以供以后重新使用。
总之,你是对的。
但请注意,该变量不应保留每个循环之间的值。 在这种情况下,您可能需要每次初始化它。 你也可以创建一个更大的块,包含循环,唯一的目的是声明必须从一个循环到另一个循环保持其值的变量。 这通常包括循环计数器本身。
{
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
都严格限制在自己的范围内,使其角色更加准确。 从审稿人的角度来看,它更好,因为他有较少的远程状态变量需要担心和跟踪。
即使编译器会更好地帮助:假设将来,在代码的某些错误更改之后, result
不会用f2()
正确初始化。 第二个版本会拒绝工作,在编译时指出一个明确的错误消息(比运行时更好)。 第一个版本不会发现任何东西, f1()
的结果将被第二次简单地测试,对f2()
的结果感到困惑。
补充信息
开源工具CppCheck(针对C / C ++代码的静态分析工具)提供了有关最佳变量范围的一些优秀提示。
回应分配评论:上述规则在C中是正确的,但可能不适用于某些C ++类。
对于标准类型和结构,变量的大小在编译时已知。 C中没有“构造”这样的东西,所以当函数被调用时,变量的空间将被简单地分配到堆栈中(不需要任何初始化)。 这就是为什么在循环中声明变量时有“零”成本。
然而,对于C ++类来说,这个构造函数是我知道的更少。 我想分配可能不会成为问题,因为编译器应该足够聪明以重用相同的空间,但初始化可能发生在每次循环迭代时。
一般来说,保持它非常接近是一种非常好的做法。
在某些情况下,会考虑性能等因素,将变量拉出循环。
在你的例子中,程序每次创建并销毁字符串。 一些图书馆使用小字符串优化(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
的情况下,'space'包含一个指针,其中包含表示字符的动态分配。 所以这:
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;
}
在每次迭代时仍然需要字符的物理副本,但是表单可能导致一个动态分配,因为您分配字符串,实现应该看到不需要调整字符串的后备分配大小。 当然,在这个例子中你不会这么做(因为已经证明了多个优秀的替代方案),但是当字符串或向量的内容变化时你可能会考虑它。
那么你如何处理所有这些选项(以及更多)? 保持非常接近的默认值 - 直到您理解成本并知道何时应该偏离。
对于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的输出。
当然,这是一个疯狂的例子。
但是它说明了当构造函数和/或析构函数需要一些时间时,每个循环都有相同的构造时,它可能会成为性能问题。
链接地址: http://www.djcxy.com/p/5059.html上一篇: Declaring variables inside loops, good practice or bad practice?
下一篇: Why can templates only be implemented in the header file?