我不知道这些实际上是什么,但是我一直都在看到它们。 Python的实现类似于:

x += 5,是x = x + 5的简写。我在阅读过的每本关于Python,C,R等的书籍或编程教程中都涉及到它。我知道这很方便,可以节省三个按键,包括空格。但是,当我阅读代码时,它们似乎总是让我绊倒,至少在我看来,使它的可读性降低,而不是更多。在那个地方?

评论

@EricLippert:C#是否以与上述最佳答案相同的方式处理此问题?说x + = 5比x = x + 5实际上更有效率吗?还是真的像您建议的那样只是语法糖?

@blesh:关于如何在源代码中表达加法的细节,可能会影响最终的可执行代码的效率,这种情况可能在1970年就已出现;当然不是现在。优化编译器是很好的,您在这里或那里的忧虑要超过十亿分之一秒。 + =运算符是在“二十年前”开发的,这显然是错误的;已故的丹尼斯·里奇(Dennis Richie)从1969年到1973年在贝尔实验室开发了C语言。

参见blogs.msdn.com/b/ericlippert/archive/2011/03/29/…(另请参阅第二部分)

大多数函数式程序员都会认为这是错误的做法。

#1 楼

这不是简写。

+=符号在1970年代以C语言出现,并且-带有C的“智能汇编程序”思想,对应于明显不同的机器指令和地址模式:

i=i+1”,“ "i+=1”和“ ++i”之类的东西虽然产生抽象的效果,但在较低的级别对应于处理器的不同工作方式。

特别是这三个表达式,假设i变量位于CPU寄存器中存储的内存地址中(将其命名为D-将其视为“指向int的指针”),并且处理器的ALU接受参数并返回一个“累加器”(我们称它为A-认为它是一个整数)。

由于这些限制(该时期所有微处理器中非常普遍),翻译很可能是

;i = i+1;
MOV A,(D); //Move in A the content of the memory whose address is in D
ADD A, 1;  //The addition of an inlined constant
MOV (D) A; //Move the result back to i (this is the '=' of the expression)

;i+=1;
ADD (D),1; //Add an inlined constant to a memory address stored value

;++i;
INC (D); //Just "tick" a memory located counter


第一种方法是不理想的,但是使用变量inste进行操作时,它更通用常量(ADD A, BADD A, (D+x))的广告,或者在转换更复杂的表达式时(它们全部在堆栈中以推入低优先级运算的方式沸腾,调用高优先级,弹出并重复执行,直到消除所有参数为止)。

第二个是“状态机”的典型代表:我们不再是“求值表达式”,而是“运算值”:我们仍然使用ALU,但避免将值移动到允许替换参数的结果周围。如果需要更复杂的表达,则无法使用此类指令:由于需要多次i = 3*i + i-2,因此无法就地操作i。第三个(甚至更简单)甚至没有考虑“加法”的概念,而是将更“原始”(在计算意义上)的电路用于计数器。由于对寄存器进行改造以使其成为计数器所需要的组合网络较小,因此指令变得更短,加载更快并立即执行。与现代加法器相比,它的速度更快。编译器(现在称为C),启用了编译器优化,可以根据方便交换对应关系,但是语义上仍然存在概念上的差异。

x += 5表示


找到x所标识的位置
在其中添加5

但是x = x + 5表示:


求x + 5 />

查找x标识的位置
将x复制到累加器中
将5加到累加器中


将结果存储在x


查找x标识的位置
将累加器复制到其中



当然,优化可以


如果“查找x”没有副作用,则两个“查找”可以执行一次(并且x成为存储在指针寄存器中的地址)
如果将ADD应用于&x而不是累加器,则可以忽略这两个副本。

,使优化的代码与x += 5相同。仅在“发现x”没有副作用的情况下可以执行,否则

*(x()) = *(x()) + 5;




*(x()) += 5;


在语义上是不同的,因为x()的副作用(允许x()是执行奇怪的事情并返回int*的函数)将产生两次或一次。将x = x + yx += y应用于直接的l值的情况。

要使用Python,它继承了C的语法,但是由于在解释语言中执行代码之前没有翻译/优化,因此事情不一定是如此紧密地联系在一起(因为减少了一个解析步骤)。但是,解释器可以针对三种类型的表达式引用不同的执行例程,这取决于表达式的形成方式和评估上下文,从而利用不同的机器代码。


