基础数据类型
- 整型,用于表示整数
- 浮点型,用于表示小数
- 复数,用来表示复数
- 布尔型,用于表示真/假
- 字符串,用于表示文本信息
复合数据类型
- 数组,用于表示多个数据(数据集合)
- 结构体,用于自定义一些数据集合和方法
引用数据类型
- 指针,用于表示内存地址的类型
- 切片,用于表示多个数据(数据集合)
- 字典,用于表示键值对集合
- 函数,用于将一个语句序列打包为一个单元,然后可以从程序中其它地方多次调用
- 通道,channel,是goroutine中的通信机制
接口数据类型
- 接口,用于约束和泛指数据类型
一、切片的定义
Slice(切片)代表变长的序列,可以理解为动态数组,序列中每个元素都有相同的类型。一个slice类型一般写作[]T,其中T代表slice中元素的类型;slice的语法和数组很像,只是没有固定长度而已。
数组和slice之间有着紧密的联系。一个slice是一个轻量级的数据结构,提供了访问数组子序列(或者全部)元素的功能,而且slice的底层确实引用一个数组对象。切片(slice)是Go中重要的数据类型,每个切片对象内部都维护着:数组指针、切片长度、切片容量 三个数据,指针指向第一个slice元素对应的底层数组元素的地址,要注意的是slice的第一个元素并不一定就是数组的第一个元素。长度对应slice中元素的数目;长度不能超过容量,容量一般是从slice的开始位置到底层数据的结尾位置。内置的len和cap函数分别返回slice的长度和容量。
type slice struct {
array unsafe.Pointer
len int
cap int
}

在向切片中追加的数据个数大于容量时,内部会自动扩容且每次扩容都当前容量的2倍(当容量超过1024时每次扩容则只增加 1/4容量)。
需要说明,slice 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。
切片的特点如下:
- 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
- 切片的长度可以改变,因此,切片是一个可变的数组。
- 切片遍历方式和数组一样,可以用
len()求长度。表示可用元素数量,读写操作不能超过该限制。 - cap可以求出slice最大扩张容量,不能超出数组限制。0 <=
len(slice)<=len(array),其中array是slice引用的数组。 - 切片的定义:
var变量名 []类型,比如var str []string,var arr []int。 - 如果 slice == nil,那么
len、cap结果都等于 0。
二、创建切片
// 创建切片
var nums []int
// 创建切片
var data = []int{11,22,33}
data := []int{11,22,33}
// 创建切片
// make只用于 切片、字典、channel
var users = make([]int,1,3)
// 切片的指针类型
var v1 = new([]int)
// 指针类型(nil)
var v2 *[]int
示例
package main
import "fmt"
func main() {
//1.声明切片
var s1 []int
// 2.:=
s2 := []int{}
// 3.make()
var s3 = make([]int, 0)
fmt.Println(s1, s2, s3) // [] [] []
// 4.初始化赋值
var s4 = make([]int, 0, 0)
fmt.Println(s4) // []
s5 := []int{1, 2, 3}
fmt.Println(s5) // [1,2,3]
// 5.从数组切片
arr := [5]int{1, 2, 3, 4, 5}
var s6 []int
// 前包后不包
s6 = arr[1:4]
fmt.Println(s6) // [2,3,4]
var s7 = new([]int)
var s8 *[]int
fmt.Println(s7, s8) // &[] <nil>
}
三、切片初始化
1. 通过数组进行初始化
package main
import (
"fmt"
)
var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
var slice0 []int = arr[2:8]
var slice1 []int = arr[0:6] //可以简写为 var slice []int = arr[:end]
var slice2 []int = arr[5:10] //可以简写为 var slice[]int = arr[start:]
var slice3 []int = arr[0:len(arr)] //var slice []int = arr[:]
var slice4 = arr[:len(arr)-1] //去掉切片的最后一个元素
func main() {
fmt.Printf("全局变量:arr %v\n", arr)
fmt.Printf("全局变量:slice0 %v\n", slice0)
fmt.Printf("全局变量:slice1 %v\n", slice1)
fmt.Printf("全局变量:slice2 %v\n", slice2)
fmt.Printf("全局变量:slice3 %v\n", slice3)
fmt.Printf("全局变量:slice4 %v\n", slice4)
fmt.Printf("-----------------------------------\n")
arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}
slice5 := arr[2:8]
slice6 := arr[0:6] //可以简写为 slice := arr[:end]
slice7 := arr[5:10] //可以简写为 slice := arr[start:]
slice8 := arr[0:len(arr)] //slice := arr[:]
slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素
fmt.Printf("局部变量: arr2 %v\n", arr2)
fmt.Printf("局部变量: slice5 %v\n", slice5)
fmt.Printf("局部变量: slice6 %v\n", slice6)
fmt.Printf("局部变量: slice7 %v\n", slice7)
fmt.Printf("局部变量: slice8 %v\n", slice8)
fmt.Printf("局部变量: slice9 %v\n", slice9)
}
输出结果:
全局变量:arr [0 1 2 3 4 5 6 7 8 9]
全局变量:slice0 [2 3 4 5 6 7]
全局变量:slice1 [0 1 2 3 4 5]
全局变量:slice2 [5 6 7 8 9]
全局变量:slice3 [0 1 2 3 4 5 6 7 8 9]
全局变量:slice4 [0 1 2 3 4 5 6 7 8]
-----------------------------------
局部变量: arr2 [9 8 7 6 5 4 3 2 1 0]
局部变量: slice5 [2 3 4 5 6 7]
局部变量: slice6 [0 1 2 3 4 5]
局部变量: slice7 [5 6 7 8 9]
局部变量: slice8 [0 1 2 3 4 5 6 7 8 9]
局部变量: slice9 [0 1 2 3 4 5 6 7 8]
2. 通过make来创建切片
var slice []type = make([]type, len)
slice := make([]type, len)
slice := make([]type, len, cap)

