|
|
8.结构体
结构体struct是字段的集合,并且可以通过[struct].[item]的方式来访问和修改。
- package main
- import "fmt"
- type Vertex struct {
- X int
- Y int
- }
- func main() {
- v := Vertex{1, 2}
- //方式一:通过指针
- p := &v
- p.X = 1e9
- //////////
- //方式二:直接修改
- v.Y = 5
- fmt.Println(v)
- }
复制代码 输出结果:{1000000000 5}
这似乎有些类似于python中的字典。
9.数组★
数组的操作比较多,本章内容会比上面长。
数组在 Go 中是值类型,赋值或传参时会复制整个数组。
类型 [n]T 是 n 个类型为 T 的值的数组。
通过关键字var声明。
我们暂时可以理解为python中的列表,下面是两种主要声明方式,一种使用var声明后再赋值,另一种使用`:=`和花括号:
- package main
- import "fmt"
- func main() {
- var a [2]string
- a[0] = "Hello"
- a[1] = "World"
- fmt.Println(a[0], a[1])
- fmt.Println(a)
- primes := [6]int{2, 3, 5, 7, 11, 13}
- fmt.Println(primes)
- }
复制代码 输出:- Hello World
- [Hello World]
- [2 3 5 7 11 13]
复制代码 需要注意的是,上文中a声明时就给定了数组的容量,也就是说它的容量无法再修改了。数组的下标是从0开始的(学过编程的都知道!),查询时下表写在数组名右侧:a[x],但是声明的时候容量写在类型左侧:[x]type,不要弄错了!
数组具有固定大小。另一方面,切片是一种动态大小的、灵活的数组元素视图。在实践中,切片比数组更为常见。
切片是基于数组的,本质上是对数组的一种“切割”(但这不影响原来的数组)。
类型 []T 是元素类型为 T 的切片。就是在数组容量那里留空!当然也可以使用`:=`快速声明赋值。
切片的形成通过指定两个索引,一个低界限和一个高界限,用冒号分隔:
a[low : high],这选择了一个半开范围,包含第一个元素,但不包含最后一个元素。当然同很多语言一样是有默认值的,省略low就是从头开始取(low=0),省略high则取到末尾(high=len(a))。那么对于数组var a [10]int,这些切片表达式是等价的:
- a[0:10]
- a[:10]
- a[0:]
- a[:]
复制代码
以下表达式创建了一个包含 primes 的第 1 至第 3 个元素的切片:
- primes := [6]int{2, 3, 5, 7, 11, 13}
- var s []int = primes[1:4] //or s:=primes[1:4]
复制代码 这样s就是[3 5 7](即`[3]int{3, 5, 7}`)了。
我们说切片不影响原来的数组,是因为它本质上是数组的引用。
你可以理解为一种指针:

这张图片很好体现了b对a的指向关系(伪代码示意)。如果a的内容被修改了,那么b也会受到影响。如果你在修改b,那么因为b是指向a的,所以原则上你是在修改a,比如说你使用b[1]=114514,如果这时又有c指向a的[1:3],那么c会由原来的[2 3]变为[114514 3]。
我们来看示例(JetBrainsAcademy):
- package main
- import "fmt"
- func main() {
- names := [4]string{
- "John",
- "Paul",
- "George",
- "Ringo",
- }
- fmt.Println(names)//[1]
- a := names[0:2]
- b := names[1:3]
- fmt.Println(a, b)//[2]
- b[0] = "XXX"
- fmt.Println(a, b)//[3]
- fmt.Println(names)//[4]
- }
复制代码 输出:
- [John Paul George Ringo]
- [John Paul] [Paul George]
- [John XXX] [XXX George]
- [John XXX George Ringo]
复制代码 接下来是难点,我们必须复习一下:
- 数组字面量:[3]bool{true, true, false}中,方括号里写了 3,表示这是一个长度为 3 的数组。
- 数组在 Go 中是值类型,赋值或传参时会复制整个数组。
接下来看切片字面量:
我们说[]type创建的是对type类型数组的切片,尽管它的“指针”还没有指向。
但是我们会看到:[]bool{true, true, false},这是切的什么呢?
切片本身不存储数据,它只是一个“视图”,指向底层的一个数组。
从写法上看,切片字面量和数组字面量几乎一样,唯一的区别就是方括号里有没有写长度。
但从执行过程上看,当你写 []bool{true, true, false} 时,Go 编译器会自动帮你创建一个长度为 3 的匿名数组,里面放着这三个布尔值,然后让这个切片指向它。这个过程等价于:
- // 先创建一个数组
- arr := [3]bool{true, true, false}
- // 再基于该数组生成一个切片(引用整个数组)
- slice := arr[:]
复制代码 所以,切片字面量就是一种“省略了长度”的数组字面量,但底层依然存在一个数组,只是这个数组你看不见名字,由切片间接管理。
为什么有这种设计?因为在实际开发中,我们更常用切片(动态、灵活),而很少直接操作数组。切片字面量让我们可以像写数组一样方便地初始化数据,但又不必操心长度——长度由编译器根据元素个数自动推断。
同样的,你也可以创建一个结构体数组的切片:
- s := []struct {
- i int
- b bool
- }{
- {2, true},
- {3, false},
- {5, true},
- {7, true},
- {11, false},
- {13, true},
- }
复制代码 (是不是有点像js)
s就是[{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
切片既有 长度 也有 容量。
切片的长度是指它所包含的元素数量。
切片的容量是指其底层数组中的元素数量,从切片的第一个元素开始计数,更像是一种能力(capability)。
可以使用表达式 len(s) 和 cap(s) 获取切片 s 的长度和容量。
如果容量足够,可以通过重新切片来扩展切片的长度。
- package main
- import "fmt"
- func main() {
- s := []int{2, 3, 5, 7, 11, 13}
- printSlice(s)
- // Slice the slice to give it zero length.
- s = s[:0]
- printSlice(s)
- // Extend its length.
- s = s[:4]
- printSlice(s)
- // Drop its first two values.
- s = s[2:]
- printSlice(s)
- }
- func printSlice(s []int) {
- fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
- }
复制代码
输出:
- len=6 cap=6 [2 3 5 7 11 13]
- len=0 cap=6 []
- len=4 cap=6 [2 3 5 7]
- len=2 cap=4 [5 7]
复制代码 注意之前所说的,数组在 Go 中是值类型,赋值或传参时会复制整个数组,所以不要疑惑为什么s已经被切过了还能“复原”。
对于一个刚刚声明未赋值的切片:var s []int
它是零值切片,值是nil。
一个nil切片的长度和容量都是0,并且没有底层数组。
你可以写:s==nil,这是真的。
你可以使用内置的 make 函数可以创建切片;这就是你创建动态大小数组的方法。
make 函数会分配一个清零的数组,并返回一个指向该数组的切片:
- a := make([]int, 5) // len(a)=5
复制代码
要指定容量,请传递一个第三参数给 make:
- b := make([]int, 0, 5) // len(b)=0, cap(b)=5
- b = b[:cap(b)] // len(b)=5, cap(b)=5
- b = b[1:] // len(b)=4, cap(b)=4
复制代码 例子(JetbrainsAcademy):
- package main
- import "fmt"
- func main() {
- a := make([]int, 5)
- printSlice("a", a)
- b := make([]int, 0, 5)
- printSlice("b", b)
- c := b[:2]
- printSlice("c", c)
- d := c[2:5]
- printSlice("d", d)
- }
- func printSlice(s string, x []int) {
- fmt.Printf("%s len=%d cap=%d %v\n",
- s, len(x), cap(x), x)
- }
复制代码 输出:
- a len=5 cap=5 [0 0 0 0 0]
- b len=0 cap=5 []
- c len=2 cap=5 [0 0]
- d len=3 cap=3 [0 0 0]
复制代码 切片可以包含任何类型,包括其他切片。这有点像多重指向,很多条道路收束在同一个点(内存)上(这个比喻不完全正确!)。
例子(JetbrainsAcademy):
- package main
- import (
- "fmt"
- "strings"
- )
- func main() {
- // Create a tic-tac-toe board.
- board := [][]string{
- []string{"_", "_", "_"},
- []string{"_", "_", "_"},
- []string{"_", "_", "_"},
- }
- // The players take turns.
- board[0][0] = "X"
- board[2][2] = "O"
- board[1][2] = "X"
- board[1][0] = "O"
- board[0][2] = "X"
- for i := 0; i < len(board); i++ {
- fmt.Printf("%s\n", strings.Join(board[i], " "))
- }
- }
复制代码 输出:
由于篇幅过长,向切片中添加元素下一节继续讲述。 |
|