对于谁喜欢更多的细节...

每个CPU都有一个ALU(算术逻辑单元),从本质上讲,它是一个组合网络,其输入和输出“插入”到寄存器和/或存储器的存储取决于指令的操作码。

二进制操作通常实现为“累加器寄存器的修改器,输入取自” somewhere”,其中某处可以是
-指令流内本身(对于清单内容而言是典型的:ADD A 5)
-在另一个注册表中(对于具有临时性的表达式计算来说是典型的:例如ADD AB)
-在内存中,在寄存器指定的地址处(通常为数据获取,例如:ADD A(H))-H,在这种情况下,其工作方式类似于取消引用指针。

是伪代码,+=

ADD (X) 5


,而=

MOVE A (X)
ADD A 5
MOVE (X) A


即x + 5给出一个暂时分配的。 x += 5直接在x上运行。

实际的实现取决于处理器的实际指令集:
如果没有x = x+5操作码,则第一个代码变为第二个:没有办法。 />
如果存在这样的操作码,并且启用了优化,则在消除了反向移动并调整了寄存器操作码之后,第二个表达式将成为第一个表达式。

评论


+1是唯一的答案,它解释了它过去曾经映射到不同(且效率更高)的机器代码。

–彼得·托克(PéterTörök)
2012年2月9日在8:47

@KeithThompson是的,但是不能否认汇编对C语言(以及随后的所有C风格语言)的设计产生了巨大影响。

–马特·戴维(MattDavey)
2012年2月9日在9:39



Erm,“ + =”不映射到“ inc”(映射到“ add”),“ ++”映射到“ inc”。

–布伦丹
2012年2月9日在12:46

我认为“ 20年前”是指“ 30年前”。顺便说一句,COBOL在C方面又击败了20年:ADD 5 TOX。

– JoelFan
2012年2月9日在16:29

理论上很棒;事实错了。 x86 ASM INC仅加1,因此它不会影响此处讨论的“添加和分配”运算符(尽管这对于“ ++”和“-”是一个很好的答案)。

– Mark Brackett
2012年2月9日在18:45

#2 楼

根据您的想法,它实际上更容易理解,因为它更简单。以例如:

x = x + 5调用“取x,加5,然后将新值分配回x”的思维过程。

x += 5因此,这不仅仅是速记,它实际上更直接地描述了功能。当阅读大量的代码时,它更容易掌握。

评论


+1,我完全同意。我从小就开始编程,当时我的思维容易延展,而x = x + 5仍然困扰着我。当我晚些时候上数学时,这让我更加困扰。使用x + = 5更具描述性,并且作为表达式更有意义。

–多项式
2012年2月9日在12:14

在某些情况下,变量的名称也很长:truereallyreallylongvariablename = truereallyreallylongvariablename +1 ...哦,是的!错字

–user39685
2012年2月9日在13:44



@Matt Fenwick:不必是长变量名;它可能是某种形式的表达。这些可能更难以验证,并且读者必须花费大量精力来确保它们相同。

– David Thornley
2012年2月9日在14:57

当您的目标是将x增加5时,X = X + 5就是一种骇客。

– JeffO
2012年2月10日的1:08

@Polynomial我想我小时候也遇到过x = x + 5的概念。如果我没记错的话是9岁-这对我来说非常有意义,而现在对我来说仍然非常有意义,并且更喜欢x + =5。第一个更加冗长,我可以更清楚地想象这个概念-我将x赋值为x的先前值(无论是什么)然后加5的想法。我猜想,不同的大脑以不同的方式工作。

–克里斯·哈里森(Chris Harrison)
2015年5月20日,3:39



#3 楼

至少在Python中,x += yx = x + y可以做完全不同的事情。例如,如果我们这样做

a = []
b = a


,那么a += [3]将导致a == b == [3],而a = a + [3]将产生a == [3]b == []。也就是说,+=可以就地修改对象(嗯,它可能会做,您可以定义__iadd__方法来执行几乎所有您喜欢的事情),而=则创建一个新对象并将变量绑定到该对象。

在使用NumPy进行数值运算时,这非常重要,因为您经常会最终获得对数组不同部分的多个引用,并且确保您不要无意间修改了数组的其他部分也很重要对数组的引用或不必要的复制(这可能非常昂贵)。

