Go 中数据类型的分类

值类型 说明 默认值 复合类型 说明 默认值
bool false slice 引用类型 nil
numeric 0 map 引用类型 nil
(unsafe)pointer nill channel
struct function


interface
string “”
pointer nil
array 0
  1. fmt.Println(map[string]uint64{
  2. "ptr": uint64(unsafe.Sizeof(&struct{}{})),
  3. "map": uint64(unsafe.Sizeof(map[bool]bool{})),
  4. "slice": uint64(unsafe.Sizeof([]struct{}{})),
  5. "chan": uint64(unsafe.Sizeof(make(chan struct{}))),
  6. "func": uint64(unsafe.Sizeof(func() {})),
  7. "interface": uint64(unsafe.Sizeof(interface{}(0))),
  8. })
  9. // 输出
  10. map[chan:8 func:8 interface:16 map:8 ptr:8 slice:24]
  • chan/func/map/ptr 均为 8 个字节,即一个指向具体数据的指针
  • interface 为 16,两个指针,一个指向具体类型,一个指向具体数据。细节可参考 Russ Cox 的 Go Data Structures: Interfaces
  • slice 为 24,包括一个指向底层 array 的指针,两个整型,分布表示 cap、len

关键字

Go语言中类似if和switch的关键字有25个(均为小写)。
关键字不能用于自定义名字,只能在特定语法结构中使用。

  1. break default func interface select
  2. case defer go map struct
  3. chan else goto package switch
  4. const fallthrough if range type
  5. continue for import return var

此外,还有大约30多个预定义的名字,比如int和true等,主要对应内建的常量、类型和函数。

内建常量:

  1. true false iota nil

内建类型:

  1. int int8 int16 int32 int64
  2. uint uint8 uint16 uint32 uint64 uintptr
  3. float32 float64 complex128 complex64
  4. bool byte rune string error

内建函数:

  1. make len cap new append copy close delete
  2. complex real imag
  3. panic recover

类型分类

声明变量的方式

Go 语言在声明变量时会默认给变量赋个当前类型的空值

声明方式 说明
var 变量名 <变量类型> 声明单个变量
var 变量名1, 变量名2,… <变量类型> 声明多个同类型变量
变量名 := 值 声明变量,并赋值;Go 语言会根据所赋值推断变量的类型
变量名1, 变量名2,… := 值1, 值2,… 声明多个同类型变量并赋值,几个变量必须赋几个值

基本数据类型

  • 整型
  • 浮点型
  • 字符型
  • 布尔型
  • 复数型
  • 字符串型
  • 错误类型

复合数据类型

  • 指针
  • 数组
  • 切片
  • 字典
  • 通道
  • 结构体
  • 接口

自定义类型

Go语言支持我们自定义类型,比如刚刚上面的结构体类型,就是我们自定义的类型,这也是比较常用的自定义类型的方法。

另外一个自定义类型的方法是基于一个已有的类型,就是基于一个现有的类型创造新的类型,这种也是使用type关键字。

  1. type Duration int64

我们在使用time这个包的时候,对于类型time.Duration应该非常熟悉,它其实就是基于int64 这个基本类型创建的新类型,来表示时间的间隔。

但是这里我们注意,虽然Duration是基于int64创建,觉得他们其实一样,比如都可以使用数字赋值。

基本数据类型

数字类型比较多,默认值都是 0。定义int类型时,默认根据系统类型设置取值范围,32位系统与int32的值范围相同,64位系统与int64的值范围相同。见下表:

整型

类型 名称 存储空间 值范围 数据级别
uint8 无符号8位整形 8-bit 0 ~ 255
uint16 无符号16位整形 16-bit 0 ~65535 6万多
uint32 无符号32位整形 32-bit 0 ~ 4294967295 40多亿
uint64 无符号64位整形 64-bit 0 ~ 18446744073709551615 大到没概念
int8 8位整形 8-bit -128 ~ 127 正负百
int16 16位整形 16-bit -32768 ~ 32767 正负3万多
int32 32位整形 32-bit -2147483648 ~ 2147483647 正负20多亿
int64 64位整形 64-bit -9223372036854775808 ~ 9223372036854775807 正负大到没概念
int 系统决定 系统决定 32位系统为int32的值范围,64位系统为int64的值范围
uintptr 无符号整型 系统决定 能存放指针地址即可

浮点型

类型 名称 存储空间 值范围 数据级别
float32 32位浮点数 32-bit IEEE-754 1.401298464324817070923729583289916131280e-45 ~ 3.402823466385288598117041834516925440e+38 精度6位小数
float64 64位浮点数 64-bit IEEE-754 4.940656458412465441765687928682213723651e-324 ~ 1.797693134862315708145274237317043567981e+308 精度15位小数

