交换两个变量而不使用临时变量


74

我希望能够交换两个变量,而无需在C#中使用临时变量。能做到吗?

decimal startAngle = Convert.ToDecimal(159.9);
decimal stopAngle = Convert.ToDecimal(355.87);

// Swap each:
//   startAngle becomes: 355.87
//   stopAngle becomes: 159.9


通过单击以下链接来参考我的答案。仅使用两个变量交换两个数字
Pankaj Lilan

1
十进制stopAngle = Convert.ToDecimal(159.9); 十进制startAngle = Convert.ToDecimal(355.87);

1
我建议选择@TimothyP提供的有关C#7元组的答案作为新的可接受答案。现在,这是实现目标的最实用方法。
布兰登·邦德斯

切勿将浮点数转换为小数。它应该是字符串,而不是浮点数,例如Convert.ToDecimal(“
159.9

Answers:


127

首先,像C#那样在没有临时变量的情况下进行交换是一个非常糟糕的主意

但是为了回答,您可以使用以下代码:

startAngle = startAngle + stopAngle;
stopAngle = startAngle - stopAngle;
startAngle = startAngle - stopAngle;

但是,如果两个数字相差很大,则四舍五入会出现问题。这是由于浮点数的性质。

如果要隐藏临时变量,可以使用实用程序方法:

public static class Foo {

    public static void Swap<T> (ref T lhs, ref T rhs) {
        T temp = lhs;
        lhs = rhs;
        rhs = temp;
    }
}

35
这对于整数或定点数很好。如果使用浮点数,则最终会产生微小的舍入错误。它们可能足够大,也可能不够大,取决于您如何使用数字。
Kennet Belenky 09年

7
只要您不遇到溢出问题,就可以正常工作
patjbs

144
解决此问题的唯一方法是使用临时变量。像这样的“聪明”代码(以及“聪明”,我的意思是“愚蠢”)比临时变量解决方案可读性和显而易见性低得多。如果我从一个仆从中看到了这样的代码,则它们将受到制裁并被送回去正确执行。我没有专门针对您,@CommuSoft(因为您回答了问题),但问题本身就是垃圾。
paxdiablo,2009年

4
@Janusz Lenar:在使用指针操作的语言中,您可以使用相同的技巧来交换指针。在C#中,您可以在不安全的环境中执行此操作。:D但是无论如何我都同意交换对象等。没有第三个变量是一个坏主意(对此做出回应。__curious_geek)。但问题是明确表示要在没有额外变量的情况下执行此操作。
Willem Van Onsem 2011年

14
该技术比使用临时变量在计算上更加昂贵。
迈克

221

交换两个变量的正确方法是:

decimal tempDecimal = startAngle;
startAngle = stopAngle;
stopAngle = tempDecimal;

换句话说,使用一个临时变量。

你有它。没有聪明的花招,没有代码的维护者来诅咒您数十年,没有进入The Daily WTF的条目,也没有花费太多的时间试图找出为什么需要一次操作,因为从最低的角度来看,甚至语言最复杂的功能是一系列简单的操作。

只是一个非常简单,易读,易于理解的t = a; a = b; b = t;解决方案。

我认为,开发人员尝试使用技巧来“交换变量而不使用临时变量”或“ Duff的设备”,这只是在试图展示它们的聪明程度(并失败了)。

我将它们比作那些阅读精挑细选的书,仅仅是为了让他们在聚会上看起来更加有趣(而不是扩大视野)。

与简单的“临时变量”解决方案(使用算术/布尔运算而不是在装配级进行简单移动)相比,在其中添加和减去的解决方案或基于XOR的解决方案可读性较低,并且很可能会变慢。

通过编写高质量的可读代码来为自己和他人提供服务。

那是我的怒吼。谢谢收听 :-)

顺便说一句,我很清楚这不能回答您的特定问题(对此我会道歉),但是在SO上有很多先例,人们问过如何做某事,正确的答案是“不要做到”。


