以下代码做了什么?

5
i = i + j ;
j = i - j ;
i = i - j ;

上面的代码是做什么用的?有人能用其他代码写出同样的操作吗?
谢谢。

1
似乎颠倒了这两个数字。(请自行尝试:http://jsfiddle.net/bradchristie/jjcCG/) - Brad Christie
1
不使用中间临时变量交换两个整数的标准答案是: - Nishant
1
上面的代码执行数学运算,即加法、减法和赋值;-) - 使用其他代码编写相同的操作可能会像这样:int temp = i; i = j; j = temp - ring bearer
1
在提出这样的问题之前,请尝试使用示例值运行此代码多次并学习模式。 - asgs
10个回答

14
这是一种交换 ij 值的技术,不需要创建临时变量。这是一种内存优化的形式。
如果您有兴趣学习这些内容,可以访问以下网站了解值交换: http://booleandreams.wordpress.com/2008/07/30/how-to-swap-values-of-two-variables-without-using-a-third-variable/ 另一种交换值的方法是使用按位异或(XOR)运算符
a = a ^ b
b = a ^ b
a = a ^ b
我个人最喜欢这种方式,因为从概念上考虑更有趣。整数是一组位(1和0)
64位整数有64个“1和0”
这些1和0是二进制的。
1 = 1
10 = 2
11 = 3
100 = 4
101 = 5
111 = 6
这是二进制到十进制的示例。现在,按位运算符XOR的工作方式类似于翻转开关。所以:
2 ^ 1 = 3 :二进制:10 ^ 01 = 11

3 ^ 2 = 1 :二进制:11 ^ 10 = 01 = 1
现在您已经理解了这一点,您可以看到如何使用它交换变量。
让我们将a = 3b = 2(二进制)设置,并尝试一下
a = 100
b = 10
a = a ^ b :: a = 100 ^ 10 = 110
b = a ^ b :: b = 110 ^ 10 = 100

a = a ^ b :: a = 110 ^ 100 = 10

a与b进行异或运算:: a = 110 ^ 100 = 10

现在 a = 10b = 100 或者 a = 2b = 3

现在,a = 10b = 100 或者 a = 2b = 3

Welcome to bits!

欢迎来到位操作!


正如我所想... 你能给出另一个实现同样功能的代码示例吗?(当然不仅仅是改变参数名称)?谢谢! - Batman
@Mosh 当然,你是指交换变量的另一种技术吗? - Dbz

12

它交换了ij的值。假设它们的初始值为ab,那么这些语句将被计算为:

i = a + b;
j = (a + b) - b; // = a
i = (a + b) - a; // = b
回答你问题的第二部分,一个替代方案(在现实生活中的非面试场合很可能采取的方法)看起来会像这样:
int tmp = i;
i = j;
j = tmp;

4
说实在的,它假设i和j是整数,并交换它们,假定没有溢出或者溢出由语言适当处理。如果它们不是整数,则可能会丢失精度。 - DJClayworth
“适当处理”是什么意思?简单地忽略溢出就可以了。 - maaartinus
其中一种“适当”的处理方式(你称之为“忽略”)是返回正确的结果,但省略无法表示的高位比特。大多数编程语言都这样做。然而,如果出现溢出的语言返回最高可表示整数,或者抛出异常,则是“不适当”处理的示例,会导致此代码无法工作。 - DJClayworth

6

临时变量交换

最常用的交换方法是使用临时变量:

T swap = i;
i = j;
j = swap;
swap = null; // or let it fell out of scope

算术交换

i = i + j ;
j = i - j ; // i + j - j = i
i = i - j ; // i + j - (i + j - j) = j

如果以下条件成立,则此黑客技巧有效:

  • ij整数,它们的和在2147483647和-2147483648之间。
  • ij长整数,它们的和在9223372036854775807和-9223372036854775808之间。

xor 交换

有一个类似的xor交换黑客技巧。

i = i ^ j;
j = i ^ j; // i ^ j ^ j = i
i = i ^ j; // i ^ j ^ i ^ j ^ j = j

这是一个轻微变化的版本:

a[i] = a[i] ^ a[j];
a[j] = a[i] ^ a[j]; 
a[i] = a[i] ^ a[j]; 

只有在以下情况下,此黑客才有效:

  • i != j,两个索引引用相同的元素并互相抵消。

不要认为它们相等的那一位是正确的。如果是这样的话,由于所有位都是独立的,它将无法适用于任何具有重叠1或0位的数字。我很确定如果i = j,它确实可以工作。 - aronp

3
显然,对于其他代码而言,实现这个的方法如下:
const int tmp = i;
i = j;
j = tmp;

这假设类型为int。请注意,在计算中存在整数溢出/下溢风险时,该交换技巧并不安全。此外,它也不是很明确,读者需要非常努力地思考才能理解发生了什么(或者问别人)。


溢出/下溢在这里不是问题,它将独立于此工作(至少只要您使用2s补码)。 - Voo

2

它交换它们 - 但如果您使用不同的变量名称使事情更清晰,那么更容易看出来:

int originalI = ...;
int originalJ = ...;

int tmp = originalI + originalJ;
int newJ = tmp - originalJ;
int newI = tmp - newJ;

现在进行替换操作:
int originalI = ...;
int originalJ = ...;

int tmp = originalI + originalJ;
int newJ = originalI + originalJ - originalJ;
int newI = originalI + originalJ - (originalI + originalJ - originalJ);

...并简化:

int originalI = ...;
int originalJ = ...;

int tmp = originalI + originalJ;
int newJ = originalI;
int newI = originalJ;

...并删除临时变量:

int originalI = ...;
int originalJ = ...;

int newJ = originalI;
int newI = originalJ;

1

看起来它在交换 ij


1

它交换变量。

另一种方法:

i ^= j;
j ^= i;
i ^= j;

1
假设ij是数字原始类型,假设运算符的操作方式与Java、C++或类似语言中的操作方式相同,并且假设运算符未被重新定义,则我们可以这样看待这行代码:

i = i + j;
j = i - j;
i = i - j;

在这里,i成为两个变量的和。但在第二行代码中,我们从该总和中减去了j并将其分配给j。因此,我们可以将前两行简化为以下内容:

j = i;

现在,变量j等于我们最初的i。但目前,i的值与它最初的值不同。现在,i等于原始两个值的和。在下一行代码中,我们说:
i = i - j,在伪代码中相当于:
i = originalSum - originalValueOf_i; //原始的j

所以,基本上,所有原始代码都等同于一个交换例程,即:

tempVal = i;
i = j;
j = tempVal;


我们甚至可以尝试一个涉及正负值的示例来证明这是正确的:

i = -24.3;
j = 10.4;
//原始代码:
i = i + j; //i现在等于-13.9
j = i - j; //j现在等于-13.9 - 10.4,即-24.3(我们的原始i值)。
i = i - j; //i现在等于-13.9 -(-24.3),即10.4(我们的原始j)。


有人可能会按照他/她的方式编写您发布的交换,以避免方法调用开销、声明新变量等,但如果您要频繁交换值,则反复使用您发布的代码可能会变得棘手。像上面显示的交换例程是相当常见的。


0

它交换了ij的值:

i = i+j;
j = (original_i + j) - j = original_i;
i = original_i + original_j - original_i = original_j;

是的,有其他方法可以完成此操作


0

i 获得 j 的值。而 j 保持它的值。


但是当然,他给出的运算符之一是错误的,我曾经遇到过这个问题。 - Cu7l4ss

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接