复数型

类型 名称 存储空间 值范围 数据级别
omplex64 复数,含 float32 位实数和 float32 位虚数 64-bit 实数、虚数的取值范围对应 float32
complex128 复数,含 float64 位实数和 float64 位虚数 128-bit 实数、虚数的取值

字符型

类型 名称 存储空间 值范围 数据级别
byte 字符型,unit8 别名 8-bit 表示 UTF-8 字符串的单个字节的值,对应 ASCII 码的字符值
rune 字符型,int32 别名 32-bit 表示 单个 Unicode 字符
  1. package main
  2. import "fmt"
  3. func main() {
  4. // 无符号整形,默认值都是0
  5. var u8 uint8
  6. var u16 uint16
  7. var u32 uint32
  8. var u64 uint64
  9. fmt.Printf("u8: %d, u16: %d, u32: %d, u64: %d\n", u8, u16, u32, u64) // 默认值都为0
  10. u8 = 255
  11. u16 = 65535
  12. u32 = 4294967295
  13. u64 = 18446744073709551615
  14. fmt.Printf("u8: %d, u16: %d, u32: %d, u64: %d\n", u8, u16, u32, u64)
  15. // 整型
  16. var i8 int8
  17. var i16 int16
  18. var i32 int32
  19. var i64 int64
  20. fmt.Printf("i8: %d, i16: %d, i32: %d, i64: %d\n", i8, i16, i32, i64) // 默认值都为0
  21. i8 = 127
  22. i16 = 32767
  23. i32 = 2147483647
  24. i64 = 9223372036854775807
  25. fmt.Printf("i8: %d, i16: %d, i32: %d, i64: %d\n", i8, i16, i32, i64)
  26. // int 型,取值范围32位系统为 int32,64位系统为 int64,取值相同但为不同类型
  27. var i int
  28. //i = i32 // 报错,编译不通过,类型不同
  29. //i = i64 // 报错,编译不通过,类型不同
  30. i = -9223372036854775808
  31. fmt.Println("i: ", i)
  32. // 浮点型,f32精度6位小数,f64位精度15位小数
  33. var f32 float32
  34. var f64 float64
  35. fmt.Printf("f32: %f, f64: %f\n", f32, f64) // 默认值都为 0.000000
  36. f32 = 1.12345678
  37. f64 = 1.12345678901234567
  38. fmt.Printf("f32: %v, f64: %v\n", f32, f64) // 末位四舍五入,输出:f32: 1.1234568, f64: 1.1234567890123457
  39. // 复数型
  40. var c64 complex64
  41. var c128 complex128
  42. fmt.Printf("c64: %v, c128: %v\n", c64, c128) // 实数、虚数的默认值都为0
  43. c64 = 1.12345678 + 1.12345678i
  44. c128 = 2.1234567890123456 + 2.1234567890123456i
  45. fmt.Printf("c64: %v, c128: %v\n", c64, c128) // 输出:c64: (1.1234568+1.1234568i), c128: (2.1234567890123457+2.1234567890123457i)
  46. // 字符型
  47. var b byte // uint8 别名
  48. var r1, r2 rune // uint16 别名
  49. fmt.Printf("b: %v, r1: %v, r2: %v\n", b, r1, r2) // 默认值为0
  50. b = 'a'
  51. r1 = 'b'
  52. r2 = '字'
  53. fmt.Printf("b: %v, r1: %v, r2: %v\n", b, r1, r2) // 输出:b: 97(ASCII表示的数), r1: 98(utf-8表示的数), r2: 23383 (utf-8表示的数)
  54. b = u8
  55. r1 = i32
  56. fmt.Printf("b: %v, r1: %v\n", b, r1) // 输出:b: 255, r1: 2147483647
  57. // 指针地址
  58. var p uintptr
  59. fmt.Printf("p: %v\n", p) // 默认值为0
  60. p = 18446744073709551615 // 64位系统最大值
  61. //p = 18446744073709551616 // 报错:超出最大值
  62. fmt.Printf("p: %v\n", p)
  63. }

布尔类型 (bool)

值:truefalse,默认值为 false

  1. package main
  2. import "fmt"
  3. func main() {
  4. var v1, v2 bool // 声明变量,默认值为 false
  5. v1 = true // 赋值
  6. v3, v4 := false, true // 声明并赋值
  7. fmt.Print("v1:", v1) // v1 输出 true
  8. fmt.Print("\nv2:", v2) // v2 没有重新赋值,显示默认值:false
  9. fmt.Print("\nv3:", v3) // v3 false
  10. fmt.Print("\nv4:", v4) // v4 true
  11. }

