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
值类型
类型
说明
slice
切片,引用类型
map
映射,引用类型
channel
通道,引用类型
interface
接口,引用类型
function
函数,引用类型
值类型 :在赋值时复制数据,修改副本不会影响原始数据。
引用类型 :在赋值时复制地址,修改数据会影响原始数据。
这里主要介绍数组,切片,映射,结构体。
2 数组 Array
1 2 3 var arr [size]Tarr := [size]T{e...} arr := [...]T{e...}
3 切片 Slice
切片(声明 & 初始化):
1 2 var s []Ts := []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
返回值:实际复制的元素个数,是目标切片和源切片中较小的一方的长度。
len(dst) >= len(src)
:复制源切片中的元素,目标切片中剩余的元素保持不变。
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++ { }
for
和 range
:通过索引和值的方式,简洁且易于理解。如果不关心索引或值,可以省略。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 for i, v := range s { } for _, v := range s { } for i, _ := range s { } for i := range s { }
4 映射 Map
映射(声明 & 初始化):
1 2 var s map [K]Vs := 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
使用 for
和 range
遍历 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.NewInttype 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 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 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 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 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:25
5.2.2 指针类型接受者
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 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
[]T
:指定切片元素的类型,这里 T
是元素类型。
len
:切片的初始长度,即切片中的元素数量。
cap
:切片的初始容量,底层数组的大小。如果省略 cap
,那么容量默认为与 len
相等。
map[K]V
:K
是键的类型,V
是值的类型。
hint
:是一个可选的容量提示(建议的初始桶数),用于优化性能。如果不指定,Go 会根据需求动态分配。
chan T
:指定通道中元素的类型 T
。
capacity
:通道的容量,表示通道中可以缓冲的元素数。如果是无缓冲通道,可以省略 capacity
,默认值是 0。