这个函数为什么会改变输入参数?

huangapple go评论68阅读模式
英文:

Golang - Why does this function change the input argument?

问题

我正在学习Golang,我之前是使用PHP的。有时候我对一些核心功能有一些困惑。

具体来说,我正在构建一个纸牌游戏,并创建了一个CardStack类型,其中包含一些方便的方法,可以在纸牌堆中使用(比如玩家的手牌、弃牌堆等),例如DrawCards(...)AppendCards(...)等。

问题是,我有一个函数func (c* CardStack) DrawCards(cards []deck.Card) ([]deck.Card, error),它会改变参数cards []deck.Card,我无法弄清楚为什么会这样,也不知道如何避免这种情况。

这是我的CardStack结构体:

type CardStack struct {
	cards []deck.Card
}

这是我的DrawCards方法:

func (c *CardStack) DrawCards(cards []deck.Card) ([]deck.Card, error) {
	return c.getCardsSlice(cards, true)
}

// 返回缺失的卡牌
func (c *CardStack) getCardsSlice(cards []deck.Card, rm bool) ([]deck.Card, error) {
	var err error
	var returnc = []deck.Card{}
	for _, card := range cards {
		fmt.Println("BEFORE c.findCard(cards): ")
		deck.PrintCards(cards) // 在我的例子中,这将打印出{Kc, 8d},这是我期望的结果
		_, err = c.findCard(card, rm) // 在这行之后,cards变量被改变了
		fmt.Println("AFTER c.findCard(cards): ")
		deck.PrintCards(cards) // 在我的例子中,这将打印出{8d, 8d},这完全不是我期望的结果
		if err != nil {
			return returnc, err
		}
	}
	return returnc, nil
}

// 期望的输入是类似"Ts"或"2h"的字符串(1. 牌面 2. 花色)
func (c *CardStack) findCard(cc deck.Card, rm bool) (deck.Card, error) {
	for i, card := range c.GetCards() {
		if cc == card {
			return c.cardByIndex(i, rm)
		}
	}
	return deck.Card{}, fmt.Errorf("Card not found")
}

func (c *CardStack) cardByIndex(n int, rm bool) (deck.Card, error) {
	if n > len(c.GetCards()) {
		return deck.Card{}, fmt.Errorf("Index out of bounds")
	}

	card := c.GetCards()[n]
	if rm {
		c.SetCards(append(c.GetCards()[:n], c.GetCards()[n+1:]...))
	}
	return card, nil
}

为了更好地解释,特别是在getCardsSlice中调用的findCard(...)方法会修改原始值(我已经添加了注释来指示发生的位置)。

如果有帮助的话,这是我用于调试的main()方法的一部分:

// ...
ss, _ := cards.SubStack(1, 3) // ss 现在包含{Kc, 8d}
ss.Print() // 打印{Kc, 8d}
cards.Print() // 打印{5c, Kc, 8d}(在代码的其他地方赋值)
cards.DrawCards(ss) // 从{5c, Kc, 8d}中抽取{Kc, 8d}
cards.Print() // 打印{5c} - 如预期
ss.Print() // 打印{8d, 8d} - ???

我做错了什么,应该如何解决这个问题。

非常感谢任何帮助。

编辑

整个CardStack文件:http://pastebin.com/LmhryfGc

编辑2

我打算迟早将其放在GitHub上(希望代码看起来还行),这是链接:https://github.com/d1am0nd/hearths-go/tree/cardstack/redo

英文:

I'm learning Golang and I come from PHP background. I have a bit of trouble understanding some of the core functionalities at times.

Specifically, right now I'm building a Hearths game and I've created a CardStack type, that has some convenient methods one might use in a card stack (read: player's hand, discard pile...) such as DrawCards(...), AppendCards(...)...

The problem I have is that the function func (c* CardStack) DrawCards(cards []deck.Card) ([]deck.Card, error) {...} changes the argument cards []deck.Card and I cannot figure out why or how to avoid this.

This is my CardStack:

type CardStack struct {
	cards []deck.Card
}

This is my DrawCards method:

func (c *CardStack) DrawCards(cards []deck.Card) ([]deck.Card, error) {
	return c.getCardsSlice(cards, true)
}

// Returns cards that are missing
func (c *CardStack) getCardsSlice(cards []deck.Card, rm bool) ([]deck.Card, error) {
	var err error
	var returnc = []deck.Card{}
	for _, card := range cards {
		fmt.Println("BEFORE c.findCard(cards): ")
		deck.PrintCards(cards) // In my example this will print out {Kc, 8d}, which is what I expect it to be
		_, err = c.findCard(card, rm) // AFTER THIS LINE THE cards VAR IS CHANGED
		fmt.Println("AFTER c.findCard(cards): ")
		deck.PrintCards(cards) // In my example this will print out {8d, 8d}, which is not at all what I expected
		if err != nil {
			return returnc, err
		}
	}
	return returnc, nil
}

// Expects string like "Ts" or "2h" (1. face 2. suit)
func (c *CardStack) findCard(cc deck.Card, rm bool) (deck.Card, error) {
	for i, card := range c.GetCards() {
		if cc == card {
			return c.cardByIndex(i, rm)
		}
	}
	return deck.Card{}, fmt.Errorf("Card not found")
}

func (c *CardStack) cardByIndex(n int, rm bool) (deck.Card, error) {
	if n > len(c.GetCards()) {
		return deck.Card{}, fmt.Errorf("Index out of bounds")
	}

	card := c.GetCards()[n]
	if rm {
		c.SetCards(append(c.GetCards()[:n], c.GetCards()[n+1:]...))
	}
	return card, nil
}

To explain a bit more - specifically the findCard(...) method that gets called in getCardsSlice messes with the original value (I've added comments to indicate where it happens).

If it's of any help, this is part of my main() method that I use for debugging:

// ...
ss, _ := cards.SubStack(1, 3) // ss now holds {Kc, 8d}
ss.Print() // Prints {Kc, 8d}
cards.Print() // Prints {5c, Kc, 8d} (assigned somewhere up in the code)
cards.DrawCards(ss) // Draws {Kc, 8d} from {5c, Kc, 8d}
cards.Print() // Prints {5c} - as expected
ss.Print() // Prints {8d, 8d} - ???

What am I doing wrong and how should I go about doing this.

Any kind of help is appreciated.

Edit:

The whole CardStack file: http://pastebin.com/LmhryfGc

Edit2:

I was going to put it on github sooner or later (was hoping after the code looks semi-ok), here it is - https://github.com/d1am0nd/hearths-go/tree/cardstack/redo

答案1

得分: 5

在你的示例中,DrawCards 函数中的 cards 值是 CardsStack.cards 切片的子切片,它引用了相同的底层数组中的值。

当你调用 findCard 并从 CardStack.cards 切片中移除一张卡片时,你正在操作与 cards 参数使用的相同数组。

当你想要一个切片的副本时,你需要分配一个新的切片并复制每个元素。在你的示例中,你可以这样做:

ssCopy := make([]deck.Card, len(ss))
copy(ssCopy, ss)
cards.DrawCards(ssCopy)
英文:

In your example, the value of cards in DrawCards is a sub-slice of the CardsStack.cards slice, which is referencing values in the same backing array.

When you call findCard and remove a card from the CardStack.cards slice, you are manipulating the same array that the cards argument is using.

When you want a copy of a slice, you need to allocate a new slice and copy each element. To do this in your example, you could:

ssCopy := make([]deck.Card, len(ss))
copy(ssCopy, ss)
cards.DrawCards(ssCopy)

huangapple
  • 本文由 发表于 2017年1月31日 03:48:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/41944253.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定