字符串 (string)

Go 语言默认编码都是 UTF-8。

  1. package main
  2. import "fmt"
  3. func main() {
  4. var str1 string // 默认值为空字符串 ""
  5. str1 = `hello world`
  6. str2 := "你好世界"
  7. str := str1 + " " + str2 // 字符串连接
  8. fmt.Println(str1)
  9. fmt.Println(str2)
  10. fmt.Println(str) // 输出:hello world 你好世界
  11. // 遍历字符串
  12. l := len(str)
  13. for i := 0; i < l; i++ {
  14. chr := str[i]
  15. fmt.Println(i, chr) // 输出字符对应的编码数字
  16. }
  17. }

复合类型

类型 名称 长度 默认值 说明
pointer 指针 nil
array 数组 0
slice 切片 nil 引用类型
map 字典 nil 引用类型
struct 结构体

pointer

指针其实就是指向一个对象(任何一种类型数据、包括指针本身)的地址值,对指针的操作都会映射到指针所指的对象上。

与变量类似,使用前需要声明,使用 符号可以取内存地址&
声明指针的格式:

  1. var 指针变量名 *指针类型

指针的使用

  1. package main
  2. import ("fmt")
  3. func main() {
  4. // 声明指针变量
  5. var p *int // 定义指向int型的指针,默认值为空:nil
  6. // nil指针不指向任何有效存储地址,操作系统默认不能访问
  7. // fmt.Printf("%x\n", *p) // 编译报错
  8. //声明变量
  9. var a int = 10
  10. p = &a // 取地址
  11. add := a + *p // 取值
  12. fmt.Println(a) // 输出:10
  13. fmt.Println(p) // 输出:0xc0420080b8
  14. fmt.Println(add) // 输出:20
  15. }

通过指针修改变量

  1. package main
  2. import "fmt"
  3. func main () {
  4. var num int = 10
  5. fmt.Println(&num) // 0xc042052080
  6. var prt *int
  7. // 指针赋值
  8. prt = &num
  9. // 通过指针修改变量
  10. *ptr = 20
  11. fmt.Println(num) // 20
  12. }

go空指针

  1. //package 声明开头表示代码所属包
  2. package main
  3. import "fmt"
  4. func main() {
  5. var ptr *int
  6. fmt.Println("ptr的值为:", ptr) // ptr的值为: <nil>
  7. //判断空指针
  8. if ptr == nil{
  9. fmt.Println("是空") // 是空
  10. }
  11. }

值传递和引用传递

c代码

  1. void pass_by_val(int a){
  2. a++;
  3. }
  4. void pass_by_ref(int& a){
  5. a++;
  6. }
  7. int main() {
  8. int a = 3;
  9. pass_by_val(a);
  10. printf("pass_by_val: %d\n", a) // 3
  11. printf("pass_by_ref: %d\n", a) // 4
  12. }

值传递

  1. package main
  2. import "fmt"
  3. func swap(a, b int){
  4. a, b = b, a
  5. }
  6. func main() {
  7. a, b := 3, 4
  8. swap(a, b)
  9. fmt.Println(a, b)// 3 4
  10. }

引用传递

  1. package main
  2. import "fmt"
  3. func swap(a, b *int){
  4. *a, *b = *b, *a
  5. }
  6. func main() {
  7. a, b := 3, 4
  8. swap(&a, &b)
  9. fmt.Println(a, b) // 4 3
  10. }

new()和make()

  • new()用来分配内存,但与其他语言中的同名函数不同,它不会初始化内存,只会将内存置零
  • make(T)会返回一个指针,该指针指向新分配的,类型为T的零值,适用于创建结构体
  • make()的目的不同于new(),它只能创建slice、map、channel,并返回类型为T(非指针)的已初始化(非零值)的值
  1. //package 声明开头表示代码所属包
  2. package main
  3. import "fmt"
  4. func main() {
  5. p :=new([]int)
  6. fmt.Println(p)
  7. //[]int切片
  8. //10: 初始化10个长度
  9. //50: 容量为50
  10. m :=make([]int, 10, 50)
  11. fmt.Println(m)
  12. m[0] = 10
  13. (*p)[0] = 10
  14. fmt.Println(p)
  15. }

数组(array)

数组为一组相同数据类型数据的集合,数组定义后大小固定,不能更改,每个元素称为element,声明的数组元素默认值都是对应类型的0值。

