Jer*_*yRR 229 c++ loops variable-declaration
问题#1:在循环中声明一个变量是一个好习惯还是坏习惯?
我已经阅读了其他关于是否存在性能问题的线程(大多数说没有),并且您应该始终将变量声明为接近它们将被使用的位置.我想知道的是,这是否应该避免,或者它是否真的是首选.
例:
for(int counter = 0; counter <= 10; counter++)
{
string someString = "testing";
cout << someString;
}
Run Code Online (Sandbox Code Playgroud)
问题2:大多数编译器是否已经声明变量已经被声明并且只是跳过了那个部分,或者它实际上每次都在内存中为它创建了一个位置?
Cya*_*yan 300
这是一种很好的做法.
通过在循环内创建变量,可以确保它们的范围仅限于循环内部.它不能在循环外引用或调用.
这条路:
如果变量的名称有点"通用"(如"i"),那么在代码的后面某处将它与同名的另一个变量混合就没有风险(也可以使用-Wshadow
GCC上的警告指令进行缓解)
编译器知道变量作用域仅限于循环内部,因此如果变量在其他地方被错误引用,则会发出正确的错误消息.
最后但并非最不重要的是,编译器可以更有效地执行一些专用优化(最重要的是寄存器分配),因为它知道变量不能在循环之外使用.例如,无需存储结果以供以后重复使用.
简而言之,你是对的.
但请注意,变量不应该在每个循环之间保留其值.在这种情况下,您可能需要每次都初始化它.您还可以创建一个更大的块,包含循环,其唯一目的是声明必须将其值从一个循环保留到另一个循环的变量.这通常包括循环计数器本身.
{
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 */
}
Run Code Online (Sandbox Code Playgroud)
对于问题#2:当调用函数时,变量被分配一次.实际上,从分配的角度来看,它几乎与在函数开头声明变量相同.唯一的区别是范围:变量不能在循环外使用.甚至可能没有分配变量,只是重新使用一些空闲槽(来自其范围已经结束的其他变量).
通过限制和更精确的范围,可以获得更准确的优化.但更重要的是,它使您的代码更安全,在阅读代码的其他部分时需要担心更少的状态(即变量).
即使在if(){...}
街区之外也是如此.通常,而不是:
int result;
(...)
result = f1();
if (result) then { (...) }
(...)
result = f2();
if (result) then { (...) }
Run Code Online (Sandbox Code Playgroud)
写起来更安全:
(...)
{
int const result = f1();
if (result) then { (...) }
}
(...)
{
int const result = f2();
if (result) then { (...) }
}
Run Code Online (Sandbox Code Playgroud)
差异似乎很小,特别是在这么小的例子上.但是在更大的代码库上,它会有所帮助:现在没有风险将某些result
值传输f1()
到f2()
阻止.每个result
都严格限制在自己的范围内,使其角色更加准确.从评论者的角度来看,它更好,因为他需要担心和跟踪的长程状态变量较少.
甚至编译器也会有所帮助:假设在将来某些错误的代码更改后,result
未正确初始化f2()
.第二个版本将简单地拒绝工作,在编译时声明一个明确的错误消息(比运行时更好).第一个版本不会发现任何东西,结果f1()
将只是第二次测试,被混淆的结果f2()
.
开源工具CppCheck(C/C++代码的静态分析工具)提供了一些关于变量最佳范围的优秀提示.
回应对分配的评论:上述规则在C中是正确的,但可能不适用于某些C++类.
对于标准类型和结构,变量的大小在编译时是已知的.在C中没有"构造"这样的东西,所以当调用函数时,变量的空间将被简单地分配到堆栈中(没有任何初始化).这就是在循环中声明变量时出现"零"成本的原因.
但是,对于C++类,我不太了解这个构造函数.我认为分配可能不会成为问题,因为编译器应该足够聪明以重用相同的空间,但初始化很可能发生在每次循环迭代.
jus*_*tin 18
一般来说,保持非常接近是一种非常好的做法.
在某些情况下,会有诸如性能之类的考虑因素证明将变量拉出循环是正确的.
在您的示例中,程序每次都会创建并销毁字符串.有些库使用小型字符串优化(SSO),因此在某些情况下可以避免动态分配.
假设您想要避免那些多余的创建/分配,您可以将其写为:
for (int counter = 0; counter <= 10; counter++) {
// compiler can pull this out
const char testing[] = "testing";
cout << testing;
}
Run Code Online (Sandbox Code Playgroud)
或者你可以拉出常数:
const std::string testing = "testing";
for (int counter = 0; counter <= 10; counter++) {
cout << testing;
}
Run Code Online (Sandbox Code Playgroud)
大多数编译器是否已经声明变量已经被声明并且只是跳过那个部分,或者它实际上每次都在内存中为它创建一个位置?
它可以重用变量消耗的空间,并且可以将不变量拉出循环.在const char数组的情况下(上图) - 该数组可以被拉出.但是,在对象(例如std::string
)的情况下,必须在每次迭代时执行构造函数和析构函数.在该情况下std::string
,"空间"包括指针,该指针包含表示字符的动态分配.所以这:
for (int counter = 0; counter <= 10; counter++) {
string testing = "testing";
cout << testing;
}
Run Code Online (Sandbox Code Playgroud)
在每种情况下都需要冗余复制,并且如果变量高于SSO字符计数的阈值(并且SSO由std库实现),则动态分配和释放.
这样做:
string testing;
for (int counter = 0; counter <= 10; counter++) {
testing = "testing";
cout << testing;
}
Run Code Online (Sandbox Code Playgroud)
在每次迭代时仍然需要字符的物理副本,但是表单可能会导致一次动态分配,因为您分配了字符串,并且实现应该看到不需要调整字符串的后备分配.当然,在这个例子中你不会这样做(因为已经演示了多个优秀的替代品),但是当字符串或矢量的内容变化时你可能会考虑它.
那么你如何处理所有这些选择(以及更多)?默认情况下保持非常接近 - 直到您了解成本并知道何时应该偏离.
小智 13
对于C++,它取决于你在做什么.好吧,这是愚蠢的代码,但想象一下
Run Code Online (Sandbox Code Playgroud)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;
}
Run Code Online (Sandbox Code Playgroud)
您将等待55秒,直到获得myFunc的输出.仅仅因为每个循环构造函数和析构函数一起需要5秒才能完成.
在获得myOtherFunc的输出之前,您将需要5秒钟.
当然,这是一个疯狂的例子.
但它说明当构造函数和/或析构函数需要一些时间时,每个循环完成相同的构造时,它可能会成为性能问题.
我没有发帖回答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.
Run Code Online (Sandbox Code Playgroud)
我不知道你是否意识到(当我第一次开始编程时我没有),括号(只要它们是成对的)可以放在代码中的任何地方,而不仅仅是在"if","for","而"等
我的代码是用Microsoft Visual C++ 2010 Express编译的,所以我知道它的工作原理; 另外,我试图在它所定义的括号之外使用变量,我收到一个错误,所以我知道变量被"销毁"了.
我不知道使用这种方法是否是不好的做法,因为许多未标记的括号可能很快使代码无法读取,但也许一些注释可能会清除.
归档时间: |
|
查看次数: |
113483 次 |
最近记录: |