8
+1; 并出于其他原因:使用+/-(等)技巧,您正在执行不必要的算术运算。对于整数,一按就可以接受(舍入/溢出不是问题,CPU成本实际上为零),但是对于十进制,加法和减法是不平凡的操作。它甚至不能使用FPU,因为它们不是float / double。所以已经使用临时变量了!!!
Marc Gravell

5
当然这是最好的方法,但是没有温度变量就明确提出了要求
Willem Van Onsem 09年

+1我同意你的看法。当您开始使简单的东西变得复杂时,您将在接下来的几年中遇到各种各样的问题需要解决……
尼尔森·里斯

2
也许有一个真正的理由不使用temp变量。如果这两个变量非常大,则您不想创建一个新变量,因此即使没有很长时间,也要拥有3个非常大的变量。
koumides

AFAIK解决的问题是如何在C中展开循环,从而允许在第一个或最后一个循环中使用非整数乘法器。您可以轻松地做到这一点,而无需还原到Duff的设备,并且更具可读性。如果您认为它解决了一个其他问题,而该问题无法更“可读地”解决,那么我很容易被说服。无论如何,即使它在过去的某个时候解决了问题,如今,即使在最简单的嵌入式系统上,也几乎不需要。
paxdiablo

145

C#7引入了元组,它可以交换两个变量而无需临时变量:

int a = 10;
int b = 2;
(a, b) = (b, a);

这就赋予baab


8
遗憾的是,此功能以前不存在,在那里他可能会学到一种强大的新语言结构,该结构将为他提供更多工具来破解问题,而不是上面的所有无用的语。
杰弗里·韦斯特

但是,这表现如何?我总是担心内部会在堆上创建一些“元组”实例,并且所有这些奇特的事情都会发生。


-1,我刚刚在IDEone上测试了这种方法,它不适用于原始类型,不太可能也适用于引用类型数据。ideone.com/03zt9U
TheBeardedQuack

@TheBeardedQuack您的链接显示正确的结果。为什么说它不适用于基本类型?
AlexWei

74

是的,使用以下代码:

stopAngle = Convert.ToDecimal(159.9);
startAngle = Convert.ToDecimal(355.87);

对于任意值,此问题更加困难。:-)


43
int a = 4, b = 6;
a ^= b ^= a ^= b;

适用于所有类型,包括字符串和浮点数。


22
我希望有一天会忘记XOR交换。
helpermethod 2010年

9
XOR交换接近书呆子的顶峰。在学校学习了几天后,我有了必杀技。
加百利·麦格纳

9
这似乎并没有在所有的工作stackoverflow.com/questions/5577140/...
安德鲁Savinykh

3
这是C#。上面的代码不会像@zespri所说的那样交换。最后一句话:在C#中,您不能将运算符^=string或一起使用float,因此不会与它们一起编译。
Jeppe Stig Nielsen 2015年

5
如果我曾经飞过美国政府。制造战斗机,或必须配备起搏器等,我非常希望我不会因为某些程序员“希望某天会忘记XOR交换而导致”的堆栈溢出而死
克里斯·贝克

22

BenAlabaster展示了一种进行变量切换的实用方法,但是不需要try-catch子句。这段代码就足够了。

static void Swap<T>(ref T x, ref T y)
{
     T t = y;
     y = x;
     x = t;
}

用法与他显示的相同:

float startAngle = 159.9F
float stopAngle = 355.87F
Swap(ref startAngle, ref stopAngle);

您还可以使用扩展方法:

static class SwapExtension
{
    public static T Swap<T>(this T x, ref T y)
    {
        T t = y;
        y = x;
        return t;
    }
}

像这样使用它:

float startAngle = 159.9F;
float stopAngle = 355.87F;
startAngle = startAngle.Swap(ref stopAngle);

两种方法都在该方法中使用一个临时变量,但是在进行交换时不需要使用临时变量。


2
是的,但仅在方法中,而不在您进行切换的地方。
Marcus

3
使用抽象是解决问题的好方法。它为常见问题提供了一种通用解决方案,并使调用代码更易于阅读。当然,它使用了一些额外的内存字节和一些额外的处理器周期,但是除非您多次调用此代码,否则您不会注意到任何区别。
Olivier Jacot-Descombes 2013年