声明变量:

  1. var 数组名 [数组长度]数组类型

数组长度 len(arr)

注:数组长度在定义后就不可变 len(arr)

遍历:

  • 循环通过过数组下标访问
  1. arr[0] ~ arr[(len(arr))]
  • range arr, 有两个返回值

    • 第一个为数组下标
    • 第二个为元素的值


    而且数组在Go语言中是一个值类型(value type),所有值类型变量在赋值和作为参数传递时都会产生一次复制动作,即对原值的拷贝

  • 1.声明后赋值

  • 2.声明并赋值
  • 3.声明时不设定大小,赋值后语言本身会计算数组大小
  • 4.声明时不设定大小,赋值时指定索引
  • 遍历数组
  1. package main
  2. import "fmt"
  3. func main() {
  4. // 1.声明后赋值
  5. // var <数组名称> [<数组长度>]<数组元素>
  6. var arr [2]int // 数组元素的默认值都是 0
  7. fmt.Println(arr) // 输出:[0 0]
  8. arr[0] = 1
  9. arr[1] = 2
  10. fmt.Println(arr) // 输出:[1 2]
  11. // 2.声明并赋值
  12. // var <数组名称> = [<数组长度>]<数组元素>{元素1,元素2,...}
  13. var intArr = [2]int{1, 2}
  14. strArr := [3]string{`aa`, `bb`, `cc`}
  15. fmt.Println(intArr) // 输出:[1 2]
  16. fmt.Println(strArr) // 输出:[aa bb cc]
  17. // 3.声明时不设定大小,赋值后语言本身会计算数组大小
  18. // var <数组名称> [<数组长度>]<数组元素> = [...]<元素类型>{元素1,元素2,...}
  19. var arr1 = [...]int{1, 2}
  20. arr2 := [...]int{1, 2, 3}
  21. fmt.Println(arr1) // 输出:[1 2]
  22. fmt.Println(arr2) // 输出:[1 2 3]
  23. //arr1[2] = 3 // 编译报错,数组大小已设定为2
  24. // 4.声明时不设定大小,赋值时指定索引
  25. // var <数组名称> [<数组长度>]<数组元素> = [...]<元素类型>{索引1:元素1,索引2:元素2,...}
  26. var arr3 = [...]int{1: 22, 0: 11, 2: 33}
  27. arr4 := [...]string{2: "cc", 1: "bb", 0: "aa"}
  28. fmt.Println(arr3) // 输出:[11 22 33]
  29. fmt.Println(arr4) // 输出:[aa bb cc]
  30. // 遍历数组
  31. for i := 0; i < len(arr4); i++ {
  32. v := arr4[i]
  33. fmt.Printf("i:%d, value:%s\n", i, v)
  34. }
  35. for index, value := range arr4 {
  36. fmt.Printf("arr[%d] = %d \t", idnex, value)
  37. }
  38. }

数组比较和赋值

  • 支持比较,只支持 == 或 !=, 比较是不是每一个元素都一样
  • 2个数组比较,数组类型要一样
  1. package main
  2. import "fmt"
  3. func main() {
  4. a := [5]int{1, 2, 3, 4, 5}
  5. b := [5]int{1, 2, 3, 4, 5}
  6. c := [5]int{1, 2, 3}
  7. fmt.Println(" a == b ", a == b) // true
  8. fmt.Println(" a == c ", a == c) // false
  9. //同类型的数组可以赋值
  10. var d [5]int
  11. d = a
  12. fmt.Println("d = ", d) // d = [1 2 3 4 5]
  13. fmt.Println(" d == a ", d == a) // true
  14. }

数组是值类型还是引用类型?

在函数间传递变量时,总是以值的方式,如果变量是个数组,那么就会整个复制,并传递给函数。

如果数组非常大,比如长度100多万,那么这对内存是一个很大的开销。

如果有几百万怎么办,有一种办法是传递数组的指针,这样,复制的大小只是一个数组类型的指针大小。

数组做函数参数:

  • 它是值传递
  • 实参数组 的每个元素给 形参数组 拷贝一份
  • 形参的数组实参数组 的复制品

数组做函数参数

  1. package main
  2. import "fmt"
  3. //数组做函数参数,它是值传递
  4. //实参数组的每个元素给形参数组拷贝一份
  5. //形参的数组是实参数组的复制品
  6. func modify(a [5]int) {
  7. a[0] = 666
  8. fmt.Println("modify a = ", a) // modify a = [666 2 3 4 5]
  9. }
  10. func main() {
  11. a := [5]int{1, 2, 3, 4, 5} //初始化
  12. modify(a) //数组传递过去
  13. fmt.Println("main: a = ", a) // main: a = [1 2 3 4 5]
  14. }

