0%

golang 学习笔记

golang 学习笔记

语法部分

数组 [n]T

语法: 变量名 := [数组长度]数据类型{数组内数据}
例如: country := [3]string{‘中国’,’日本’,’韩国’}
也可以只生命,不赋值,或者不填满数组
var country [5]string
需要注意的是这个时候不能使用 :=
语法允许 var country [5]string
或者 country := [5]string{}
golang 的数组是固定长度的,如果想使用不固定长度的,可以使用切片(slice)

切片 []T

相比数组,切片不需要声明长度,语法是通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
slice[low : high] // 默认 low 是 0,high是数组上界
声明切片的方式有 3 种:

  1. 从数组上进行切片

    1
    2
    array := [5]int{1,2,3,4,5}
    slice := array[1:4] // 从数组 array 上切下角标为 1 到 4(不包括)的元素
  2. 从切片上进行切片

    1
    2
    3
    array := [5]int{1,2,3,4,5}
    slice1 := array[1:4] // [2,3,4]
    slice2 := slice[1:2] // [3]
  3. 单独声明一个切片

    1
    slice := []int{1,2,3} // [1,2,3] 可以理解为不设置长度的数组就是切片

要注意的是它是一个半开区间,包括第一个元素,但排除最后一个元素
比如: a := [5]int{1,2,3,4,5}
b := a[1:4] // 2,3,4 从 1 下标(包含)开始到 4 下标(不包含)
c := b[0:2] // 2,3

切片并不存储任何数据,它只是引用了底层数组中的一段

切片的长度与容量

切片的长度就是它所包含的元素个数
切片的容量是从它的第一个元素开始数,到其底层数组元素末尾的个数
切片 s 的长度和容量可通过表达式 len(s) 和 cap(s) 来获取

1
2
3
4
5
6
7
8
9
10
11
12
// 这里的 len 和 js 中的 array.length 一样
// cap 需要对比起底层数组来看
s := []int{2, 3, 5, 7, 11, 13} // len=6 cap=6 s=[2 3 5 7 11 13]

// 截取切片使其长度为 0
s = s[:0] // len=0 cap=6 s=[] 开始的元素和底层数组一致,所以 cap 是 6

// 拓展其长度
s = s[:4] // len=4 cap=6 s=[2 3 5 7] 开始的元素是 2,所以 cap 还是 6

// 舍弃前两个值
s = s[2:] // len=2 cap=4 s=[5 7] 开始的元素是 5,所以 cap 是 4

make 函数

make函数有三个参数,最后一个是可选参数
make([]type, len, cap)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func main() {
a := make([]int, 5)
printSlice("a", a) // a len=5 cap=5 [0 0 0 0 0]

b := make([]int, 0, 5)
printSlice("b", b) // b len=0 cap=5 []

c := b[:2]
printSlice("c", c) // c len=2 cap=5 [0 0]

d := c[2:5]
printSlice("d", d) // d len=3 cap=3 [0 0 0]
}

func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}

append 函数

append 函数是用户在切片尾部追加元素,功能上类似 push
语法是 append(s []T, …T)
append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾
需要注意的是 append 函数不会改变 s,只会返回追加之后的值,所以需要赋值给 s
s = append(s[]T, ...T)

1
2
3
a := [3]int{1,2,3} // 123
b := a[:]; // 123
b = append(b, 4, 5) // 12345

如果切片是从数组引用来的,那么 append 方法会另外分配一个数组来存储新追加到切片尾部的数据,并不会改变原底层数组

range 语法

for 循环的range 是用来遍历数组、切片(slice)或者映射(map)的
for (let item in array){} 等同于
for _,item := range array {}
例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
// i 是数组下标 v是值
for i, v := range pow {
fmt.Printf("2的%d次方 = %d\n", i, v)
}
}

// 输出结果
20次方 = 1
21次方 = 2
22次方 = 4
23次方 = 8
24次方 = 16
25次方 = 32
26次方 = 64
27次方 = 128

但是我们在遍历切片时,经常是不需要用到 index 的,比如JS 中的 for in 遍历
这个时候我们可以用_来表示忽略该参数

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

func main() {
pow := make([]int, 10) // [0,0,0,0,0,0,0,0,0,0]
for i := range pow { // 只需要 index 时可以忽略第二个参数与逗号
pow[i] = 1 << uint(i) // == 2**i8
}
for _, value := range pow { // 只需要 value 时 index 使用_,代替
fmt.Printf("%d\n", value)
}
}

结构体 struct

结构体有点类似 js 中的 class,里面放的都是 class 的成员,并不是 map 那样的键值对.
使用 type 和 struct 声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Rectangle struct {
width int
height int
}

func (r Rectangle) setWidth() {
r.width = 100
}

func (r *Rectangle) setHeight() {
r.height = 100
}

func main() {
rec := Rectangle{width: 1, height: 1}
rec.setWidth()
fmt.Println(rec.width) // 仍然是1

rec.setHeight()
fmt.Println(rec.height) // 变为100
}

上面这段代码是方法的一种使用场景,奇怪的是把Rectangle作为函数接受对象时,加了个*,也就是使用结构体的指针,为什么不使用指针的时候 rec 的成员没有被改变呢?

不带指针的情况下,方法会拷贝一份新的结构体,这个时候操作的是新结构体的数据,对原结构体没有影响
官方文档也提到:
1.如果你需要在方法/函数内改变Rectangle的值,则必须声明一个指针方法。
2.如果你的Rectangle结构体数据较大,比如有100个字段,如果使用不带指针的方法,则每一次都需要对其进行拷贝,内存消耗较大,此时建议使用带指针的方法。
3.如果有部分方法是带指针的,则为了代码统一,建议其他方法也写成带指针的。