@ OlivierJacot-Descombes,我希望如果您调用它一百万次,JIT会对其进行优化。
塞巴斯蒂安

2
所以问题是,为什么.NET库中没有包含这个方便的功能?这是Generic Methods文档中给出的第一个示例。
Mark Ransom

18

二进制XOR交换,带有详细示例:

XOR真值表

a b a^b
0 0  0
0 1  1
1 0  1
1 1  0

输入:

a = 4;
b = 6;

步骤1a = a ^ b

a  : 0100
b  : 0110
a^b: 0010 = 2 = a

步骤2b = a ^ b

a  : 0010
b  : 0110
a^b: 0100 = 4 = b

步骤3a = a ^ b

a  : 0010
b  : 0100
a^b: 0110 = 6 = a

输出:

a = 6;
b = 4;

13

不在C#中。在本机代码中,您也许可以使用三重XOR交换技巧,但不能使用高级类型安全语言。(无论如何,我听说XOR技巧实际上比许多常见CPU架构中使用临时变量要慢。)

您应该只使用一个临时变量。没有理由您不能使用它。这并不是说供应有限。


异或运算适用的大多数类型都适合寄存器,因此编译器无论如何都不应该为其分配堆栈空间。
BCS

是的,但是要复杂得多。您很少需要在汇编程序级别交换值。交换通常可以作为其他算法的副作用来完成。大多数时候,只需要交换就可以用高级语言表达事物。编译之后,交换不再存在,因此根本不花费时间:-)
Nils Pipenbrinck

如果内存不足,例如在嵌入式设备上,则临时变量有时会短缺。
AsherMaximum

@AsherMaximum:但是如果C#提供了一种交换两个变量的方法,那就更好了。然后可以在有或没有temp变量的情况下实现。通过显式地执行该操作,该代码将变得不可读且难以维护。
Willem Van Onsem 2015年

13

为了将来的学习者和人类,我将此更正提交给当前选择的答案。

如果要避免使用临时变量,则只有两个明智的选择会首先考虑性能,然后考虑可读性。

  • 在通用Swap方法中使用临时变量。(绝对最佳性能,内联温度变量旁边)
  • 使用Interlocked.Exchange。(在我的计算机上慢5.9倍,但这是您的唯一选择,如果多个线程将同时交换这些变量。)

事情你应该永远不会做的事:

  • 切勿使用浮点运算。(缓慢,舍入和溢出错误,很难理解)
  • 切勿使用非原始算术。(缓慢,溢出错误,难以理解)Decimal不是CPU原语,并且导致的代码远远超出您的想象。
  • 切勿使用算术期间。还是有点骇客。(缓慢,难以理解)这就是编译器的工作。它可以针对许多不同的平台进行优化。

因为每个人都喜欢硬数字,所以这里有一个程序比较您的选择。从Visual Studio外部以发布模式运行它,以便进行Swap内联。我的机器上的结果(Windows 7 64位i5-3470):

Inline:      00:00:00.7351931
Call:        00:00:00.7483503
Interlocked: 00:00:04.4076651

码:

class Program
{
    static void Swap<T>(ref T obj1, ref T obj2)
    {
        var temp = obj1;
        obj1 = obj2;
        obj2 = temp;
    }

    static void Main(string[] args)
    {
        var a = new object();
        var b = new object();

        var s = new Stopwatch();

        Swap(ref a, ref b); // JIT the swap method outside the stopwatch

        s.Restart();
        for (var i = 0; i < 500000000; i++)
        {
            var temp = a;
            a = b;
            b = temp;
        }
        s.Stop();
        Console.WriteLine("Inline temp: " + s.Elapsed);


        s.Restart();
        for (var i = 0; i < 500000000; i++)
        {
            Swap(ref a, ref b);
        }
        s.Stop();
        Console.WriteLine("Call:        " + s.Elapsed);

        s.Restart();
        for (var i = 0; i < 500000000; i++)
        {
            b = Interlocked.Exchange(ref a, b);
        }
        s.Stop();
        Console.WriteLine("Interlocked: " + s.Elapsed);

        Console.ReadKey();
    }
}