数组指针做函数参数 引用类型

  • 数组指针: *[5]int
  • 指针组组: [5]*int
  1. package main //必须有个main包
  2. import "fmt"
  3. //p指向实现数组a,它是指向数组,它是数组指针
  4. //*p代表指针所指向的内存,就是实参a
  5. //func modify(p *[5]int) {
  6. // (*p)[0] = 666
  7. // fmt.Println("modify *a = ",
  8. //}
  9. // 指针数组
  10. func pArr() {
  11. // 指针数组
  12. // 并且为索引1和3都创建了内存空间,其他索引是指针的零值nil
  13. array := [5]*int{1: new(int), 3:new(int)}
  14. /**
  15. 以上需要注意的是,只可以给索引1和3赋值,因为只有它们分配了内存,才可以赋值,
  16. 如果我们给索引0赋值,运行的时候,会提示无效内存或者是一个nil指针引用。
  17. */
  18. *array[1] = 1
  19. // 分配内存
  20. array[0] = new(int)
  21. // 赋值
  22. *array[0] = 2
  23. fmt.Println(*array[0]) // 2
  24. }
  25. // 数组指针 *[5]int
  26. func modify(p *[5]int) {
  27. p[0] = 666 // == (*p)[0] = 666
  28. fmt.Println("modify *a = ", *p) // modify *a = [666 2 3 4 5]
  29. }
  30. func main() {
  31. // 初始化
  32. a := [5]int{1, 2, 3, 4, 5}
  33. // 地址传递 (引用)
  34. modify(&a)
  35. fmt.Println("main: a = ", a) // main: a = [666 2 3 4 5]
  36. pArr()
  37. }

切片(slice) 引用类型

数组的长度在定义之后无法再次修改;数组是值类型,每次传递都将产生一份副本。显然这种数据结构无法完全满足开发者的真实需求。

Go语言提供了数组切片(slice)来弥补数组的不足。

切片并不是数组或数组指针,它通过内部 指针和相关属性引用数组片段,以实现变 方案。

slice并不是真正意义上的动态数组,而是一个引用类型。
slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度。
数据类型 - 图1

相关函数

  • len(slice): 返回 slice 的元素个数(长度)
  • cap(slice): 返回 slice 的分配空间大小
  • append(slice1, slice2…): 把 slice2 追加到 slice1 产生新的 slice, 若 slice2 是变量时,不能省略…,相当于 append(slice1, a[, b, …])
  • copy(目标slice, 源slice): 以最小的切片元素个数为准,将源 slice 复制到 目标 slice

切片的创建和初始化

slice和数组的区别

  • 声明数组时,方括号内写明了数组的长度或使用…自动计算长度
  • 声明slice时,方括号内没有任何字符

产生slice的三种方式

声明与 array 一样,不过不需要指定长度

  1. var slice1 []int
  2. slice2 := []int {元素1[, 元素2, ...]}

从数组(或者切片或者字符串)中获取

arr[i:j]

  • i=数组的开始位置
  • j=结束位结果
  • j-i=切片的长度
  • i和j都可以省略,省略时 i=0, j=len(arr),i是从0开始,j是从1开始
  1. a b c d e f
  2. i 0 1 2 3 4 5
  3. j 1 2 3 4 5 6
  4. slice1 := arr[:] // arr[0:6] / arr[0:]
  5. slice2 := arr[1:1] // []
  6. slice4 := arr3[1:3] // b c
  7. slice5 := arr3[:5] // = arr3[0:5]

make

  1. slice1 := make([]int, 5, 10)
  2. len(slice1) = 5, cap(slice1) = 10, 元素的初始值为
  1. // 声明切片和声明array一样,只是少了长度,此为空(nil)切片
  2. var s1 []int
  3. s2 := []int{}
  4. //make([]T, length, capacity) // capacity省略,则和length的值相同
  5. var s3 []int = make([]int, 0)
  6. s4 := make([]int, 0, 0)
  7. s5 := []int{1, 2, 3} // 创建切片并初始化

切片的操作

切片截取

