1 基本类型

类型 长度(字节)
bool 1
byte 1
rune 4
int, uint 4 or 8
int8, uint8 1
int16, uint16 2
int32, uint32 4
int64, uint64 8
float32 4
float64 8
complex64 8
complex128 16
uintptr 4 or 8
类型 说明
array 值类型
struct 值类型
类型 说明
string UTF-8 字符串
类型 说明
slice 切片,引用类型
map 映射,引用类型
channel 通道,引用类型
interface 接口,引用类型
function 函数,引用类型

值类型:在赋值时复制数据,修改副本不会影响原始数据。

引用类型:在赋值时复制地址,修改数据会影响原始数据。

这里主要介绍数组,切片,映射,结构体。

2 数组 Array

1
2
3
var arr [size]T
arr := [size]T{e...}
arr := [...]T{e...}

3 切片 Slice

切片(声明 & 初始化):

1
2
var s []T
s := []T{e...}

使用 make 函数创建切片:

1
2
var s []T = make([]T, len, cap)
s := make([]T, len, cap)

从数组创建切片:

1
2
var s []T = arr[start:end]
s := arr[start:end]

子切片:

操作 含义
s[n] 切片 s 中索引位置为 n 的项
s[:] 切片 s 的索引位置 0 到 len(s)-1 处所获得的切片
s[low] 切片 s 的索引位置 low 到 len(s)-1 处所获得的切片
s[:high] 切片 s 的索引位置 0 到 high 处所获得的切片,len=high
s[low:high] 切片 s 的索引位置 low 到 high 处所获得的切片,len=high-low
s[low:high:max] 切片 s 的索引位置 low 到 high 处所获得的切片,len=high-low,cap=max-low
len(s) 切片 s 的长度,总是 <=cap(s)
cap(s) 切片 s 的容量,总是 >=len(s)

3.1 数组和切片内存布局

读写操作实际目标是底层数组,只需注意索引号的差别。

3.2 切片追加元素

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
s := make([]int, 0, 5)
for i := 0; i < 5; i++ {
s = append(s, i)
fmt.Println(s)
}
}

[0]
[0 1]
[0 1 2]
[0 1 2 3]
[0 1 2 3 4]

超出 slice.cap 限制,重新分配底层数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
arr := [...]int{1, 2, 3, 4, 5}
s := arr[0:2:3]
s = append(s, 6)
fmt.Printf("arr = %v, &arr[0]: %p\n", arr, &arr[0])
fmt.Printf("s = %v, &(s->arr)[0]: %p\n", s, &s[0])
s = append(s, 7)
fmt.Printf("arr = %v, &arr[0]: %p\n", arr, &arr[0])
fmt.Printf("s = %v, &(s->arr)[0]: %p\n", s, &s[0])
}

arr = [1 2 6 4 5], &arr[0]: 0xc00000e300
s = [1 2 6], &(s->arr)[0]: 0xc00000e300
arr = [1 2 6 4 5], &arr[0]: 0xc00000e300
s = [1 2 6 7], &(s->arr)[0]: 0xc00000e360

arr 和第一次 append 后的 s 共享同一个底层数组,但在第二次 append 后,切片的容量不足时,会分配一个新的更大的底层数组,并将原数组的数据复制过去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func main() {
s := make([]int, 0, 1)
c := cap(s)
for i := 0; i < 50; i++ {
s = append(s, i)
if n := cap(s); n > c {
fmt.Printf("cap: %d -> %d\n", c, n)
c = n
}
}
}

cap: 1 -> 2
cap: 2 -> 4
cap: 4 -> 8
cap: 8 -> 16
cap: 16 -> 32
cap: 32 -> 64

切片的容量增长是自动的,并且通常按照指数增长的方式进行扩容,容量一般是原来容量的 2 倍,直到切片可以容纳新的元素。

3.3 切片拷贝

1
func copy(dst, src []T) int
  • dst:目标切片

  • src:源切片

返回值:实际复制的元素个数,是目标切片和源切片中较小的一方的长度。

  1. len(dst) >= len(src):复制源切片中的元素,目标切片中剩余的元素保持不变。
  2. len(dst) < len(src):目标切片填充到最大可能的大小,剩余的部分则不再复制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func main() {
src := []int{1, 2, 3, 4, 5}
dst := make([]int, len(src))
n := copy(dst, src)
fmt.Printf("%d\n", n)
fmt.Printf("src: %v\n", src)
fmt.Printf("dst: %v\n", dst)
}

5
src: [1 2 3 4 5]
dst: [1 2 3 4 5]