1
实际上,位黑客一点也不慢。例如,为了加载0到寄存器中,由于指令较短,编译器会将变量与自身进行或。在极少数情况下,通过使用较少的变量,可以避免寄存器溢出。这是编译器的工作,但是不幸的是,没有语言支持来有效地执行此操作。
Willem Van Onsem 2015年

你是对的。如果这是一个热点,而速度问题很严重,那么您将需要不受管理。比特黑客通常会适得其反。这确实是抖动的责任。
jnm2


8

<不推荐使用>

您可以使用基本数学在3行中完成此操作-在我的示例中,我使用了乘法,但是简单的加法也可以。

float startAngle = 159.9F;
float stopAngle = 355.87F;

startAngle = startAngle * stopAngle;
stopAngle = startAngle / stopAngle;
startAngle = startAngle / stopAngle;

编辑:如注释中所述,如果y = 0则不起作用,因为它将生成除以零的错误,而我没有考虑过。因此,替代地提出的+/-解决方案将是最好的方法。

</不建议使用>


为了使我的代码立即可理解,我将更可能这样做。[总是考虑那个必须维护您的代码的可怜的人]:

static bool Swap<T>(ref T x, ref T y)
{
    try
    {
        T t = y;
        y = x;
        x = t;
        return true;
    }
    catch
    {
        return false;
    }
}

然后,您可以在一行代码中做到这一点:

float startAngle = 159.9F
float stopAngle = 355.87F
Swap<float>(ref startAngle, ref stopAngle);

要么...

MyObject obj1 = new MyObject("object1");
MyObject obj2 = new MyObject("object2");
Swap<MyObject>(ref obj1, ref obj2);

像晚餐一样做...您现在可以传递任何类型的物体并切换它们...


几乎等于我的解决方案,但请注意乘法和除法运算会花费大量CPU时间。而且浮点数的上限和下限更容易通过乘法达到。
Willem Van Onsem 09年

