轨道
/
Go
Go
/
练习
/
纸牌戏法
纸牌戏法

纸牌戏法

学习练习

简介

切片

Go 中的切片与其他语言中的列表或数组类似。 它们保存若干个特定类型(或接口)的元素。

Go 中的切片基于数组。 数组的大小是固定的。 而切片则是数组中元素的一个动态大小、灵活的视图。

切片的写法是[]T,其中T是切片中元素的类型:

var empty []int                 // an empty slice
withData := []int{0,1,2,3,4,5}  // a slice pre-filled with some data

你可以使用方括号表示法,通过从 0 开始的下标来获取或设置元素:

withData[1] = 5
x := withData[1] // x is now 5

你可以通过在现有切片上获取一段元素范围来创建新的切片。 同样使用方括号表示法,但要同时指定起始下标(包含)和结束下标(不包含)。 如果不指定起始下标,它默认为 0。 如果不指定结束下标,它默认为切片的长度。

newSlice := withData[2:4]
// => []int{2,3}
newSlice := withData[:2]
// => []int{0,1}
newSlice := withData[2:]
// => []int{2,3,4,5}
newSlice := withData[:]
// => []int{0,1,2,3,4,5}

你可以使用append函数向切片中添加元素。 下面我们向a切片中追加4和2。

a := []int{1, 3}
a = append(a, 4, 2)
// => []int{1,3,4,2}

append总是返回一个新的切片,当我们只是想向现有切片追加元素时,通常会像上面那样,把结果重新赋值给作为第一个实参传入的那个切片变量。

append也可以用来合并两个切片:

nextSlice := []int{100,101,102}
newSlice  := append(withData, nextSlice...)
// => []int{0,1,2,3,4,5,100,101,102}

可变参数函数

通常,Go 中的函数只接受固定数量的实参。 不过,在 Go 中也可以编写可变参数函数。

可变参数函数是一种可以接受可变数量实参的函数。

如果函数定义中最后一个形参的类型前面有省略号...,那么这个函数就可以为该形参接受任意数量的实参。

func find(a int, b ...int) {
    // ...
}

在上面的函数中,形参b是可变参数,我们可以向b传入 0 个或更多实参。

find(5, 6)
find(5, 6, 7)
find(5)
Caution

可变参数必须是函数的最后一个形参。

可变参数函数的工作方式,是把可变数量的实参转换为一个切片,其类型为可变参数的类型。

下面是一个可变参数函数实现的例子。

func find(num int, nums ...int) {
    fmt.Printf("type of nums is %T\n", nums)

    for i, v := range nums {
        if v == num {
            fmt.Println(num, "found at index", i, "in", nums)
            return
        }
    }

    fmt.Println(num, "not found in ", nums)
}

func main() {
    find(89, 90, 91, 95)
    // =>
    // type of nums is []int
    // 89 not found in  [90 91 95]

    find(45, 56, 67, 45, 90, 109)
    // =>
    // type of nums is []int
    // 45 found at index 2 in [56 67 45 90 109]

    find(87)
    // =>
    // type of nums is []int
    // 87 not found in  []
}

在上面程序的find(89, 90, 91, 95)这一行中,传给 find 函数的可变数量实参是90、91和95。 find函数期望在num之后有一个 int 类型的可变形参。 因此,这三个实参会被编译器转换为一个int类型的切片[]int{90, 91, 95},然后作为nums传给 find 函数。

有时你已经有一个切片,想把它传给一个可变参数函数。 这可以通过传入该切片并在后面加上...来实现。 这会告诉编译器在可变参数函数内部直接使用这个切片。 在这种情况下,上面描述的那个创建切片的步骤会被直接省略。

list := []int{1, 2, 3}
find(1, list...) // "find" defined as shown above

说明

作为一名准魔术师,Elyse 需要练习一些基本功。 她有一摞卡牌想要摆弄。

为了让事情简单一点,她只用 1 到 10 这十张牌。

1. 创建包含特定卡牌的切片

练习卡牌时,Elyse 喜欢先从整副牌中她最喜欢的三张开始:2、6 和 9。 编写一个函数 FavoriteCards,返回一个按该顺序包含这几张卡牌的切片。

cards := FavoriteCards()
fmt.Println(cards)
// Output: [2 6 9]

2. 从牌堆中取出一张卡牌

返回给定牌堆中 index 下标处的卡牌。

card := GetItem([]int{1, 2, 4, 1}, 2) // card == 4

如果下标越界(即它是负数,或超出了牌堆末尾),我们希望返回 -1:

card := GetItem([]int{1, 2, 4, 1}, 10) // card == -1
Note

按照 Go 的惯例,会返回一个错误,而不是返回一个“越界”值。 这里,当期望一个正整数时,“越界”值就是 -1。 返回错误时,惯常的做法是连同错误一起返回 zero value。 如何在返回错误的同时返回正确的返回值,将在以后的练习中介绍。

3. 替换牌堆中的一张卡牌

把 index 下标处的卡牌替换为提供的新卡牌,并返回调整后的牌堆。 注意,这会修改传入的切片,这正是预期的行为。

index := 2
newCard := 6
cards := SetItem([]int{1, 2, 4, 1}, index, newCard)
fmt.Println(cards)
// Output: [1 2 6 1]

如果下标越界(即它是负数,或超出了牌堆末尾),我们希望把新卡牌追加到牌堆末尾:

index := -1
newCard := 6
cards := SetItem([]int{1, 2, 4, 1}, index, newCard)
fmt.Println(cards)
// Output: [1 2 4 1 6]

4. 在牌堆顶部添加卡牌

把 value 形参指定的卡牌添加到牌堆顶部。

slice := []int{3, 2, 6, 4, 8}
cards := PrependItems(slice, 5, 1)
fmt.Println(cards)
// Output: [5 1 3 2 6 4 8]

如果没有为 value 形参传入实参,那么结果就等于原来的切片。

slice := []int{3, 2, 6, 4, 8}
cards := PrependItems(slice)
fmt.Println(cards)
// Output: [3 2 6 4 8]

5. 从牌堆中移除一张卡牌

从牌堆中移除 index 下标处的卡牌,并返回该牌堆。 注意,这可能会修改传入的切片,这是可以的。

cards := RemoveItem([]int{3, 2, 6, 4, 8}, 2)
fmt.Println(cards)
// Output: [3 2 4 8]

如果下标越界(即它是负数,或超出了牌堆末尾),我们希望保持牌堆不变:

cards := RemoveItem([]int{3, 2, 6, 4, 8}, 11)
fmt.Println(cards)
// Output: [3 2 6 4 8]
通过 GitHub 编辑 链接将在新窗口或新标签页中打开
Go Exercism

准备好开始 纸牌戏法 了吗?

注册 Exercism,借助 34 个概念165 个练习 和真人导师指导,学习并掌握 Go,全部免费。