一个无数人讨论过的问题,今天终于看到一个人讲得全面而清楚。下面这个帖子是shornmao (死猫)发的,我只是帮他贴过来而已,希望死猫不会生我的气。
-----------------------
首先声明,简单的比较前缀自增运算符和后缀自增运算符的效率是片面的,因为存在很多因素影响这个问题的答案。首先考虑内建数据类型的情况:如果自增运算表达式的结果没有被使用,而仅仅简单的用于增加一员操作数,答案是明确的,前缀法和后缀法没有任何区别,编译器的处理都应该是相同的,很难想象得出有什么编译器实现可以别出心裁在二者之间制造任何差异。测试C++源代码如下://test1.cppvoid test(){int i=0;i++;++i;}Gnu C/C++ 2编译的汇编中间代码如下: .file "test1.cpp"gcc2_compiled.:___gnu_compiled_cplusplus:.text .align 4.globl _test__Fv .def _test__Fv; .scl 2; .type 32; .endef_test__Fv: pushl %ebp movl %esp,%ebp subl $24,%esp movl $0,-4(%ebp) ;i=0 incl -4(%ebp) ;i++ incl -4(%ebp) ;++i jmp L3 jmp L2 .p2align 4,,7L3:L2: leave ret很显然,不管是i++还是++i都仅仅是一条incl指令而已。如果表达式的结果被使用,那么情况要稍微复杂一些。测试C++源代码如下://test2.cppvoid test(){int i=0,a,b;a=i++;b=++i;}Gnu C/C++ 2编译的汇编中间代码如下: .file "test2.cpp"gcc2_compiled.:___gnu_compiled_cplusplus:.text .align 4.globl _test__Fv .def _test__Fv; .scl 2; .type 32; .endef_test__Fv: pushl %ebp movl %esp,%ebp subl $24,%esp movl $0,-4(%ebp) ;i=0 movl -4(%ebp),%eax ;i --> ax movl %eax,-8(%ebp) ;ax --> a(a=i) incl -4(%ebp) ;i++ incl -4(%ebp) ;++i movl -4(%ebp),%eax ;i --> ax movl %eax,-12(%ebp) ;ax --> b(b=i) jmp L3 jmp L2 .p2align 4,,7L3:L2: leave ret有差别吗?显然也没有,同样是一条incl指令,再加上两条movl指令借用eax寄存器复制调用栈内容。让我们再加上编译器优化,重新编译后的汇编代码如下: .file "test2.cpp"gcc2_compiled.:___gnu_compiled_cplusplus:.text .align 4.globl _test__Fv .def _test__Fv; .scl 2; .type 32; .endef_test__Fv: pushl %ebp movl %esp,%ebp leave ret好了,优化的过火了,由于i,a,b三个变量没有被使用,所以干脆全都被优化了,结果成了一个什么都不做的空函数体。那么,让我们再加上一点代码使用a和b的结果吧,这样i的结果也不能够忽略了,C++源代码如下://test3.cppint test(){int i=0,a,b;a=i++;b=++i;return a+b;}此时汇编代码如下: .file "test3.cpp"gcc2_compiled.:___gnu_compiled_cplusplus:.text .align 4.globl _test__Fv .def _test__Fv; .scl 2; .type 32; .endef_test__Fv: pushl %ebp movl %esp,%ebp movl $2,%eax leave ret你还是没有想到吧,答案仅仅是编译器计算了返回值,常量展开(constant-unwinding)启动,变成了直接返回常量结果。怎么办?我们把i变成参数,避免这种预期以外的结果,C++源代码如下://test4.cppint test1(int i){int a=i++;return a;}int test2(int i){int a=++i;return a;}好了,很辛苦,终于得到了不一样的汇编代码: .file "test4.cpp"gcc2_compiled.:___gnu_compiled_cplusplus:.text .align 4.globl _test1__Fi .def _test1__Fi; .scl 2; .type 32; .endef_test1__Fi: pushl %ebp movl %esp,%ebp movl 8(%ebp),%eax leave ret .align 4.globl _test2__Fi .def _test2__Fi; .scl 2; .type 32; .endef_test2__Fi: pushl %ebp movl %esp,%ebp movl 8(%ebp),%eax incl %eax leave ret和你接触到的教条正相反吧,++i反而增加了一条汇编指令incl,而i++却没有,这就是编译器优化的魅力。因为不管i有没有增加,都不影响a的值,而函数仅仅返回i的值,所以i的自增运算就根本不必进行了。所以,为了更客观一些,我们将i参数改为按照引用传递,C++源代码如下;//test5.cppint test1(int &i){int a=i++;return a;}int test2(int &i){int a=++i;return a;}这一次的结果加入了指针的运算,稍微复杂一些: .file "test5.cpp"gcc2_compiled.:___gnu_compiled_cplusplus:.text .align 4.globl _test1__FRi .def _test1__FRi; .scl 2; .type 32; .endef_test1__FRi: pushl %ebp movl %esp,%ebp movl 8(%ebp),%eax movl (%eax),%edx incl (%eax) movl %edx,%eax leave ret .align 4.globl _test2__FRi .def _test2__FRi; .scl 2; .type 32; .endef_test2__FRi: pushl %ebp movl %esp,%ebp movl 8(%ebp),%eax movl (%eax),%edx leal 1(%edx),%ecx movl %ecx,(%eax) movl %ecx,%eax leave ret惊讶吗?还是a=i++的代码更高效一些,不知道这会让你有什么想法。反正,我得出的结论,对于内建数据类型来说,i++和++i孰优孰劣,是编译器实现相关的,实在不必太可以关心这个问题。最后让我们再回到起点,对于自定义数据类型(主要是指类)说,不需要再做很多汇编代码的分析了,我很清楚的知道为什么会有人循循善诱。因为前缀式可以返回对象的引用,而后缀式必须返回对象的值,所以导致了在大对象的时候产生了较大的复制开销,引起效率降低,因此会有劝告尽量使用前缀式,尽可能避免后缀式,除非从行为上真的需要后缀式。这也就是More Effective C++/Term 7中的原文提到的,处理使用者自定义类型(注意不是指内建类型)的时候,应该尽可能的使用前缀式地增/递减,因为他天生体质较佳。同时,为了保证前缀和后缀对递增/递减的语义的实现保持一致,设计上的一般原则是后缀式的实现以前缀式为基础,这样,后缀式往往多了一次函数调用,这也许也是一个需要考虑的效率因素,不过相比之下,就有点微乎其微了。重申一点关于这个问题的进一步叙述,可以在Scott Mayer的<<More Effective C++>>一书的条款7中获得,大约在原书的P31-34上。
分享到:
相关推荐
这是和JVM的内存分配有关,JVM在处理这段带代码时,会先把i++的结果赋值给一个临时变量temp,然后再将这个临时变量的值赋值给i。即如下: int i = 0; int temp; // i = i++; int a = temp = i++;// 临时变量...
1. **目标与宗旨**:I++ 工作组的目标是尽快在相关的软件包中全面实施 I++ DME 和 I++ DMS 接口。成员公司放弃了许可费用的权利,并承诺不会对专利法提出任何索赔。 2. **使用自由性**:工作组致力于消除任何限制...
在实际编程中,i++和++i的效率差异往往被忽略,因为对于内置类型,在大多数编译器中,i++和++i的效率是相同的,编译器会进行优化。然而,对于自定义类型,尤其是当重载了前缀和后缀的自增运算符时,使用++i通常更为...
I++ DME的目标是实现软件包中I++ DME和I++ DMS接口的全面覆盖,并尽可能快地实施这些接口。通过这种方式,可以促进不同制造商之间的测量设备与软件系统的互操作性,降低集成成本,提高生产效率。 - **无许可费用**...
C++ 中的左值和右值、i++ 与 ++i 的区别 在 C++ 编程语言中,左值(Lvalue)和右值(Rvalue)是两个重要的概念,它们在变量和表达式中的角色不同,理解这两个概念对于编写正确和高效的代码非常重要。 左值(Lvalue...
当然如果编译器会将这些差别都优化掉,那么效率就都差不多了。 再给大家详细说下++i 与 i++ 的区别 1、++i 的用法(以 a=++i ,i=2 为例) 先将 i 值加 1 (也就是 i=i+1 ),然后赋给变量 a (也就
从效率角度来说,i++ 和 ++i 都是 O(1) 的操作,无论是前缀递增还是后缀递增,都不会对性能产生影响。 PHP session 传递方式 在 PHP 中,session 可以通过多种方式来传递,包括 GET 方式、表单隐蔽值方式和文件...
在C/C++中,后缀递增运算符(i++)和前缀递增运算符(++i)都用于将变量增加1,但它们的操作和结果在某些情况下有本质的区别。前缀递增运算符++i先增加变量的值,然后返回新的值。后缀递增运算符i++先返回变量当前的值,...
在大多数情况下,`++i` 和 `i++` 的效率差异可以忽略不计,因为现代解释器和编译器已经高度优化。然而,如果在循环或复杂表达式中频繁使用,这种微小的顺序差异可能会影响代码的行为。在性能敏感的代码段中,应根据...
2、我们自定的数据类型,++i效率高于i++,通过运算符重载来给大家说明这一点。 Operator Operator::operator++() { ++value; //内部成员变量 return *this; } Operator Operator::operator++(int) { ...
标题和描述中提到的知识点主要围绕着在编程中常见的递增操作(i++)和递减操作(i--)的执行效率问题进行讨论。在不同的上下文中,这两种操作的效率可能会有所不同,这通常与使用的编程语言、编译器优化以及处理器的指令...
在自定义数据类型的情况下,++i效率更高! 分析: (自定义数据类型的情况下) ++i返回对象的引用; i++总是要创建一个临时对象,在退出函数时还要销毁它,而且返回临时对象的值时还会调用其拷贝构造函数。 (重载这两个...
最后,值得一提的是,了解JavaScript中i++和++i的区别,不仅能够帮助开发者在编写for循环时做出更好的选择,同时也能够帮助他们更好地理解其他编程语言(如C++)中的对应概念,因为这些概念在不同的编程语言中可能会...
3. 优势对比:与传统信息渠道相比,I++在信息的全面性、准确性及获取效率上有明显优势;与现有网络信息渠道相比,I++专注于房地产领域,提供更为专业和个性化的服务。 项目分析: 1. 市场分析:I++通过市场调研,...
综上所述,"管家婆财贸双全I++9.5"的这个压缩包提供了软件运行所需的一些关键组件和辅助资源,包括动态链接库、配置文件、注册表信息以及用户手册。在实际使用中,用户需要将这些文件放置在正确的位置,并且插入正确...
相较于传统搜索引擎,I++专注于房地产领域,具有更强的专业性和针对性;通过技术创新,提供更优质的服务,有望在市场中占据一席之地。 六、营销与推广策略 1. 线上推广:利用社交媒体、行业网站进行广告投放,提高...
在Java编程语言中,`i=3; a=i++;...在团队合作中,清晰地编写和理解代码能够提高工作效率,减少错误,并促进有效的沟通。因此,不断学习和熟练掌握编程基本概念对于任何Java开发者来说都至关重要。