“小数不允许小数位”是什么意思?这听起来令人困惑,因为十进制类型确实以高精度表示实数(十进制d = 9.1m;在C#中完全有效)。
Dirk Vollmar,09年

1
@CommuSoft:要点,我没有考虑过。在这种情况下,+ /-无疑是更好的选择。
BenAlabaster

2
您的交换方式如何?为什么它返回布尔值,为什么布尔值始终为真(如果存在)?为什么它吞没了所有异常(我相信在这种情况下,它只能是ThreadAbortException,因为它不会分配内存或扩大调用堆栈)?
Doug McClean

1
不涉及数组方差的简单强类型分配永远不会抛出异常。类型不匹配将在编译时捕获(这就是强类型的含义)。
Olivier Jacot-Descombes 2013年

7

为了完整起见,以下是二进制XOR交换:

int x = 42;
int y = 51236;
x ^= y;
y ^= x;
x ^= y;

这适用于所有原子对象/引用,因为它直接处理字节,但是可能需要一个不安全的上下文来处理小数点,或者如果确实感到不舒服,则使用指针。在某些情况下,它也可能比temp变量慢。



6

使用C#7,您可以使用元组分解在一行中实现所需的交换,并且很清楚发生了什么。

decimal startAngle = Convert.ToDecimal(159.9);
decimal stopAngle = Convert.ToDecimal(355.87);

(startAngle, stopAngle) = (stopAngle, startAngle);

5

当心您的环境!

例如,这似乎在ECMAscript中不起作用

y ^= x ^= y ^= x;

但这确实

x ^= y ^= x; y ^= x;

我的建议?假设尽可能少。


即使在c上,如果涉及指针(例如在函数中)* a ^ = * b ^ = * a ^ = * b不起作用(但是局部变量起作用,例如c ^ = d ^ = c ^ = d),但是* a ^ = * b ^ = * a; * b ^ = * a; 作品。因此,我的选择是使用* a ^ = * b; * b ^ = * a; * a ^ = * b; 完美地运作。
普拉莫德(Pramod)2012年

1
ECMAscript有何关联?该问题被标记为C#。
彼得·莫滕森

4

在一行中交换2个数字的简单方法:

a=(a+b)-(b=a);

例如:a = 1,b = 2

步骤1:a =(1 + 2)-(b = 1)

步骤2:a = 3-1

=> a = 2和b = 1


高效的方法是使用:

C语言编程: (x ^= y), (y ^= x), (x ^= y);

Java: x = x ^ y ^ (y = x);

蟒蛇: x, y = y, x

注意:人们最容易犯的错误是: //使用按位XOR交换(C / C ++中的错误解决方案)

x ^= y ^= x ^= y; 

资料来源:GeeksforGeek


正确的c ++ :std::swap(x, y);
清晰的


4

与元组

decimal startAngle = Convert.ToDecimal(159.9);
decimal stopAngle = Convert.ToDecimal(355.87);

(startAngle, stopAngle) = (stopAngle, startAngle);

3

对于二进制类型,您可以使用以下时髦技巧:

a %= b %= a %= b;

只要a和b不是完全相同的变量(例如,相同内存的别名),它就可以工作。


3

我希望这会有所帮助...

using System;

public class Program
{
    public static void Main()
    {
        int a = 1234;
        int b = 4321;

        Console.WriteLine("Before: a {0} and b {1}", a, b);

        b = b - a;
        a = a + b;
        b = a - b;

        Console.WriteLine("After: a {0} and b {1}", a, b);
    }
}

2
startAngle = (startAngle + stopAngle) - (stopAngle = startAngle);

2

我们可以做一个简单的技巧

a = 20;
b = 30;
a = a+b; // add both the number now a has value 50
b = a-b; // here we are extracting one number from the sum by sub
a = a-b; // the number so obtained in above help us to fetch the alternate number from sum
System.out.print("swapped numbers are a = "+ a+"b = "+ b);

1

如果要交换2个字符串变量:

a = (a+b).Substring((b=a).Length);

相应的辅助方法:

public static class Foo {
    public static void SwapString (ref string a, ref string b) {
       a = (a+b).Substring((b=a).Length);
    }
}

用法如下:

string a="Test 1";
string b="Test 2";
Foo.SwapString(a, b);

0

这是一行中的另一种方法:

decimal a = 159.9m;
decimal b = 355.87m;

a = b + (b = a) - b;

0

这是交换两个变量的一些不同过程

//process one
a=b+a;
b=a-b;
a=a-b;
printf("a= %d  b=  %d",a,b);

//process two
a=5;
b=10;
a=a+b-(b=a);
printf("\na= %d  b=  %d",a,b);

//process three
a=5;
b=10;
a=a^b;
b=a^b;
a=b^a;
printf("\na= %d  b=  %d",a,b);

//process four
a=5;
b=10;
a=b-~a-1;
b=a+~b+1;
a=a+~b+1;
printf("\na= %d  b=  %d",a,b);


-4

交换两个变量的非常简单的代码:

static void Main(string[] args)
{
    Console.WriteLine("Prof.Owais ahmed");
    Console.WriteLine("Swapping two variables");

    Console.WriteLine("Enter your first number ");
    int x = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("Enter your first number ");
    int y = Convert.ToInt32(Console.ReadLine());

    Console.WriteLine("your vlaue of x is="+x+"\nyour value of y is="+y);

    int z = x;
    x = y;
    y = z;

    Console.WriteLine("after Swapping value of x is="+x+"/nyour value of y is="+y);
    Console.ReadLine();
}

9
可以肯定的是,z这里算作一个临时变量。
同步

1
@等时,如果您将其移出功能并使其变为静态它将不再是临时的大声笑
artman 18'Aug

-4

您可以尝试以下代码。它比其他代码要好得多。

a = a + b;
b = a - b;
a = a - b;

5
这与其他几个答案完全相同。其中一些已在7年前添加!
Bo Persson
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.