操作 含义
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)
  1. package main
  2. import "fmt"
  3. func main() {
  4. a := []int{1, 2, 3, 4, 5}
  5. // a[0:3:5] 下标从0开始,长度为3, 容量为5
  6. s := a[0:3:5]
  7. fmt.Println("s = ", s) // s = [1 2 3]
  8. fmt.Println("len(s) = ", len(s)) // 长度 3 3减0
  9. fmt.Println("cap(s) = ", cap(s)) // 容量 5 5减0
  10. s = a[1:4:5]
  11. fmt.Println("s = ", s) // 从下标1开始,取4-1=3个 // [2 3 4]
  12. fmt.Println("len(s) = ", len(s)) // 长度 4-1 4减1
  13. fmt.Println("cap(s) = ", cap(s)) // 容量 5-1 5减1
  14. }

示例说明

array := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

操作 结果 len cap 说明
array[:6:8] [0 1 2 3 4 5] 6 8 省略 low
array[5:] [5 6 7 8 9] 5 5 省略 high、 max
array[:3] [0 1 2] 3 10 省略 high、 max
array[:] [0 1 2 3 4 5 6 7 8 9] 10 10 全部省略
  1. package main
  2. import "fmt"
  3. func main() {
  4. var sl []int // 声明一个切片
  5. sl = append(sl, 1, 2, 3) // 往切片中追加值
  6. fmt.Println(sl) // 输出:[1 2 3]
  7. var arr = [5]int{1, 2, 3, 4, 5} // 初始化一个数组
  8. var sl1 = arr[0:2] // 冒号:左边为起始位(包含起始位数据),右边为结束位(不包含结束位数据);不填则默认为头或尾
  9. var sl2 = arr[3:]
  10. var sl3 = arr[:5]
  11. fmt.Println(sl1) // 输出:[1 2]
  12. fmt.Println(sl2) // 输出:[4 5]
  13. fmt.Println(sl3) // 输出:[1 2 3 4 5]
  14. sl1 = append(sl1, 11, 22) // 追加元素
  15. fmt.Println(sl1) // 输出:[1 2 11 22]
  16. }

切片和底层数组关系

  1. package main
  2. import "fmt"
  3. func main() {
  4. // 切片a
  5. a := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
  6. //新切片
  7. s1 := a[2:5] // 从a[2]开始,取3个元素 {2, 3, 4}
  8. fmt.Println("s1 = ", s1) // s1 = [2 3 4]
  9. s1[1] = 666
  10. fmt.Println("s1 = ", s1) // s1 = [2 666 4]
  11. fmt.Println("a = ", a) // a = [0 1 2 666 4 5 6 7 8 9]
  12. //另外新切片
  13. s2 := s1[2:7]
  14. s2[2] = 777
  15. fmt.Println("s2 = ", s2)
  16. fmt.Println("a = ", a)
  17. }

内建函数

append

append函数向 slice 尾部添加数据,返回新的 slice 对象:

append函数会智能地底层数组的容量增长,一旦超过原底层数组容量,通常以2倍容量重新分配底层数组,并复制原来的数据:

  1. package main //必须有个main包
  2. import "fmt"
  3. func appendtestA() {
  4. s1 := []int{}
  5. fmt.Printf("len = %d, cap = %d\n", len(s1), cap(s1)) // len = 0, cap = 0
  6. fmt.Println("s1 = ", s1) // []
  7. //在原切片的末尾添加元素
  8. s1 = append(s1, 1)
  9. s1 = append(s1, 2)
  10. s1 = append(s1, 3)
  11. fmt.Printf("len = %d, cap = %d\n", len(s1), cap(s1)) // len = 3, cap = 4
  12. fmt.Println("s1 = ", s1) // s1 = [1 2 3]
  13. s2 := []int{1, 2, 3}
  14. fmt.Println("s2 = ", s2) // s2 = [1 2 3]
  15. s2 = append(s2, 5)
  16. s2 = append(s2, 5)
  17. s2 = append(s2, 5)
  18. fmt.Println("s2 = ", s2) // s2 = [1 2 3 5 5 5]
  19. }
  20. func appendtestB() {
  21. slice := []int {1, 2, 3, 4, 5}
  22. newSlice := slice[1:3:4]
  23. fmt.Println(newSlice) // [2 3]
  24. fmt.Println(slice) // [1 2 3 4 5]
  25. //...操作符,把一个切片追加到另一个切片里。
  26. newSlice = append(newSlice, slice...)
  27. fmt.Println(newSlice) //[2 3 1 2 3 4 5]
  28. }
  29. func main() {
  30. appendtestA()
  31. appendtestB()
  32. }

copy