代码:
package main
import (
"fmt"
)
var slice0 []int = make([]int, 10)
var slice1 = make([]int, 10)
var slice2 = make([]int, 10, 10)
func main() {
fmt.Printf("make全局slice0 :%v\n", slice0)
fmt.Printf("make全局slice1 :%v\n", slice1)
fmt.Printf("make全局slice2 :%v\n", slice2)
fmt.Println("--------------------------------------")
slice3 := make([]int, 10)
slice4 := make([]int, 10)
slice5 := make([]int, 10, 10)
fmt.Printf("make局部slice3 :%v\n", slice3)
fmt.Printf("make局部slice4 :%v\n", slice4)
fmt.Printf("make局部slice5 :%v\n", slice5)
}
输出结果:
make全局slice0 :[0 0 0 0 0 0 0 0 0 0]
make全局slice1 :[0 0 0 0 0 0 0 0 0 0]
make全局slice2 :[0 0 0 0 0 0 0 0 0 0]
--------------------------------------
make局部slice3 :[0 0 0 0 0 0 0 0 0 0]
make局部slice4 :[0 0 0 0 0 0 0 0 0 0]
make局部slice5 :[0 0 0 0 0 0 0 0 0 0]
切片的内存布局

读写操作实际目标是底层数组,只需注意索引号的差别。
package main
import (
"fmt"
)
func main() {
data := [...]int{0, 1, 2, 3, 4, 5}
s := data[2:4]
s[0] += 100
s[1] += 200
fmt.Println(s)
fmt.Println(data)
}
输出:
[102 203]
[0 1 102 203 4 5]
3. 直接创建 slice 对象,自动分配底层数组。
package main
import "fmt"
func main() {
s1 := []int{0, 1, 2, 3, 8: 100} // 通过初始化表达式构造,可使用索引号。
fmt.Println(s1, len(s1), cap(s1))
s2 := make([]int, 6, 8) // 使用 make 创建,指定 len 和 cap 值。
fmt.Println(s2, len(s2), cap(s2))
s3 := make([]int, 6) // 省略 cap,相当于 cap = len。
fmt.Println(s3, len(s3), cap(s3))
}
输出结果:
[0 1 2 3 0 0 0 0 100] 9 9
[0 0 0 0 0 0] 6 8
[0 0 0 0 0 0] 6 6
使用 make 动态创建slice,避免了数组必须用常量做长度的麻烦。还可用指针直接访问底层数组,退化成普通数组操作。
package main
import "fmt"
func main() {
s := []int{0, 1, 2, 3}
p := &s[2] // *int, 获取底层数组元素指针。
*p += 100
fmt.Println(s)
}
输出结果:
[0 1 102 3]
至于 [][]T,是指元素类型为 []T 。
package main
import (
"fmt"
)
func main() {
data := [][]int{
[]int{1, 2, 3},
[]int{100, 200},
[]int{11, 22, 33, 44},
}
fmt.Println(data)
}
输出结果:
[[1 2 3] [100 200] [11 22 33 44]]
四、修改底层数组
v1 := make([]int,1,3)
fmt.Println(len(v1),cap(v1))
// 其他
data := make([]int,3)