func main() {
src := []int{1, 2, 3, 4, 5}
dst := make([]int, 6)
n := copy(dst, src)
fmt.Printf("%d\n", n)
fmt.Printf("src: %v\n", src)
fmt.Printf("dst: %v\n", dst)
}

5
src: [1 2 3 4 5]
dst: [1 2 3 4 5 0]
1
2
3
4
5
6
7
8
9
10
11
12
func main() {
src := []int{1, 2, 3, 4, 5}
dst := make([]int, 3)
n := copy(dst, src)
fmt.Printf("%d\n", n)
fmt.Printf("src: %v\n", src)
fmt.Printf("dst: %v\n", dst)
}

3
src: [1 2 3 4 5]
dst: [1 2 3]

3.4 切片遍历

传统的 for 循环:通过索引访问切片中的每个元素。

1
2
3
for i := 0; i < len(s); i++ {
// s[i]
}

forrange:通过索引和值的方式,简洁且易于理解。如果不关心索引或值,可以省略。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
for i, v := range s {
// index-i, value->v
}

for _, v := range s {
// get only the value, ignoring the index
}

for i, _ := range s {
// method 1: get only the index, ignoring the value
}

for i := range s {
// method 2: get only the index, ignoring the value
}

4 映射 Map

映射(声明 & 初始化):

1
2
var s map[K]V
s := map[K]V{e...}

使用 make 函数创建映射:

1
2
var s []T = make([]T, hint)
s := make([]T, hint)

4.1 添加元素

使用赋值操作来向 map 中添加键值对。

1
2
3
4
5
6
7
8
9
10
11
func main() {
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
m["d"] = 4
fmt.Println(m)
}

map[a:1 b:2 c:3 d:4]

4.2 获取元素

使用键来访问 map 中的值。如果键存在,返回值和 true;如果键不存在,返回零值和 false

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func main() {
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
value, ok := m["c"]
if ok {
fmt.Println("Found:", value)
} else {
fmt.Println("Not found")
}
}

Found: 3

4.3 删除元素

删除元素:使用 delete 函数删除 map 中的指定键值对。

1
2
3
4
5
6
7
8
9
10
11
func main() {
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
delete(m, "c")
fmt.Println(m)
}

map[a:1 b:2]

4.4 遍历 map

使用 forrange 遍历 map 中的所有键值对。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
m := map[string]int{
"a": 1,
"b": 2,
"c": 3,
}
for k, v := range m {
fmt.Printf("key:%v value:%v \n", k, v)
}
}

key:a value:1
key:b value:2
key:c value:3

5 结构体 Struct

类型定义 vs 类型别名:

1
2
3
4
5
6
7
8
9
10
11
12
13
type NewInt int

type MyInt = int

func main() {
var a NewInt
var b MyInt
fmt.Printf("type of a:%T\n", a)
fmt.Printf("type of b:%T\n", b)
}

type of a:main.NewInt
type of b:int

常见写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type person struct {
name string
age int
}

func main() {
var p person
p.name = "Alice"
p.age = 25
// or var p = person{name: "Alice", age: 25}
fmt.Printf("%#v\n", p)
}

main.person{name:"Alice", age:25}

匿名结构体

1
2
3
4
5
6
7
8
9
10
11
func main() {
var p struct {
name string
age int
}
p.name = "Alice"
p.age = 25
fmt.Printf("%#v\n", p)
}

struct { name string; age int }{name:"Alice", age:25}

指针类型结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type person struct {
name string
age int
}

func main() {
var pp = new(person)
pp.name = "Alice"
pp.age = 25
// or var pp = person{name: "Alice", age: 25}
fmt.Printf("%#v\n", pp)
}

&main.person{name:"Alice", age:25}

对比使用 & 对结构体进行取地址操作(等价于进行了 new 实例化):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type person struct {
name string
age int
}

func main() {
var pp = &person{}
pp.name = "Alice"
pp.age = 25
// or var pp = person{name: "Alice", age: 25}
fmt.Printf("%#v\n", pp)
}

&main.person{name:"Alice", age:25}

5.1 构造函数

结构体本身确实没有像某些其他语言那样的显式构造函数。可以通过编写自定义的构造函数来模拟构造过程,并且通常返回结构体的指针类型,特别是当结构体较大或较复杂时,避免不必要的值拷贝性能开销。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type person struct {
name string
age int
}

func newPerson(name string, age int) *person {
return &person{
name: name,
age: age,
}
}

func main() {
var pp = newPerson("Alice", 25)
fmt.Printf("%#v\n", pp)
}

&main.person{name:"Alice", age:25}

5.2 方法

5.2.1 值类型接收者

1
func (t T) F() ...{...}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type person struct {
name string
age int
}

func newPerson(name string, age int) *person {
return &person{
name: name,
age: age,
}
}