函数 copy 在两个 slice 间复制数据,复制 度以 len 小的为准,两个 slice 可指向同 底层数组。

  1. package main //必须有个main包
  2. import "fmt"
  3. func main() {
  4. srcSlice := []int{1, 2}
  5. dstSlice := []int{6, 6, 6, 6, 6}
  6. copy(dstSlice, srcSlice)
  7. fmt.Println("dst = ", dstSlice) // dst = [1 2 6 6 6]
  8. }

切片做函数参数

  • 我们知道切片是3个字段构成的结构类型,所以在函数间以值的方式传递的时候,占用的内存非常小,成本很低。
  • 在传递复制切片的时候,其底层数组不会被复制,也不会受影响,复制只是复制的切片本身,不涉及底层数组。
  1. package main //必须有个main包
  2. import "fmt"
  3. import "math/rand"
  4. import "time"
  5. func InitData(s []int) {
  6. //设置种子
  7. rand.Seed(time.Now().UnixNano())
  8. for i := 0; i < len(s); i++ {
  9. s[i] = rand.Intn(100) //100以内的随机数
  10. }
  11. }
  12. //冒泡排序
  13. func BubbleSort(s []int) {
  14. n := len(s)
  15. for i := 0; i < n-1; i++ {
  16. for j := 0; j < n-1-i; j++ {
  17. if s[j] > s[j+1] {
  18. s[j], s[j+1] = s[j+1], s[j]
  19. }
  20. }
  21. }
  22. }
  23. func main2() {
  24. n := 10
  25. //创建一个切片,len为n
  26. s := make([]int, n)
  27. InitData(s) // 初始化数组
  28. fmt.Println("排序前: ", s) // 排序前: [18 4 54 30 91 76 28 40 60 15]
  29. // 冒泡排序
  30. BubbleSort(s)
  31. fmt.Println("排序后: ", s) // 排序后: [4 15 18 28 30 40 54 60 76 91]
  32. }
  33. func modifySlice(slice *[]int) {
  34. fmt.Printf("%p\n", &slice) // 0xc0000044c0
  35. (*slice)[1] = 200
  36. }
  37. func main() {
  38. slice := []int{1, 2, 3, 4, 5}
  39. fmt.Printf("%p\n", &slice) // 0xc042004030
  40. modifySlice(&slice)
  41. fmt.Println(slice) // [1 200 3 4 5]
  42. }
  43. /**
  44. 0xc0000044c0
  45. 0xc000006030
  46. [1 200 3 4 5]
  47. */
  • 从上面的代码中,可以看出mian函数slice的内存地址和 modifySlice方法中slice的内存地址是不样的
  • 也就是说这两个切片的地址不一样,所以可以确认切片在函数间传递是复制的。
  • 而我们修改一个索引的值后,发现原切片的值也被修改了,说明它们共用一个底层数组。
  • 在函数间传递切片非常高效,而且不需要传递指针和处理复杂的语法,
    只需要复制切片,然后根据自己的业务修改,最后传递回一个新的切片副本即可,这也是为什么函数间传递参数,使用切片,而不是数组的原因。

map

Go语言中的map(映射、字典)是一种内置的数据结构,它是一个无序的key—value对的集合。

map格式为

  1. map[keyType]valueType

错误的键

  • 在一个map里所有的键都是唯一的,而且必须是支持和!=操作符的类型==
  • 切片函数 以及 包含切片的结构类型 这些类型由于具有引用语义,不能作为映射的键,使用这些类型会造成编译错误:
  1. dict := map[[]string ]int{} // err, invalid map key type []string
  • map值可以是任意类型,没有限制。
  • map里所有键的数据类型必须是相同的,值也必须如何,但键和值的数据类型可以不相同。

注意:map是无序的,我们无法决定它的返回顺序,所以,每次打印结果的顺利有可能不同。

创建和初始化

map的创建

  1. var m1 map[int]string //只是声明一个map,没有初始化, 此为空(nil)map,还没有分配内存空间
  2. fmt.Println(m1 == nil) // true
  3. //m1[1] = "mike" //err, panic: assignment to entry in nil map
  4. //m2, m3的创建方法是等价的
  5. m2 := map[int]string{}
  6. m3 := make(map[int]string)
  7. fmt.Println(m2, m3) //map[] map[]
  8. m4 := make(map[int]string, 10) //第2个参数指定容量
  9. fmt.Println(m4)

