C# C基本堆栈算术运算
我有两门课,一门是计算器课,另一门是测试课。Test类包含多个预期输出Test和calculator包含执行算术运算的方法我的问题是:为什么TestOne输出105而不是9,TestTwo输出6而不是2 以下是测试C# C基本堆栈算术运算,c#,math,stack,C#,Math,Stack,我有两门课,一门是计算器课,另一门是测试课。Test类包含多个预期输出Test和calculator包含执行算术运算的方法我的问题是:为什么TestOne输出105而不是9,TestTwo输出6而不是2 以下是测试 public void TestOne() { var c= new Calc(); c.Add(5); c.Add(7); c.Undo(); c.Subtract(2); c.Multiply(7); c.Undo();
public void TestOne()
{
var c= new Calc();
c.Add(5);
c.Add(7);
c.Undo();
c.Subtract(2);
c.Multiply(7);
c.Undo();
c.Multiply(3);
}
public void TestTwo()
{
var c= new Calc();
c.Add(2);
c.Add(3);
c.Add(4);
ex.Undo();
c.RepeatLastCommand();
}
下面是应用程序的功能
public class Calc
{
int total= 0;
Stack<int> stack = new Stack<int>();
public int Value
{
get { return total; }
set { total= value; }
}
public void Add(int value)
{
total = total + value;
stack.Push(value);
}
public void Subtract(int value)
{
total = total - value;
stack.Push(total);
}
public void Multiply(int value)
{
total= total * value;
stack.Push(total);
}
public void RepeatLastCommand()
{
int topOfStack =stack.Peek();
total += topOfStack;
}
public void Undo()
{
total = stack.Pop();
if (stack.Count > 0)
{
int safe = stack.Pop();
}
}
}
乍一看,您需要将撤消方法更改为
public void Undo()
{
if(stack.Count > 0 ) stack.Pop();
total = stack.Peek();
}
此外,Add方法应该将Total变量而不是传递的值推送到堆栈上
public void Add(int value)
{
total = total + value;
stack.Push(total);
}
但是,解析RepeatLastCommand操作要复杂一点,并且无法使用此结构实现,您还需要将执行的操作和该操作中使用的值存储在堆栈变量中
我认为csdp000在另一个答案中提出的解决方案可以让您走上正确的道路
相反,关于您澄清撤销中的更改的请求,很简单。Pop操作从堆栈顶部提取上次推送的值,该值为当前总计,您有兴趣恢复以前的值,因此放弃实际的顶部并将新的堆栈顶部分配给总计
编辑
在考虑了RepeatLastAction之后,我重写了您的类,以存储一个包含Calc类执行的操作的所有信息的类实例,而不是整数
public class Calc
{
bool undoAction = false;
int total = 0;
Stack<CalcAction> stack = new Stack<CalcAction>();
public int Value
{
get
{
return total;
}
set
{
total = value;
}
}
public void Add(int value)
{
CalcAction act = new CalcAction()
{
operation = Add,
actionTotal = total + value,
actionValue = value
};
total = act.actionTotal;
stack.Push(act);
undoAction = false;
}
public void Subtract(int value)
{
CalcAction act = new CalcAction()
{
operation = Subtract,
actionTotal = total - value,
actionValue = value
};
total = act.actionTotal;
stack.Push(act);
undoAction = false;
}
public void Multiply(int value)
{
CalcAction act = new CalcAction()
{
operation = Multiply,
actionTotal = total * value,
actionValue = value
};
total = act.actionTotal;
stack.Push(act);
undoAction = false;
}
public void RepeatLastCommand()
{
if (stack.Count > 0)
{
if(undoAction)
Undo();
else
{
CalcAction act = stack.Peek();
act.operation(act.actionValue);
}
}
}
public void Undo()
{
if (stack.Count > 0) stack.Pop();
total = ((CalcAction)stack.Peek()).actionTotal;
undoAction = true;
}
internal class CalcAction
{
public Action<int> operation;
public int actionValue;
public int actionTotal;
}
}
这里的主要变化是内部类calAction,它与当前操作的信息一起存储在堆栈中。这允许RepeatLastAction方法知道要重新执行的操作。唯一的例外是撤销操作,由于其签名不同于加法/减法/乘法,因此它被排除在堆栈之外。请使用此代码
添加操作信息
public class CalcFix
{
struct OperStruct
{
public Action<int> OperFunc;
public int OperValue;
public static OperStruct OperSet(Action<int> _operFunc, int _operValue)
{
OperStruct operStru = new OperStruct();
operStru.OperFunc = _operFunc;
operStru.OperValue = _operValue;
return operStru;
}
}
private bool _undo = false;
private int _total = 0;
private Dictionary<Action<int>, Action<int>> _reverseOper = new Dictionary<Action<int>, Action<int>>();
private Stack<OperStruct> stack = new Stack<OperStruct>();
public CalcFix()
{
_reverseOper.Add(Add, Subtract);
_reverseOper.Add(Subtract, Add);
_reverseOper.Add(Multiply, Division);
_reverseOper.Add(Division, Multiply);
}
public int Value { get { return _total; } }
public void Add(int value)
{
_total += value;
_undo = false;
stack.Push(OperStruct.OperSet(Add, value));
}
public void Subtract(int value)
{
_total -= value;
_undo = false;
stack.Push(OperStruct.OperSet(Subtract, value));
}
public void Multiply(int value)
{
_total *= value;
_undo = false;
stack.Push(OperStruct.OperSet(Multiply, value));
}
public void Division(int value)
{
_total /= value;
_undo = false;
stack.Push(OperStruct.OperSet(Division, value));
}
public void Undo()
{
OperStruct operFunc = stack.Pop();
if (operFunc.OperFunc != null && _reverseOper.ContainsKey(operFunc.OperFunc))
{
_reverseOper[operFunc.OperFunc](operFunc.OperValue);
_undo = true;
}
}
public void RepeatLastCommand()
{
OperStruct topOfStack = stack.Peek();
if (stack.Count > 1)
{
if (topOfStack.OperFunc != null)
{
if (!_undo) // if not called undo
_reverseOper[topOfStack.OperFunc](topOfStack.OperValue);
else // called undo
{
stack.Pop();
topOfStack = stack.Pop();
_reverseOper[topOfStack.OperFunc](topOfStack.OperValue);
_undo = true;
}
}
}
}
}
public static void TestTwo()
{
var c = new CalcFix();
c.Add(2);
c.Add(3);
c.Add(4);
System.Console.WriteLine(c.Value);
c.Undo();
System.Console.WriteLine(c.Value);
c.RepeatLastCommand();
System.Console.WriteLine(c.Value);
c.RepeatLastCommand();
System.Console.WriteLine(c.Value);
}
static void Main(string[] args)
{
TestTwo();
}
更改代码
输出:
9
5.
2.
0此类中有许多错误 1 Add是推送值,而其他操作是推送总计 2如果需要撤消,则所有操作都应在修改之前推送总计。 3“撤消”只需执行一次弹出操作。 4 RepeatLastCommand没有意义,因为您只在堆栈中存储结果,而不存储操作 编辑 在上一部分中,我试图回答你最初的问题——为什么你会得到这样的结果。但是,如果问题是如何使它工作,这里是最简单的方法。如上所述,为了支持撤销,我们只需要在堆栈中存储上一个值。为了支持RepeatLastCommand,我们只需要存储最后一个命令:- 以下是一种可能的实现方式:
public class Calc
{
int total = 0;
Stack<int> stack = new Stack<int>();
Action lastCommand;
public int Value
{
get { return total; }
set { Execute(() => total = value); }
}
public void Add(int value)
{
Execute(() => total += value);
}
public void Subtract(int value)
{
Execute(() => total -= value);
}
public void Multiply(int value)
{
Execute(() => total *= value);
}
public void Divide(int value)
{
Execute(() => total /= value);
}
public void Negate()
{
Execute(() => total = -total);
}
public void RepeatLastCommand()
{
if (lastCommand != null)
lastCommand();
}
public void Undo()
{
Execute(() => { if (stack.Count > 0) total = stack.Pop(); }, undoable: false);
}
private void Execute(Action command, bool undoable = true)
{
var oldValue = total;
command();
lastCommand = command;
if (undoable) stack.Push(oldValue);
}
}
请注意,我们不需要像其他答案中那样的特殊标志或类,因为C闭包将为我们完成所有这些,并且还允许我们抽象操作并支持二进制、一元或无arg操作。出于演示目的,我添加了求反和除法操作,以及使值集操作可撤消。如果不需要,您可以将其删除。当您在调试器中逐步执行此操作时,我们不会为您执行此操作,具体来说,逻辑在哪里偏离了您的预期?我能想到的唯一问题是我正在应用的逻辑来完成操作。我对这个领域基本上是新手,所以我希望有人能在几个小时的努力后发现一个错误。我真的不确定撤销方法在做什么,真的。Undo和RepeatLastCommand似乎都不支持除了加法之外的任何操作,我甚至不认为Undo正确地支持加法。最好的方法是在调试器中单步执行,并跟踪每行代码后运行的值。这应该会告诉你一个值在什么地方发生了意外的变化。@David我是Git上的克隆src,但我正在尝试,我对这一点还是比较陌生的。例如,在TestTwo上重复。如果执行了Repeat,它应该执行undo,但我还没来得及调用它。他应该使用命令模式,但可能有点太高级了。你好,史蒂夫。从这个小小的变化来看,它似乎已经修复了TestOne,但TestOne仍然是一个问题。但是System.InvalidOpertation:Stack empty触发了谢谢,您能解释一下区别吗。您希望RepeatLastCommand会发生什么?这似乎无法用这种结构解决,您还需要存储运算符,在撤消的情况下,我的意思是,如果在撤消后调用RepeatLastCommand,您希望执行撤消,而如果在加法/减法/除法/乘法后使用RepeatLastCommand,您希望再次执行该命令,但此时,您还需要存储最后一个操作以及传递给该操作的参数。仅仅一个堆栈是不够的。可以存储操作吗?我来看看这个。TestOne正在抛出10你知道为什么吗?同意RepeatLastCommand。我确实一步一步地完成了它,当它在第二个测试中遇到这个问题时,它没有撤消Add3或撤消,而是实际上撤消了一个
Add5是Add2和Add3的总和。恐怕需要相当多的编码才能使它正常工作。这似乎与我试图编写以解决RepeatLastCommand问题的代码非常相似。好的工作唯一的问题似乎是如果RepeatLastCommand跟随一个加法/减法或乘法。它将恢复为撤消,而不是重复加法/减法运算/Multiply@Steve谢谢,你的代码非常干净。你对如何解决这个问题有什么建议吗?在我的代码中,我将撤销操作保留在堆栈之外,并设置一个标志来处理这种特殊情况。可能它适用于您的代码,但更优雅的解决方案当然是更好的尝试,但通常不起作用。零乘和7/3等整数除法不能以这种方式撤消。