func (p person) growUp() {
p.age = p.age + 1
}

func main() {
var pp = newPerson("Alice", 25)
fmt.Printf("pp.age:%d\n", pp.age)
pp.growUp() // => (&pp).growUp()
fmt.Printf("pp.age:%d\n", pp.age)
}

pp.age:25
pp.age:25

5.2.2 指针类型接受者

1
func (t T) F() ...{...}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
type person struct {
name string
age int
}

func newPerson(name string, age int) *person {
return &person{
name: name,
age: age,
}
}

func (p *person) growUp() {
p.age = p.age + 1
}

func main() {
var pp = newPerson("Alice", 25)
fmt.Printf("pp.age:%d\n", pp.age)
pp.growUp()
fmt.Printf("pp.age:%d\n", pp.age)
}

pp.age:25
pp.age:26

5.3 嵌套结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
type person struct {
name string
age int
addr address
}

func newPerson(name string, age int, address address) *person {
return &person{
name: name,
age: age,
addr: address,
}
}

type address struct {
province string
city string
}

func newAddress(province string, city string) *address {
return &address{
province: province,
city: city,
}
}

func main() {
var pa = newAddress("湖南省", "长沙市")
var pp = newPerson("Alice", 25, *pa)
fmt.Printf("%+v \n", pp)
fmt.Printf("name: %v, age: %v, province: %v, city: %v", pp.name, pp.age, pp.addr.province, pp.addr.city)
}

&{name:Alice age:25 addr:{province:湖南省 city:长沙市}}
name: Alice, age: 25, province: 湖南省, city: 长沙市

嵌套匿名结构体:

1
2
3
4
5
6
7
8
9
10
type person struct {
name string
age int
address
}

type address struct {
province string
city string
}
  • 通过匿名结构体.字段名访问:p.address.provice
  • 通过匿名结构体字段名访问:p.provice

发生冲突,需要指定匿名结构体。

继承是指子类通过继承父类的方法和字段,可以直接访问父类的所有功能。Go 通过子类通过组合父类(即嵌入父类),可以访问父类的方法和字段来模拟继承。

5.4 可见性

结构体的字段名和方法名首字母大写时,可以被外部包访问,首字母小写时,只能在包内访问。

5.5 序列化和反序列化

  • 序列化(json.Marshal
  • 反序列化(json.Unmarshal
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
Email string `json:"email,omitempty"`
}

func main() {
pp1 := Person{
Name: "John Doe",
Age: 30,
Email: "john.doe@example.com",
}
jsonData, _ := json.Marshal(pp1)
fmt.Println(string(jsonData))

jsonString := `{"name":"John Doe","age":30,"email":"john.doe@example.com"}`
var pp2 Person
_ = json.Unmarshal([]byte(jsonString), &pp2)
fmt.Printf("%+v\n", pp2)
}

{"name":"John Doe","age":30,"email":"john.doe@example.com"}
{Name:John Doe Age:30 Email:john.doe@example.com}

6 指针

1
2
3
4
5
6
7
func main() {
var a int = 100
var pa *int = &a
fmt.Printf("a: %d, pa: %p", a, pa)
}

a: 100, pa: 0xc00008c098

*:解引用运算符,用来通过指针访问变量的值。

&:取地址运算符,用来获取变量的内存地址(即指针)。

1
2
3
4
5
6
7
8
func main() {
var pa *int
*pa = 100
fmt.Println(*pa)
}

panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xc0000005 code=0x1 addr=0x0 pc=0x7a9962]

6.1 new

new:返回的是类型的指针,无论该类型是引用类型还是值类型。

1
func new(Type) *Type
1
2
3
4
5
6
7
8
func main() {
var pa *int
pa = new(int)
*pa = 100
fmt.Println(*pa)
}

100

6.2 make

make:返回的是引用类型(切片、映射、通道)本身,而不是指针。

1
func make(t Type, size ...IntegerType) Type
  • 创建切片 (slice)
1
make([]T, len, cap)

[]T:指定切片元素的类型,这里 T 是元素类型。

len:切片的初始长度,即切片中的元素数量。

cap:切片的初始容量,底层数组的大小。如果省略 cap,那么容量默认为与 len 相等。

  • 创建映射 (map)
1
make(map[K]V, hint)

map[K]VK 是键的类型,V 是值的类型。

hint:是一个可选的容量提示(建议的初始桶数),用于优化性能。如果不指定,Go 会根据需求动态分配。

  • 创建通道 (channel)
1
make(chan T, capacity)

chan T:指定通道中元素的类型 T

capacity:通道的容量,表示通道中可以缓冲的元素数。如果是无缓冲通道,可以省略 capacity,默认值是 0。