初始化

  • 定义同时初始化
  1. var m1 map[int]string = map[int]string{1: "mike", 2: "yoyo"}
  2. fmt.Println(m1) //map[1:mike 2:yoyo]
  • 自动推导类型 :=
  1. m2 := map[int]string{1: "mike", 2: "yoyo"}
  2. fmt.Println(m2)
  1. package main
  2. import "fmt"
  3. func main() {
  4. //定义一个变量, 类型为map[int]string
  5. var m1 map[int]string
  6. // m1[0] = "aaaa" // panic: assignment to entry in nil map 未分配内存空间
  7. fmt.Println("m1 = ", m1) // m1 = map[]
  8. //对于map只有len,没有cap
  9. fmt.Println("len = ", len(m1)) // len = 0
  10. //可以通过make创建
  11. m2 := make(map[int]string)
  12. fmt.Println("m2 = ", m2) // m2 = map[]
  13. fmt.Println("len = ", len(m2)) // len = 0
  14. //可以通过make创建,可以指定长度,只是指定了容量,但是里面却是一个数据也没有
  15. m3 := make(map[int]string, 2)
  16. m3[1] = "mike" //元素的操作
  17. m3[2] = "go"
  18. m3[3] = "c++"
  19. m3[4] = "js"
  20. fmt.Println("m3 = ", m3) // m3 = map[1:mike 2:go 3:c++, 4:js]
  21. fmt.Println("len = ", len(m3)) // len = 4
  22. //初始化
  23. //键值是唯一的
  24. m4 := map[int]string{1: "mike", 2: "go", 3: "c++"}
  25. fmt.Println("m4 = ", m4) // m4 = map[1:mike 2:go 3:c++]
  26. }

常用操作

赋值

  1. package main
  2. import "fmt"
  3. func main() {
  4. m1 := map[int]string{1: "mike", 2: "yoyo"}
  5. //赋值,如果已经存在的key值,修改内容
  6. fmt.Println("m1 = ", m1) // m1 = map[1:mike 2:yoyo]
  7. //追加,map底层自动扩容,和append类似
  8. m1[1] = "c++"
  9. m1[3] = "go"
  10. fmt.Println("m1 = ", m1) // m1 = map[1:c++ 2:yoyo 3:go]
  11. }

map遍历

  1. package main //必须有个main包
  2. import (
  3. "fmt"
  4. "sort"
  5. )
  6. func main234() {
  7. m := map[int]string{1: "mike", 2: "yoyo", 3: "go"}
  8. //第一个返回值为key, 第二个返回值为value, 遍历结果是无序的
  9. for k, v := range m {
  10. fmt.Printf("%d =======> %s\n", k, v)
  11. }
  12. // 1 =======> mike
  13. // 2 =======> yoyo
  14. // 3 =======> go
  15. //如何判断一个key值是否存在
  16. //第一个返回值为key所对应的value, 第二个返回值为key是否存在的条件,存在ok为true
  17. value, ok := m[3]
  18. if ok == true {
  19. fmt.Println("m[1] = ", value) // m[1] = go
  20. } else {
  21. fmt.Println("key不存在")
  22. }
  23. }
  24. /**
  25. range 一个Map的时候,也可以使用一个返回值,这个默认的返回值就是Map的键。
  26. */
  27. func main() {
  28. dict := map[string]int{"王五": 60, "张三": 43}
  29. var names []string
  30. // 默认返回键
  31. for key := range dict {
  32. names = append(names, key)
  33. }
  34. fmt.Println(names) // [王五 张三]
  35. //排序
  36. sort.Strings(names)
  37. for _, key := range names {
  38. fmt.Println(key, dict[key])
  39. }
  40. // 张三 43
  41. // 王五 60
  42. }

map删除

  1. package main
  2. import "fmt"
  3. func main() {
  4. m := map[int]string{1: "mike", 2: "yoyo", 3: "go"}
  5. fmt.Println("m = ", m)
  6. delete(m, 1) // 删除key为1的内容
  7. fmt.Println("m = ", m) // m = map[2:yoyo 3:go]
  8. }

map做函数参数 引用传递

在函数间传递映射并不会制造出该映射的一个副本,不是值传递,而是引用传递

  1. package main
  2. import "fmt"
  3. func test(m map[int]string) {
  4. delete(m, 1)
  5. }
  6. // 引用
  7. //函数间传递Map是不会拷贝一个该Map的副本的,也就是说如果一个Map传递给一个函数,该函数对这个Map做了修改,那么这个Map的所有引用,都会感知到这个修改。
  8. func main() {
  9. m := map[int]string{1: "mike", 2: "yoyo", 3: "go"}
  10. fmt.Println("m = ", m) // m = map[1:mike 2:yoyo 3:go]
  11. test(m) //在函数内部删除某个key
  12. fmt.Println("m = ", m) // m = map[2:yoyo 3:go]
  13. }

参考