嗨,我知道这是一个非常愚蠢/基本的问题,但代码之间有什么区别: -
int *i;
for(j=0;j<10;j++)
{
i = static_cast<int *>(getNthCon(j));
i->xyz
}
,有些事情是这样的: -
for(j=0;j<10;j++)
{
int *i = static_cast<int *>(getNthCon(j));
i->xyz;
}
我的意思是,这些代码在逻辑上是否完全相同,或者由于其本地特性会有什么不同?
答案 0 :(得分:1)
一个实际差异是i
的范围。在第一种情况下,i
在循环的最后一次迭代后继续存在。在第二个它没有。
在某些情况下,您可能希望在完成所有计算后知道i
的值。在这种情况下,请使用第二种模式。
不太实际的区别在于每种情况下=
令牌的性质。在第一个示例中,i = ...
表示分配。在第二个示例中,int *i = ...
表示初始化。某些类型(但不是int*
和fp_ContainerObject*
)可能会以不同方式处理分配和初始化。
答案 1 :(得分:0)
他们之间差别很小。
在第一个代码示例中,i
在循环外声明,因此您在每次迭代时重复使用相同的指针变量。在第二个中,i
是循环体的局部。
由于i
未在循环外使用,并且在未来的迭代中未使用在一次迭代中分配给它的值,因此在本地声明它是更好的样式,如第二个示例中那样。
顺便说一下,i
是指针变量的坏名称;它通常用于int
变量,尤其是for
循环中使用的变量。
答案 2 :(得分:0)
对于任何理智的优化编译器,内存分配方面没有区别。唯一的区别是i
的范围。这是一个示例程序(是的,我知道这里有泄漏):
#include <iostream>
int *get_some_data(int value) {
return new int(value);
}
int main(int argc, char *argv[]){
int *p;
for(int i = 0; i < 10; ++i) {
p = get_some_data(i);
std::cout << *p;
}
return 0;
}
生成的程序集输出:
int main(int argc, char *argv[]){
01091000 push esi
01091001 push edi
int *p;
for(int i = 0; i < 10; ++i) {
01091002 mov edi,dword ptr [__imp_operator new (10920A8h)]
01091008 xor esi,esi
0109100A lea ebx,[ebx]
p = get_some_data(i);
01091010 push 4
01091012 call edi
01091014 add esp,4
01091017 test eax,eax
01091019 je main+1Fh (109101Fh)
0109101B mov dword ptr [eax],esi
0109101D jmp main+21h (1091021h)
0109101F xor eax,eax
std::cout << *p;
01091021 mov eax,dword ptr [eax]
01091023 mov ecx,dword ptr [__imp_std::cout (1092048h)]
01091029 push eax
0109102A call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (1092044h)]
01091030 inc esi
01091031 cmp esi,0Ah
01091034 jl main+10h (1091010h)
}
现在,在循环内部声明了指针:
int main(int argc, char *argv[]){
008D1000 push esi
008D1001 push edi
for(int i = 0; i < 10; ++i) {
008D1002 mov edi,dword ptr [__imp_operator new (8D20A8h)]
008D1008 xor esi,esi
008D100A lea ebx,[ebx]
int *p = get_some_data(i);
008D1010 push 4
008D1012 call edi
008D1014 add esp,4
008D1017 test eax,eax
008D1019 je main+1Fh (8D101Fh)
008D101B mov dword ptr [eax],esi
008D101D jmp main+21h (8D1021h)
008D101F xor eax,eax
std::cout << *p;
008D1021 mov eax,dword ptr [eax]
008D1023 mov ecx,dword ptr [__imp_std::cout (8D2048h)]
008D1029 push eax
008D102A call dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (8D2044h)]
008D1030 inc esi
008D1031 cmp esi,0Ah
008D1034 jl main+10h (8D1010h)
}
如您所见,输出相同。请注意,即使在 debug 构建中,程序集仍保持相同。
答案 3 :(得分:0)
Ed S.表明,大多数编译器都会为这两种情况生成相同的代码。但是,正如Mahesh指出的那样,它们实际上并不完全相同(甚至超出了明显的事实,即在版本1中的循环范围之外使用i
而不是版本2)。让我试着以一种没有误导性的方式解释这两者是如何成真的。
首先,i
的存储空间来自哪里?
标准对此保持沉默 - 只要存储在i
范围的整个生命周期内可用,它就可以在编译器喜欢的任何地方。但是处理局部变量的典型方法(技术上,具有自动存储持续时间的变量)是将适当范围的堆栈帧扩展sizeof(i)
个字节,并将i
存储为该堆栈帧的偏移量。
“教学编译器”可能总是为每个范围创建堆栈帧。但是真正的编译器通常不会打扰,特别是在进入和退出循环范围时没有任何反应。 (除了通过查看程序集或使用调试器进行攻击,你无法区分它们,因此当然可以这样做。)因此,两个版本最终可能会以i
为参考与函数堆栈帧完全相同的偏移量。 (实际上,它很合理i
将最终出现在注册表中,但这并不会改变任何重要的内容。)
现在让我们来看看生命周期。
在第一种情况下,编译器必须默认初始化i
,它在函数作用域中声明,每次通过循环复制赋值,并在函数作用域的末尾销毁它。在第二种情况下,编译器必须在每个循环开始时复制初始化i
,并在每个循环结束时销毁它。像这样:
如果i
属于班级类型,这将是一个非常显着的差异。 (如果不明白为什么,请参见下文。)但不是,它是一个指针。这意味着默认初始化和销毁都是无操作,复制初始化和复制分配是相同的。
因此,生命周期管理代码在两种情况下都是相同的:每次循环都会复制一次,就是这样。
换句话说,允许存储,并且可能是相同的存储;生命周期管理必须是相同的。
我答应回到为什么如果我是班级类型,这些会有所不同。
比较这个伪代码:
i.IType();
for(j=0;j<10;j++) {
i.operator=(static_cast<IType>(getNthCon(j));
}
i.~IType();
到此:
for(j=0;j<10;j++) {
i.IType(static_cast<IType>(getNthCon(j));
i.~IType();
}
乍一看,第一个版本看起来“更好”,因为它是1个IType(),10个运算符=(IType&amp;)和1~IType(),而第二个版本是10个IType(IType&amp;)和10个〜 ITYPE()。对于某些课程,这可能是真的。但是如果你考虑operator =如何工作,它通常必须至少相当于复制结构和破坏。
因此,真正的区别在于第一个版本需要默认构造函数和复制赋值运算符,而第二个版本不需要。如果你拿出那个static_cast位(所以我们讨论的是转换构造函数和赋值而不是复制),你所看到的就等同于:
for(j=0;j<10;j++) {
std::ifstream i(filenames[j]);
}
显然,在这种情况下,你会尝试将我拉出循环。
但同样,这只适用于“大多数”课程;你可以轻松地设计一个版本2非常糟糕的类,而版本1更有意义。
答案 4 :(得分:-2)
对于每次迭代,在第二种情况下,在堆栈上创建新的指针变量。在第一种情况下,指针变量只创建一次(即,在进入循环之前)