评论


__iadd__的+1:有些语言可以创建对可变数据结构的不可变引用,其中定义了运算符+ =,例如scala:val sb = StringBuffer(); lb + =“可变结构” vs var s =“”; s + =“可变变量”。进一步修改了数据结构的内容,而后者又将变量指向新的结构。

–飞羊
2012-2-10 13:08



#4 楼

这被称为成语。编程习惯用语是有用的,因为它们是编写特定编程结构的一致方式。通常,我不会混合使用更复杂的操作和这些语法的缩写。当增加1时,这是最有意义的。

评论


x ++和++ x与x + = 1和彼此略有不同。

–加里·威洛比(Gary Willoughby)
2012年2月9日在13:44

@Gary:++ x和x + = 1在C和Java(也许也为C#)中是等效的,尽管在C ++中不一定如此,因为那里复杂的运算符语义。关键是它们都对x求值一次,将变量加1,然后得到的结果就是求值后变量的内容。

–研究员
2012年2月10日于10:02

@Donal研究员:优先级不同,因此++ x + 3与x + = 1 + 3不同。用括号括起x + = 1并相同。就其本身而言,它们是相同的。

– David Thornley
2012年2月10日在21:21

@DavidThornley:当它们都有未定义的行为时,很难说“它们是相同的” :)

–轨道轻赛
15年5月19日在21:36

表达式++ x + 3,x + = 1 + 3或(x + = 1)+ 3都不具有未定义的行为(假定结果值“ fits”)。

– John Hascall
15年5月19日在22:55

#5 楼

为了使@Pubby的观点更清楚一点,请考虑使用someObj.foo.bar.func(x, y, z).baz += 5

,如果没有+=运算符,则有两种方法可以解决:这不仅冗长而且冗长,而且速度较慢。因此,必须
使用一个临时变量:someObj.foo.bar.func(x, y, z).baz = someObj.foo.bar.func(x, y, z).baz + 5。可以,但是简单的事情会带来很多噪音。这实际上与运行时的情况非常接近,但是编写起来很繁琐,仅使用tmp := someObj.foo.bar.func(x, y, z); tmp.baz = tmp.bar + 5会将工作转移到编译器/解释器。

+=和其他此类运算符的优势不可否认,而习惯他们只是时间问题。

评论


一旦深入到对象链的3个级别,就可以停止关心小的优化(如1)和嘈杂的代码(如2)。相反,请多考虑一下您的设计。

– Dorus
2012年2月9日在11:48

@Dorus:我选择的表达式只是“复杂表达式”的任意代表。随意用脑海中的东西代替它;

–back2dos
2012年2月9日在12:50

+1:这是进行此优化的主要原因-不管左侧表达式多么复杂,它总是正确的。

– S.Lott
2012年2月9日下午13:24

将错字放在其中可以很好地说明可能发生的情况。

– David Thornley
2012年2月9日14:59

#6 楼

确实,它更短,更容易,并且它确实是受底层汇编语言启发的,但其最佳实践的原因是,它可以防止整个错误类别,并且可以更轻松地检查代码并确保它能做什么。

使用

RidiculouslyComplexName += 1;


由于只涉及一个变量名,因此您可以确定该语句的作用。

RidiculouslyComplexName = RidiculosulyComplexName + 1;

始终令人怀疑,这两个方面是完全相同的。您看到错误了吗?当存在下标和限定词时,情况甚至更糟。

评论


在赋值语句可以隐式创建变量的语言中,情况变得更糟,尤其是在语言区分大小写的情况下。

–超级猫
2014年6月12日22:35

#7 楼

虽然+ =表示法是惯用语并且更短,但这不是为什么它更易于阅读的原因。读取代码最重要的部分是将语法映射到含义,因此语法与程序员的思维过程越接近,可读性就越高(这也是样板代码不好的原因:这不是思想的一部分程序,但仍需使代码起作用)。在这种情况下,我们的想法是“将变量x乘以5”,而不是“让x为x加5的值”。使用三元运算符的示例,其中if语句更合适。

#8 楼

要了解为什么这些运算符首先使用“ C风格”语言,请参见34年前的K&R 1st Edition(1978)的摘录:


简而言之,赋值运算符具有的优点是
它们与人们的思维方式更好地对应。我们说“将2加到
i”或“将i加2,”而不是“将i加2,然后将结果放回i。”因此i += 2。此外,对于像
yyval[yypv[p3+p4] + yypv[p1+p2]] += 2
这样的复杂表达式,赋值运算符使代码更易于理解,因为读者不必费力地检查两个long。 />表达式确实相同,或者想知道为什么不一样。而且
赋值运算符甚至可以帮助编译器生成更有效的代码。


我认为从这段文字中可以很清楚地看到Brian Kernighan和Dennis Ritchie(K&R ),相信复合赋值运算符有助于提高代码的可读性。然后。但这是程序员。堆栈交换的问题,这是我第一次可以回想起有人对复合作业的可读性提出抱怨的问题,所以我想知道是否有许多程序员认为它们是一个问题?再说一次,当我键入此命令时,该问题有95个否决票,所以也许人们在阅读代码时确实会感到讨厌。

#9 楼

除了可读性之外,它们实际上还做不同的事情:+=不必两次评估其左操作数。例如,expr = expr + 5将对expr进行两次评估(假设expr不纯)。

评论


对于除最奇怪的编译器以外的所有编译器,都没有关系。大多数编译器都很聪明,可以为expr = expr + 5和expr + = 5生成相同的二进制文件

–vsz
2012年2月9日在7:28



@vsz如果expr有副作用,则不行。

– Pubby
2012年2月9日在7:36

@vsz:在C语言中,如果expr有副作用,则expr = expr + 5必须调用两次这些副作用。

–基思·汤普森(Keith Thompson)
2012年2月9日在9:18

@Pubby:如果有副作用,那么“便利性”和“可读性”就没有问题了,这是原始问题的重点。

–vsz
2012年2月9日在11:14

最好注意那些“副作用”陈述。对volatile的读取和写入是副作用,当x为volatile时,x = x + 5和x + = 5具有相同的副作用

– MSalters
2012年2月9日在12:13

#10 楼

除了其他人很好地描述的优点以外,当您的名字很长时,它也更紧凑。



  MyVeryVeryVeryVeryVeryLongName += 1;


#11 楼

简洁明了。

键入起来要短得多。它涉及较少的运营商。它具有较小的表面积和较少的混淆机会。

它使用了更具体的运算符。

这是一个人为的示例,我不确定实际的编译器是否实现这个。 x + = y实际上使用一个参数和一个运算符,并在适当位置修改x。 x = x + y可以具有x = z的中间表示形式,其中z是x + y。后者使用两个运算符(加法和赋值)以及一个临时变量。单个运算符非常清楚,值方不能是y以外的任何值,并且不需要解释。从理论上讲,可能会有一些花哨的CPU,其正等号运算符的运行速度比串联的正号运算符和赋值运算符要快。

评论


从理论上讲,理论上讲。许多CPU上的ADD指令都有一些变体,可以使用其他寄存器,内存或常量作为第二个加数直接在寄存器或内存上进行操作。并非所有组合都可用(例如,将内存添加到内存),但有足够的用处。无论如何,任何具有出色优化器的编译器都知道会为x = x + y生成与x + = y相同的代码。


2012年2月9日在11:43

因此“人为”。

–马克·坎拉斯(Mark Canlas)
2012年2月9日在15:37

#12 楼

这是一个很好的成语。是否更快取决于语言。在C语言中,它更快,因为它可以转换为在右侧增加变量的指令。包括Python,Ruby,C,C ++和Java在内的现代语言均支持op =语法。它体积小巧,您很快就会习惯。由于您会在其他人的代码(OPC)中看到很多内容,因此您不妨习惯并使用它。这是其他几种语言中发生的事情。

在Python中,键入x += 5仍然会导致创建整数对象1(尽管它可能是从池中绘制的)和孤立整数对象包含5。

在Java中,它会导致默认转换。尝试输入

int x = 4;
x = x + 5.2  // This causes a compiler error
x += 5.2     // This is not an error; an implicit cast is done.


评论


现代命令式语言。在(纯)功能语言中,x + = 5与x = x + 5一样少。例如,在Haskell中,后者不会使x递增5 –而是会引发无限递归循环。谁想要这样的简写?