v1 := make([]int,1,3)
v2 := append(v1,66)
fmt.Println(v1) // [0 ]
fmt.Println(v2) // [0,66]
v1[0] = 999
fmt.Println(v1) // [999 ]
fmt.Println(v2) // [999,66]
// 需求:有一个切片,请往一个切片中追加一个数据。
v3 := make([]int,1,3)
v3 = append(v3,999)

v1 := []int{11,22,33}
v2 := append(v1,44)
v1[0] = 999
fmt.Println(v1) // [999 22 33]
fmt.Println(v2) // [11 22 33 44]

五、常见操作
1. 长度和容量
v1 := []int{11,22,33}
fmt.Println(len(v1), cap(v1))
2. 索引
v1 := []string{"kobe","乔丹","詹姆斯"}
v1[0]
v1[1]
v1[2]
v2 := make([]int,2,5)
v2[0]
v2[1]
v2[2] // 报错
v2[0] = 999
3. 切片
v1 := []int{11,22,33,44,55,66}
v2 := v1[1:3]
v3 := v1[1:]
v4 := v1[:3]
// 注意:通过切片切出来的数据和原切片内部存储的数据地址相同
4. 追加
package main
import (
"fmt"
)
func main() {
v1 := []int{11,22,33}
v2 := append(v1,44)
v3 := append(v1,55,66,77,88)
v4 := append(v1, []int{100,200,300}...)
fmt.Println(v2)
fmt.Println(v3)
fmt.Println(v4)
}
>>> 输出
[11 22 33 44]
[11 22 33 55 66 77 88]
[11 22 33 100 200 300]
5. 删除
package main
import "fmt"
func main() {
v1 := []int{11, 22, 33, 44, 55, 66}
deleteIndex := 2
// 切片获取到 {11,22,44、55、66}
// 又获取到 {44, 55, 66},将44、55、66要追加到
result := append(v1[:deleteIndex], v1[deleteIndex+1:]...)
fmt.Println(result) // [11 22 44 55 66]
fmt.Println(v1) //[11 22 44 55 66 66]
}
注意:使用切片时不太会使用删除。【链表】
6. 插入
package main
import "fmt"
func main() {
v1 := []int{11, 22, 33, 44, 55, 66}
insertIndex := 3 // 在索引3的位置插入99
result := make([]int, 0, len(v1)+1)
result = append(result, v1[:insertIndex]...)
result = append(result,99)
result = append(result,v1[insertIndex:]...)
fmt.Println(result)
}
注意:效率低下。【链表】
7. 循环
package main
import "fmt"
func main() {
v1 := []int{11, 22, 33, 99, 55, 66}
for i := 0; i < len(v1); i++ {
fmt.Println(i, v1[i])
}
for index, value := range v1 {
fmt.Println(index, value)
}
}
8. 切片拷贝
package main
import (
"fmt"
)
func main() {
s1 := []int{1, 2, 3, 4, 5}
fmt.Printf("slice s1 : %v\n", s1)
s2 := make([]int, 10)
fmt.Printf("slice s2 : %v\n", s2)
copy(s2, s1)
fmt.Printf("copied slice s1 : %v\n", s1)
fmt.Printf("copied slice s2 : %v\n", s2)
s3 := []int{1, 2, 3}
fmt.Printf("slice s3 : %v\n", s3)
s3 = append(s3, s2...)
fmt.Printf("appended slice s3 : %v\n", s3)
s3 = append(s3, 4, 5, 6)
fmt.Printf("last slice s3 : %v\n", s3)
}
输出结果:
slice s1 : [1 2 3 4 5]
slice s2 : [0 0 0 0 0 0 0 0 0 0]
copied slice s1 : [1 2 3 4 5]
copied slice s2 : [1 2 3 4 5 0 0 0 0 0]
slice s3 : [1 2 3]
appended slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0]
last slice s3 : [1 2 3 1 2 3 4 5 0 0 0 0 0 4 5 6]
copy :函数 copy 在两个 slice 间复制数据,复制长度以 len 小的为准。两个 slice 可指向同一底层数组,允许元素区间重叠。
package main
import (
"fmt"
)
func main() {
data := [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
fmt.Println("array data : ", data)
s1 := data[8:]
s2 := data[:5]
fmt.Printf("slice s1 : %v\n", s1)
fmt.Printf("slice s2 : %v\n", s2)
copy(s2, s1)
fmt.Printf("copied slice s1 : %v\n", s1)
fmt.Printf("copied slice s2 : %v\n", s2)
fmt.Println("last array data : ", data)
}
输出结果:
array data : [0 1 2 3 4 5 6 7 8 9]
slice s1 : [8 9]
slice s2 : [0 1 2 3 4]
copied slice s1 : [8 9]
copied slice s2 : [8 9 2 3 4]
last array data : [8 9 2 3 4 5 6 7 8 9]
应及时将所需数据 copy 到较小的 slice,以便释放超大号底层数组内存。
9. 切片嵌套
package main
import "fmt"
func main() {
v1 := []int{11, 22, 33, 99, 55, 66}
v2 := [][]int{[]int{11, 22, 33, 44}, []int{44, 55}}
v3 := [][2]int{[2]int{1, 2}, [2]int{4, 5}}
fmt.Println(v1)
fmt.Println(v2)
fmt.Println(v3)
v1[0] = 111111
v2[0][2] = 222222
v3[1][0] = 99999
fmt.Println(v1)
fmt.Println(v2)
fmt.Println(v3)
}
六、数组和切片的内存布局

1. 字符串和切片(string and slice)
string底层就是一个byte的数组,因此,也可以进行切片操作。
package main
import (
"fmt"
)
func main() {
str := "hello world"
s1 := str[0:5]
fmt.Println(s1)
s2 := str[6:]
fmt.Println(s2)
}
输出结果:
hello
world
string本身是不可变的,因此要改变string中字符。需要如下操作: 英文字符串:
package main
import (
"fmt"
)
func main() {
str := "Hello world"
s := []byte(str) //中文字符需要用[]rune(str)
s[6] = 'G'
s = s[:8]
s = append(s, '!')
str = string(s)
fmt.Println(str)
}
输出结果:
Hello Go!
2. 含有中文字符串:
package main
import (
"fmt"
)
func main() {
str := "你好,世界!hello world!"
s := []rune(str)
s[3] = '够'
s[4] = '浪'
s[12] = 'g'
s = s[:14]
str = string(s)
fmt.Println(str)
}
输出结果:
你好,够浪!hello go
golang slice data[:6:8] 两个冒号的理解
常规slice , data[6:8],从第6位到第8位(返回6, 7),长度len为2, 最大可扩充长度cap为4(6-9)
另一种写法: data[:6:8] 每个数字前都有个冒号, slice内容为data从0到第6位,长度len为6,最大扩充项cap设置为8
a[x:y:z] 切片内容 [x:y] 切片长度: y-x 切片容量:z-x
package main
import (
"fmt"
)
func main() {
slice := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
d1 := slice[6:8]
fmt.Println(d1, len(d1), cap(d1))
d2 := slice[:6:8]
fmt.Println(d2, len(d2), cap(d2))
}
数组or切片转字符串:
strings.Replace(strings.Trim(fmt.Sprint(array_or_slice), "[]"), " ", ",", -1)
七、动态扩容
1. 超出原 slice.cap 限制,就会重新分配底层数组,即便原数组并未填满。
package main
import (
"fmt"
)
func main() {
data := [...]int{0, 1, 2, 3, 4, 10: 0}
s := data[:2:3]
s = append(s, 100, 200) // 一次 append 两个值,超出 s.cap 限制。
fmt.Println(s, data) // 重新分配底层数组,与原数组无关。
fmt.Println(&s[0], &data[0]) // 比对底层数组起始指针。
}
输出结果:
[0 1 100 200] [0 1 2 3 4 0 0 0 0 0 0]
0xc4200160f0 0xc420070060
从输出结果可以看出,append 后的 s 重新分配了底层数组,并复制数据。如果只追加一个值,则不会超过 s.cap 限制,也就不会重新分配。 通常以 2 倍容量重新分配底层数组。在大批量添加数据时,建议一次性分配足够大的空间,以减少内存分配和数据复制开销。或初始化足够长的 len 属性,改用索引号进行操作。及时释放不再使用的 slice 对象,避免持有过期数组,造成 GC 无法回收。
2. slice中cap重新分配规律
package main
import (
"fmt"
)
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
cap: 64 -> 128
cap: 128 -> 256
cap: 256 -> 512
cap: 512 -> 1024
cap: 1024 -> 1280
cap: 1280 -> 1696
cap: 1696 -> 2304
如果切片的容量小于 1024 个元素,于是扩容的时候就翻倍增加容量。上面那个例子也验证了这一情况,总容量从原来的4个翻倍到现在的8个。一旦元素个数超过 1024 个元素,那么增长因子就变成 1.25 ,即每次增加原来容量的四分之一。
注意:扩容扩大的容量都是针对原来的容量而言的,而不是针对原来数组的长度而言的。
八、变量赋值
整型
package main import "fmt" func main() { v1 := 1 v2 := v1 fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc0000b4008 fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc0000b4010 }布尔类型
package main import "fmt" func main() { v1 := false v2 := v1 fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc00012a002 fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc00012a003 }浮点型
package main import "fmt" func main() { v1 := 3.14 v2 := v1 fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc000016050 fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc000016058 }字符串
package main import "fmt" func main() { v1 := "张亚飞" v2 := v1 fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc000010200 fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc000010210 }注意:字符串内部元素不可被修改。
数组
package main import "fmt" func main() { v1 := [2]int{6, 9} v2 := v1 fmt.Println(v1, v2) fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc0000b4010 fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc0000b4020 v1[0] = 11111 fmt.Println(v1, v2) }切片
package main import "fmt" func main() { v1 := []int{6, 9} v2 := v1 fmt.Println(v1, v2) // [6 9] [6 9] fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc0000a6020 fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc0000a6040 v1[0] = 11111 fmt.Println(v1, v2) // [11111 9] [11111 9] }
如果扩容,那么内部存储数据的数组就会重新开辟区域。package main import "fmt" func main() { v1 := []int{6, 9} v2 := v1 fmt.Println(v1, v2) // [6 9] [6 9] fmt.Printf("v1的内存地址:%p \n", &v1) // 0xc0000a6020 fmt.Printf("v2的内存地址:%p \n", &v2) // 0xc0000a6040 v1 = append(v1, 999) fmt.Println(v1, v2) // [11111 9] [11111 9] }
总结,目前所学的所有的数据类型中,在修改切片的内部元素时,会造成所有的赋值的变量同时修改(不扩容)。
扩展:引用类型和值类型。
九、练习题
简述切片和数组的区别?
● 切片是指针类型,数组是值类型
● 数组的长度是固定的,而切片不是(切片是动态的数组)
● 切片比数组多一个属性:容量(cap)
● 切片的底层是数组
简述new和make的区别?
- 二者都是用来做内存分配的。
- make只用于slice、map以及channel的初始化,返回的还是这三个引用类型本身;
- 而new用于类型的内存分配,并且内存对应的值为类型零值,返回的是指向类型的指针。
看代码写结果
v1 := make([]int,2,5) fmt.Println(v1[0],len(v1),cap(v1))看代码写结果
v1 := make([]int,2,5) v2 := append(v1,123) fmt.Println(len(v1),cap(v1)) // 2,5 fmt.Println(len(v2),cap(v2)) // 3,5 fmt.Println(v1) // [0 0] fmt.Println(v2) // [0 0 123]
看代码写结果
v1 := make([]int,2,5) v2 := append(v1,123) v1[0] = 999 fmt.Println(v1) // [999 0] fmt.Println(v2) // [999, 0, 123]看代码写结果
v1 := make([]int,2,2) v2 := append(v1,123) v1[0] = 999 fmt.Println(v1) // [999 0] fmt.Println(v2) // [0 0 123]
看代码写结果
v1 := make([]int,2,2) v2 := v1[0:2] v1[0] = 111 fmt.Println(v1) // [111,0] fmt.Println(v2) // [111,0]看代码写结果
v1 := [][]int{ []int{11, 22, 33, 44}, []int{44, 55} } v1[0][2] = 999 fmt.Println(v1) // [ [11 22 99 44] [44,55] ]看代码写结果
v1 := [][]int{[]int{11, 22, 33, 44}, []int{44, 55}} v2 := v1[0] v2[2] = 69 fmt.Println(v1) // [[11 22 69 44] [44 55]]看代码写结果
v1 := [][]int{[]int{11, 22, 33, 44}, []int{44, 55}} v2 := append(v1[0],99) v2[2] = 69 fmt.Println(v1) // [[11 22 33 44] [44 55]]看代码写结果
v1 := [][]int{ make([]int, 2, 5), make([]int, 2, 3) } v2 := append(v1[0], 99) fmt.Println(v1) // [ [0 0], [0 0 ] ] fmt.Println(v2) // [0 0 99] v2[0] = 69 fmt.Println(v1) // [ [69 0], [0 0 ] ] fmt.Println(v2) // [69 0 99]