我想在C#中交换两个变量而不使用临时变量,这可能吗?
decimal startAngle = Convert.ToDecimal(159.9);
decimal stopAngle = Convert.ToDecimal(355.87);
// Swap each:
// startAngle becomes: 355.87
// stopAngle becomes: 159.9
我想在C#中交换两个变量而不使用临时变量,这可能吗?
decimal startAngle = Convert.ToDecimal(159.9);
decimal stopAngle = Convert.ToDecimal(355.87);
// Swap each:
// startAngle becomes: 355.87
// stopAngle becomes: 159.9
在这个问题被提出的时候(1),交换两个变量的正确方法是使用一个临时变量:
decimal tempDecimal = startAngle;
startAngle = stopAngle;
stopAngle = tempDecimal;
就是这样。没有聪明的技巧,没有你的代码维护者在未来几十年里咒骂你,也不会花太多时间试图弄清楚为什么你需要在一个操作中使用它,因为即使是最复杂的语言特性,在最低层次上也是一系列简单的操作。
只需要一个非常简单、易读、易理解的 temp = a; a = b; b = temp;
解决方案。
我认为,那些试图使用技巧来“交换变量而不使用临时变量”或“达夫设备”的开发人员只是想展示他们有多聪明(但却失败了)。
我把他们比作那些只为了在派对上显得更有趣而阅读高深书籍的人(而不是拓展自己的视野)。
加减或基于XOR的解决方案不如一个简单的“临时变量”解决方案可读性好,而且很可能比汇编级别的普通移动更慢。
通过编写高质量易读的代码,为自己和他人提供服务。
这就是我的发泄。谢谢倾听 :-)
顺便说一下,我非常清楚这并没有回答你的具体问题(我为此道歉),但在SO上有很多先例,人们问如何做某事,而正确的答案是“不要这样做”。(1)自那时起,语言和/或.NET Core的改进采用了“Pythonic”的方式,使用元组。现在您只需执行以下操作:
(startAngle, stopAngle) = (stopAngle, startAngle);
交换值。这几乎不会改变底层操作,但至少有一个小优点,即不必在代码中引入临时命名变量。实际上,您可以看到它仍然使用一个临时变量(通过将值推送/弹出堆栈)在幕后进行,使用 (a, b) = (b, a)
语句:
IL_0005: ldloc.1 ; push b
IL_0006: ldloc.0 ; push a
IL_0007: stloc.2 ; t = pop (a)
IL_0008: stloc.0 ; a = pop (b)
IL_0009: ldloc.2 ; push t
IL_000a: stloc.1 ; b = pop (t)
首先,在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;
}
}
是的,使用以下代码:
stopAngle = Convert.ToDecimal(159.9);
startAngle = Convert.ToDecimal(355.87);
对于任意值,这个问题更加困难。 :-)
int a = 4, b = 6;
a ^= b ^= a ^= b;
适用于所有类型,包括字符串和浮点数。
^=
运算符与string
或float
一起使用,因此它们无法编译。 - Jeppe Stig NielsenBenAlabaster展示了一种实用的变量切换方法,但不需要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);
这两种方法都在函数中使用了一个临时变量,但是在进行交换的地方是不需要这个临时变量的。
一个带有详细示例的二进制异或交换:
XOR 真值表:
a b a^b
0 0 0
0 1 1
1 0 1
1 1 0
输入:
a = 4;
b = 6;
Step 1: a = a ^ b
a : 0100
b : 0110
a^b: 0010 = 2 = a
Step 2: b = a ^ b
a : 0010
b : 0110
a^b: 0100 = 4 = b
Step 3: a = a ^ b
a : 0010
b : 0100
a^b: 0110 = 6 = a
输出:
a = 6;
b = 4;
在C# 7中:
(startAngle, stopAngle) = (stopAngle, startAngle);
在 C# 中不行。在本地代码中,你可能能够使用三重异或交换技巧,但在高级类型安全语言中不行。(无论如何,我听说在许多常见的 CPU 架构中,实际上使用异或技巧会比使用临时变量更慢。)
你应该只是使用一个临时变量。没有理由你不能使用它;这并不像有限制供应一样。
为了未来的学习者和人类,我提供此更正意见以修正当前所选答案。
如果你想避免使用临时变量,有只有两个明智的选择,分别考虑了性能和可读性。
Swap
方法中使用临时变量。(与内联临时变量相比,性能最佳)Interlocked.Exchange
。(在我的机器上慢5.9倍,但这是您唯一的选择,如果多个线程将同时交换这些变量。)你永远不应该做的事情:
Decimal
不是 CPU 原语,并且会产生比您意识到的更多的代码。因为每个人都喜欢硬性数字,这里有一个比较选项的程序。在 Visual Studio 外部的发布模式下运行,以使得 Swap
能够内联。我的机器上 (Windows 7 64-bit 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();
}
}
0
加载到寄存器中,编译器会使用 异或 操作将变量与自身进行运算,因为这条指令更短。在某些罕见的情况下,通过使用较少的变量,可以避免寄存器溢出。这是编译器的工作,但不幸的是,目前没有语言支持以高效的方式执行此操作。 - Willem Van Onsem