–leftaround关于
2012年2月9日在20:06

对于现代编译器,它并不一定要快得多:即使在C语言中。

– RichieHH
15年5月19日在21:47

+ =的速度与语言无关,而与处理器有关。例如,X86是两个地址的体系结构,它本身仅支持+ =。类似于a = b + c的语句;必须编译为a = b; a + = c;因为根本没有指令可以将加法的结果放在除求和之一之外的其他任何地方。相比之下,Power体系结构是三地址体系结构,没有用于+ =的特殊命令。在这种架构上,语句a + = b;和a = a + b;始终编​​译为相同的代码。

–cmaster-恢复莫妮卡
15年7月19日在14:26



#13 楼

当您将变量用作累加器时,例如+=等运算符非常有用,例如,运行中的总计:

x += 2;
x += 5;
x -= 3;


比: />
x = x + 2;
x = x + 5;
x = x - 3;


在第一种情况下,从概念上讲,您正在修改x中的值。在第二种情况下,您正在计算一个新值,并将其每次分配给x。而且,尽管您可能永远也不会写出那么简单的代码,但是想法还是一样的……重点是您对现有值所做的工作,而不是创建一些新的值。

评论


对我而言,这恰恰相反-第一种形式就像屏幕上的污垢,第二种则干净且可读。

– Mikhail V
16年11月16日在6:10

@MikhailV为不同的人提供了不同的笔触,但是如果您不熟悉编程,则可能会在一段时间后更改视图。

–卡莱布
16年11月16日在7:34

我对编程不是那么陌生,我只是认为您很长时间以来就已经习惯了+ =表示法,因此您可以阅读它。最初并没有使它具有任何美观的语法,因此客观上,用空格包围的+运算符要比+ =和几乎没有区别的朋友更为清晰。并不是说您的答案是错误的,而是您不应太依赖自己的习惯使消费变得“容易阅读”。

– Mikhail V
16 Nov 16在17:31



我的感觉是,更多的是关于累积和存储新值之间的概念差异,而不是使表达式更紧凑。大多数命令式编程语言都具有类似的运算符,因此我似乎并不是唯一一个认为它有用的人。但是就像我说的,不同的人有不同的中风。如果您不喜欢它,请不要使用它。如果您想继续讨论,也许“软件工程聊天”会更合适。

–卡莱布
16年11月16日在19:19

#14 楼

考虑这个

(some_object[index])->some_other_object[more] += 5


D0你真的想写

(some_object[index])->some_other_object[more] = (some_object[index])->some_other_object[more] + 5


#15 楼

其他答案针对更常见的情况,但是还有另一个原因:在某些编程语言中,它可能会过载。例如Scala。


Scala小课程:

var j = 5 #Creates a variable
j += 4    #Compiles

val i = 5 #Creates a constant
i += 4    #Doesn’t compile



如果一个类仅定义+运算符,则x+=y为确实是x=x+y的快捷方式。

如果类重载+=,则它们不是:

var a = ""
a += "This works. a now points to a new String."

val b = ""
b += "This doesn’t compile, as b cannot be reassigned."

val c = StringBuffer() #implements +=
c += "This works, as StringBuffer implements “+=(c: String)”."



另外,运算符++=是两个单独的运算符(不仅这些:+ a,++ a,a ++,a + ba + = b也是不同的运算符);在可能出现运算符重载的语言中,这可能会产生有趣的情况。就像上面描述的那样-如果要使+运算符重载以执行加法,请记住,也必须重载+=

评论


“如果一个类仅定义+运算符,则x + = y确实是x = x + y的快捷方式。”但是可以肯定的是,它还能以其他方式起作用吗?在C ++中,通常先定义+ =,然后将a + b定义为“制作a的副本,使用+ =将b放在其上,然后返回a的副本”。

–leftaround关于
2012-2-11 13:42

#16 楼

说一次,只说一次:在x = x + 1中,我说两次“ x”。

但不要写“ a = b + = 1”,否则我们将不得不杀死10只小猫,27只老鼠,一只狗和一只仓鼠。


永远不要更改变量的值,因为它可以更容易地证明代码正确—请参见函数式编程。但是,如果这样做,最好不要只说一次。

评论


“从不更改变量的值”功能范式?

– Peter Mortensen
15年7月19日在9:26