Go 中数据类型的分类
| 值类型 | 说明 | 默认值 | 复合类型 | 说明 | 默认值 |
|---|---|---|---|---|---|
| bool | false | slice | 引用类型 | nil | |
| numeric | 0 | map | 引用类型 | nil | |
| (unsafe)pointer | nill | channel | |||
| struct | function | ||||
| interface | |||||
| string | “” | ||||
| pointer | nil | ||||
| array | 0 |
fmt.Println(map[string]uint64{"ptr": uint64(unsafe.Sizeof(&struct{}{})),"map": uint64(unsafe.Sizeof(map[bool]bool{})),"slice": uint64(unsafe.Sizeof([]struct{}{})),"chan": uint64(unsafe.Sizeof(make(chan struct{}))),"func": uint64(unsafe.Sizeof(func() {})),"interface": uint64(unsafe.Sizeof(interface{}(0))),})// 输出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个(均为小写)。
关键字不能用于自定义名字,只能在特定语法结构中使用。
break default func interface selectcase defer go map structchan else goto package switchconst fallthrough if range typecontinue for import return var
此外,还有大约30多个预定义的名字,比如int和true等,主要对应内建的常量、类型和函数。
内建常量:
true false iota nil
内建类型:
int int8 int16 int32 int64uint uint8 uint16 uint32 uint64 uintptrfloat32 float64 complex128 complex64bool byte rune string error
内建函数:
make len cap new append copy close deletecomplex real imagpanic recover
类型分类
声明变量的方式
Go 语言在声明变量时会默认给变量赋个当前类型的空值
| 声明方式 | 说明 |
|---|---|
| var 变量名 <变量类型> | 声明单个变量 |
| var 变量名1, 变量名2,… <变量类型> | 声明多个同类型变量 |
| 变量名 := 值 | 声明变量,并赋值;Go 语言会根据所赋值推断变量的类型 |
| 变量名1, 变量名2,… := 值1, 值2,… | 声明多个同类型变量并赋值,几个变量必须赋几个值 |
基本数据类型
- 整型
- 浮点型
- 字符型
- 布尔型
- 复数型
- 字符串型
- 错误类型
复合数据类型
- 指针
- 数组
- 切片
- 字典
- 通道
- 结构体
- 接口
自定义类型
Go语言支持我们自定义类型,比如刚刚上面的结构体类型,就是我们自定义的类型,这也是比较常用的自定义类型的方法。
另外一个自定义类型的方法是基于一个已有的类型,就是基于一个现有的类型创造新的类型,这种也是使用type关键字。
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 字符 |
package mainimport "fmt"func main() {// 无符号整形,默认值都是0var u8 uint8var u16 uint16var u32 uint32var u64 uint64fmt.Printf("u8: %d, u16: %d, u32: %d, u64: %d\n", u8, u16, u32, u64) // 默认值都为0u8 = 255u16 = 65535u32 = 4294967295u64 = 18446744073709551615fmt.Printf("u8: %d, u16: %d, u32: %d, u64: %d\n", u8, u16, u32, u64)// 整型var i8 int8var i16 int16var i32 int32var i64 int64fmt.Printf("i8: %d, i16: %d, i32: %d, i64: %d\n", i8, i16, i32, i64) // 默认值都为0i8 = 127i16 = 32767i32 = 2147483647i64 = 9223372036854775807fmt.Printf("i8: %d, i16: %d, i32: %d, i64: %d\n", i8, i16, i32, i64)// int 型,取值范围32位系统为 int32,64位系统为 int64,取值相同但为不同类型var i int//i = i32 // 报错,编译不通过,类型不同//i = i64 // 报错,编译不通过,类型不同i = -9223372036854775808fmt.Println("i: ", i)// 浮点型,f32精度6位小数,f64位精度15位小数var f32 float32var f64 float64fmt.Printf("f32: %f, f64: %f\n", f32, f64) // 默认值都为 0.000000f32 = 1.12345678f64 = 1.12345678901234567fmt.Printf("f32: %v, f64: %v\n", f32, f64) // 末位四舍五入,输出:f32: 1.1234568, f64: 1.1234567890123457// 复数型var c64 complex64var c128 complex128fmt.Printf("c64: %v, c128: %v\n", c64, c128) // 实数、虚数的默认值都为0c64 = 1.12345678 + 1.12345678ic128 = 2.1234567890123456 + 2.1234567890123456ifmt.Printf("c64: %v, c128: %v\n", c64, c128) // 输出:c64: (1.1234568+1.1234568i), c128: (2.1234567890123457+2.1234567890123457i)// 字符型var b byte // uint8 别名var r1, r2 rune // uint16 别名fmt.Printf("b: %v, r1: %v, r2: %v\n", b, r1, r2) // 默认值为0b = 'a'r1 = 'b'r2 = '字'fmt.Printf("b: %v, r1: %v, r2: %v\n", b, r1, r2) // 输出:b: 97(ASCII表示的数), r1: 98(utf-8表示的数), r2: 23383 (utf-8表示的数)b = u8r1 = i32fmt.Printf("b: %v, r1: %v\n", b, r1) // 输出:b: 255, r1: 2147483647// 指针地址var p uintptrfmt.Printf("p: %v\n", p) // 默认值为0p = 18446744073709551615 // 64位系统最大值//p = 18446744073709551616 // 报错:超出最大值fmt.Printf("p: %v\n", p)}
布尔类型 (bool)
值:true 和 false,默认值为 false
package mainimport "fmt"func main() {var v1, v2 bool // 声明变量,默认值为 falsev1 = true // 赋值v3, v4 := false, true // 声明并赋值fmt.Print("v1:", v1) // v1 输出 truefmt.Print("\nv2:", v2) // v2 没有重新赋值,显示默认值:falsefmt.Print("\nv3:", v3) // v3 falsefmt.Print("\nv4:", v4) // v4 true}
字符串 (string)
Go 语言默认编码都是 UTF-8。
package mainimport "fmt"func main() {var str1 string // 默认值为空字符串 ""str1 = `hello world`str2 := "你好世界"str := str1 + " " + str2 // 字符串连接fmt.Println(str1)fmt.Println(str2)fmt.Println(str) // 输出:hello world 你好世界// 遍历字符串l := len(str)for i := 0; i < l; i++ {chr := str[i]fmt.Println(i, chr) // 输出字符对应的编码数字}}
复合类型
| 类型 | 名称 | 长度 | 默认值 | 说明 |
|---|---|---|---|---|
| pointer | 指针 | nil | ||
| array | 数组 | 0 | ||
| slice | 切片 | nil | 引用类型 | |
| map | 字典 | nil | 引用类型 | |
| struct | 结构体 |
pointer
指针其实就是指向一个对象(任何一种类型数据、包括指针本身)的地址值,对指针的操作都会映射到指针所指的对象上。
与变量类似,使用前需要声明,使用 符号可以取内存地址&
声明指针的格式:
var 指针变量名 *指针类型
指针的使用
package mainimport ("fmt")func main() {// 声明指针变量var p *int // 定义指向int型的指针,默认值为空:nil// nil指针不指向任何有效存储地址,操作系统默认不能访问// fmt.Printf("%x\n", *p) // 编译报错//声明变量var a int = 10p = &a // 取地址add := a + *p // 取值fmt.Println(a) // 输出:10fmt.Println(p) // 输出:0xc0420080b8fmt.Println(add) // 输出:20}
通过指针修改变量
package mainimport "fmt"func main () {var num int = 10fmt.Println(&num) // 0xc042052080var prt *int// 指针赋值prt = &num// 通过指针修改变量*ptr = 20fmt.Println(num) // 20}
go空指针
//package 声明开头表示代码所属包package mainimport "fmt"func main() {var ptr *intfmt.Println("ptr的值为:", ptr) // ptr的值为: <nil>//判断空指针if ptr == nil{fmt.Println("是空") // 是空}}
值传递和引用传递
c代码
void pass_by_val(int a){a++;}void pass_by_ref(int& a){a++;}int main() {int a = 3;pass_by_val(a);printf("pass_by_val: %d\n", a) // 3printf("pass_by_ref: %d\n", a) // 4}
值传递
package mainimport "fmt"func swap(a, b int){a, b = b, a}func main() {a, b := 3, 4swap(a, b)fmt.Println(a, b)// 3 4}
引用传递
package mainimport "fmt"func swap(a, b *int){*a, *b = *b, *a}func main() {a, b := 3, 4swap(&a, &b)fmt.Println(a, b) // 4 3}
new()和make()
- new()用来分配内存,但与其他语言中的同名函数不同,它不会初始化内存,只会将内存置零
- make(T)会返回一个指针,该指针指向新分配的,类型为T的零值,适用于创建结构体
- make()的目的不同于new(),它只能创建slice、map、channel,并返回类型为T(非指针)的已初始化(非零值)的值
//package 声明开头表示代码所属包package mainimport "fmt"func main() {p :=new([]int)fmt.Println(p)//[]int切片//10: 初始化10个长度//50: 容量为50m :=make([]int, 10, 50)fmt.Println(m)m[0] = 10(*p)[0] = 10fmt.Println(p)}
数组(array)
数组为一组相同数据类型数据的集合,数组定义后大小固定,不能更改,每个元素称为element,声明的数组元素默认值都是对应类型的0值。
声明变量:
var 数组名 [数组长度]数组类型
数组长度 len(arr)
注:数组长度在定义后就不可变 len(arr)
遍历:
- 循环通过过数组下标访问
arr[0] ~ arr[(len(arr))]
range arr, 有两个返回值
- 第一个为数组下标
- 第二个为元素的值
而且数组在Go语言中是一个值类型(value type),所有值类型变量在赋值和作为参数传递时都会产生一次复制动作,即对原值的拷贝1.声明后赋值
- 2.声明并赋值
- 3.声明时不设定大小,赋值后语言本身会计算数组大小
- 4.声明时不设定大小,赋值时指定索引
- 遍历数组
package mainimport "fmt"func main() {// 1.声明后赋值// var <数组名称> [<数组长度>]<数组元素>var arr [2]int // 数组元素的默认值都是 0fmt.Println(arr) // 输出:[0 0]arr[0] = 1arr[1] = 2fmt.Println(arr) // 输出:[1 2]// 2.声明并赋值// var <数组名称> = [<数组长度>]<数组元素>{元素1,元素2,...}var intArr = [2]int{1, 2}strArr := [3]string{`aa`, `bb`, `cc`}fmt.Println(intArr) // 输出:[1 2]fmt.Println(strArr) // 输出:[aa bb cc]// 3.声明时不设定大小,赋值后语言本身会计算数组大小// var <数组名称> [<数组长度>]<数组元素> = [...]<元素类型>{元素1,元素2,...}var arr1 = [...]int{1, 2}arr2 := [...]int{1, 2, 3}fmt.Println(arr1) // 输出:[1 2]fmt.Println(arr2) // 输出:[1 2 3]//arr1[2] = 3 // 编译报错,数组大小已设定为2// 4.声明时不设定大小,赋值时指定索引// var <数组名称> [<数组长度>]<数组元素> = [...]<元素类型>{索引1:元素1,索引2:元素2,...}var arr3 = [...]int{1: 22, 0: 11, 2: 33}arr4 := [...]string{2: "cc", 1: "bb", 0: "aa"}fmt.Println(arr3) // 输出:[11 22 33]fmt.Println(arr4) // 输出:[aa bb cc]// 遍历数组for i := 0; i < len(arr4); i++ {v := arr4[i]fmt.Printf("i:%d, value:%s\n", i, v)}for index, value := range arr4 {fmt.Printf("arr[%d] = %d \t", idnex, value)}}
数组比较和赋值
- 支持比较,只支持 == 或 !=, 比较是不是每一个元素都一样
- 2个数组比较,数组类型要一样
package mainimport "fmt"func main() {a := [5]int{1, 2, 3, 4, 5}b := [5]int{1, 2, 3, 4, 5}c := [5]int{1, 2, 3}fmt.Println(" a == b ", a == b) // truefmt.Println(" a == c ", a == c) // false//同类型的数组可以赋值var d [5]intd = afmt.Println("d = ", d) // d = [1 2 3 4 5]fmt.Println(" d == a ", d == a) // true}
数组是值类型还是引用类型?
在函数间传递变量时,总是以值的方式,如果变量是个数组,那么就会整个复制,并传递给函数。
如果数组非常大,比如长度100多万,那么这对内存是一个很大的开销。
如果有几百万怎么办,有一种办法是传递数组的指针,这样,复制的大小只是一个数组类型的指针大小。
数组做函数参数:
- 它是值传递
实参数组的每个元素给形参数组拷贝一份形参的数组是实参数组的复制品
数组做函数参数
package mainimport "fmt"//数组做函数参数,它是值传递//实参数组的每个元素给形参数组拷贝一份//形参的数组是实参数组的复制品func modify(a [5]int) {a[0] = 666fmt.Println("modify a = ", a) // modify a = [666 2 3 4 5]}func main() {a := [5]int{1, 2, 3, 4, 5} //初始化modify(a) //数组传递过去fmt.Println("main: a = ", a) // main: a = [1 2 3 4 5]}
数组指针做函数参数 引用类型
- 数组指针: *[5]int
- 指针组组: [5]*int
package main //必须有个main包import "fmt"//p指向实现数组a,它是指向数组,它是数组指针//*p代表指针所指向的内存,就是实参a//func modify(p *[5]int) {// (*p)[0] = 666// fmt.Println("modify *a = ",//}// 指针数组func pArr() {// 指针数组// 并且为索引1和3都创建了内存空间,其他索引是指针的零值nilarray := [5]*int{1: new(int), 3:new(int)}/**以上需要注意的是,只可以给索引1和3赋值,因为只有它们分配了内存,才可以赋值,如果我们给索引0赋值,运行的时候,会提示无效内存或者是一个nil指针引用。*/*array[1] = 1// 分配内存array[0] = new(int)// 赋值*array[0] = 2fmt.Println(*array[0]) // 2}// 数组指针 *[5]intfunc modify(p *[5]int) {p[0] = 666 // == (*p)[0] = 666fmt.Println("modify *a = ", *p) // modify *a = [666 2 3 4 5]}func main() {// 初始化a := [5]int{1, 2, 3, 4, 5}// 地址传递 (引用)modify(&a)fmt.Println("main: a = ", a) // main: a = [666 2 3 4 5]pArr()}
切片(slice) 引用类型
数组的长度在定义之后无法再次修改;数组是值类型,每次传递都将产生一份副本。显然这种数据结构无法完全满足开发者的真实需求。
Go语言提供了数组切片(slice)来弥补数组的不足。
切片并不是数组或数组指针,它通过内部 指针和相关属性引用数组片段,以实现变 方案。
slice并不是真正意义上的动态数组,而是一个引用类型。
slice总是指向一个底层array,slice的声明也可以像array一样,只是不需要长度。
相关函数
- 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 一样,不过不需要指定长度
var slice1 []intslice2 := []int {元素1[, 元素2, ...]}
从数组(或者切片或者字符串)中获取
arr[i:j]
- i=数组的开始位置
- j=结束位结果
- j-i=切片的长度
- i和j都可以省略,省略时 i=0, j=len(arr),i是从0开始,j是从1开始
a b c d e fi 0 1 2 3 4 5j 1 2 3 4 5 6slice1 := arr[:] // arr[0:6] / arr[0:]slice2 := arr[1:1] // []slice4 := arr3[1:3] // b cslice5 := arr3[:5] // = arr3[0:5]
make
slice1 := make([]int, 5, 10)len(slice1) = 5, cap(slice1) = 10, 元素的初始值为
// 声明切片和声明array一样,只是少了长度,此为空(nil)切片var s1 []ints2 := []int{}//make([]T, length, capacity) // capacity省略,则和length的值相同var s3 []int = make([]int, 0)s4 := make([]int, 0, 0)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) |
package mainimport "fmt"func main() {a := []int{1, 2, 3, 4, 5}// a[0:3:5] 下标从0开始,长度为3, 容量为5s := a[0:3:5]fmt.Println("s = ", s) // s = [1 2 3]fmt.Println("len(s) = ", len(s)) // 长度 3 3减0fmt.Println("cap(s) = ", cap(s)) // 容量 5 5减0s = a[1:4:5]fmt.Println("s = ", s) // 从下标1开始,取4-1=3个 // [2 3 4]fmt.Println("len(s) = ", len(s)) // 长度 4-1 4减1fmt.Println("cap(s) = ", cap(s)) // 容量 5-1 5减1}
示例说明
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 | 全部省略 |
package mainimport "fmt"func main() {var sl []int // 声明一个切片sl = append(sl, 1, 2, 3) // 往切片中追加值fmt.Println(sl) // 输出:[1 2 3]var arr = [5]int{1, 2, 3, 4, 5} // 初始化一个数组var sl1 = arr[0:2] // 冒号:左边为起始位(包含起始位数据),右边为结束位(不包含结束位数据);不填则默认为头或尾var sl2 = arr[3:]var sl3 = arr[:5]fmt.Println(sl1) // 输出:[1 2]fmt.Println(sl2) // 输出:[4 5]fmt.Println(sl3) // 输出:[1 2 3 4 5]sl1 = append(sl1, 11, 22) // 追加元素fmt.Println(sl1) // 输出:[1 2 11 22]}
切片和底层数组关系
package mainimport "fmt"func main() {// 切片aa := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}//新切片s1 := a[2:5] // 从a[2]开始,取3个元素 {2, 3, 4}fmt.Println("s1 = ", s1) // s1 = [2 3 4]s1[1] = 666fmt.Println("s1 = ", s1) // s1 = [2 666 4]fmt.Println("a = ", a) // a = [0 1 2 666 4 5 6 7 8 9]//另外新切片s2 := s1[2:7]s2[2] = 777fmt.Println("s2 = ", s2)fmt.Println("a = ", a)}
内建函数
append
append函数向 slice 尾部添加数据,返回新的 slice 对象:
append函数会智能地底层数组的容量增长,一旦超过原底层数组容量,通常以2倍容量重新分配底层数组,并复制原来的数据:
package main //必须有个main包import "fmt"func appendtestA() {s1 := []int{}fmt.Printf("len = %d, cap = %d\n", len(s1), cap(s1)) // len = 0, cap = 0fmt.Println("s1 = ", s1) // []//在原切片的末尾添加元素s1 = append(s1, 1)s1 = append(s1, 2)s1 = append(s1, 3)fmt.Printf("len = %d, cap = %d\n", len(s1), cap(s1)) // len = 3, cap = 4fmt.Println("s1 = ", s1) // s1 = [1 2 3]s2 := []int{1, 2, 3}fmt.Println("s2 = ", s2) // s2 = [1 2 3]s2 = append(s2, 5)s2 = append(s2, 5)s2 = append(s2, 5)fmt.Println("s2 = ", s2) // s2 = [1 2 3 5 5 5]}func appendtestB() {slice := []int {1, 2, 3, 4, 5}newSlice := slice[1:3:4]fmt.Println(newSlice) // [2 3]fmt.Println(slice) // [1 2 3 4 5]//...操作符,把一个切片追加到另一个切片里。newSlice = append(newSlice, slice...)fmt.Println(newSlice) //[2 3 1 2 3 4 5]}func main() {appendtestA()appendtestB()}
copy
函数 copy 在两个 slice 间复制数据,复制 度以 len 小的为准,两个 slice 可指向同 底层数组。
package main //必须有个main包import "fmt"func main() {srcSlice := []int{1, 2}dstSlice := []int{6, 6, 6, 6, 6}copy(dstSlice, srcSlice)fmt.Println("dst = ", dstSlice) // dst = [1 2 6 6 6]}
切片做函数参数
- 我们知道切片是3个字段构成的结构类型,所以在函数间以值的方式传递的时候,占用的内存非常小,成本很低。
- 在传递复制切片的时候,其底层数组不会被复制,也不会受影响,复制只是复制的切片本身,不涉及底层数组。
package main //必须有个main包import "fmt"import "math/rand"import "time"func InitData(s []int) {//设置种子rand.Seed(time.Now().UnixNano())for i := 0; i < len(s); i++ {s[i] = rand.Intn(100) //100以内的随机数}}//冒泡排序func BubbleSort(s []int) {n := len(s)for i := 0; i < n-1; i++ {for j := 0; j < n-1-i; j++ {if s[j] > s[j+1] {s[j], s[j+1] = s[j+1], s[j]}}}}func main2() {n := 10//创建一个切片,len为ns := make([]int, n)InitData(s) // 初始化数组fmt.Println("排序前: ", s) // 排序前: [18 4 54 30 91 76 28 40 60 15]// 冒泡排序BubbleSort(s)fmt.Println("排序后: ", s) // 排序后: [4 15 18 28 30 40 54 60 76 91]}func modifySlice(slice *[]int) {fmt.Printf("%p\n", &slice) // 0xc0000044c0(*slice)[1] = 200}func main() {slice := []int{1, 2, 3, 4, 5}fmt.Printf("%p\n", &slice) // 0xc042004030modifySlice(&slice)fmt.Println(slice) // [1 200 3 4 5]}/**0xc0000044c00xc000006030[1 200 3 4 5]*/
- 从上面的代码中,可以看出mian函数slice的内存地址和 modifySlice方法中slice的内存地址是不样的
- 也就是说这两个切片的地址不一样,所以可以确认切片在函数间传递是复制的。
- 而我们修改一个索引的值后,发现原切片的值也被修改了,说明它们共用一个底层数组。
- 在函数间传递切片非常高效,而且不需要传递指针和处理复杂的语法,
只需要复制切片,然后根据自己的业务修改,最后传递回一个新的切片副本即可,这也是为什么函数间传递参数,使用切片,而不是数组的原因。
map
Go语言中的map(映射、字典)是一种内置的数据结构,它是一个无序的key—value对的集合。
map格式为
map[keyType]valueType
错误的键
- 在一个map里所有的键都是唯一的,而且必须是支持和!=操作符的类型==
切片、函数以及包含切片的结构类型这些类型由于具有引用语义,不能作为映射的键,使用这些类型会造成编译错误:
dict := map[[]string ]int{} // err, invalid map key type []string
- map值可以是任意类型,没有限制。
- map里所有键的数据类型必须是相同的,值也必须如何,但键和值的数据类型可以不相同。
注意:map是无序的,我们无法决定它的返回顺序,所以,每次打印结果的顺利有可能不同。
创建和初始化
map的创建
var m1 map[int]string //只是声明一个map,没有初始化, 此为空(nil)map,还没有分配内存空间fmt.Println(m1 == nil) // true//m1[1] = "mike" //err, panic: assignment to entry in nil map//m2, m3的创建方法是等价的m2 := map[int]string{}m3 := make(map[int]string)fmt.Println(m2, m3) //map[] map[]m4 := make(map[int]string, 10) //第2个参数指定容量fmt.Println(m4)
初始化
- 定义同时初始化
var m1 map[int]string = map[int]string{1: "mike", 2: "yoyo"}fmt.Println(m1) //map[1:mike 2:yoyo]
- 自动推导类型 :=
m2 := map[int]string{1: "mike", 2: "yoyo"}fmt.Println(m2)
package mainimport "fmt"func main() {//定义一个变量, 类型为map[int]stringvar m1 map[int]string// m1[0] = "aaaa" // panic: assignment to entry in nil map 未分配内存空间fmt.Println("m1 = ", m1) // m1 = map[]//对于map只有len,没有capfmt.Println("len = ", len(m1)) // len = 0//可以通过make创建m2 := make(map[int]string)fmt.Println("m2 = ", m2) // m2 = map[]fmt.Println("len = ", len(m2)) // len = 0//可以通过make创建,可以指定长度,只是指定了容量,但是里面却是一个数据也没有m3 := make(map[int]string, 2)m3[1] = "mike" //元素的操作m3[2] = "go"m3[3] = "c++"m3[4] = "js"fmt.Println("m3 = ", m3) // m3 = map[1:mike 2:go 3:c++, 4:js]fmt.Println("len = ", len(m3)) // len = 4//初始化//键值是唯一的m4 := map[int]string{1: "mike", 2: "go", 3: "c++"}fmt.Println("m4 = ", m4) // m4 = map[1:mike 2:go 3:c++]}
常用操作
赋值
package mainimport "fmt"func main() {m1 := map[int]string{1: "mike", 2: "yoyo"}//赋值,如果已经存在的key值,修改内容fmt.Println("m1 = ", m1) // m1 = map[1:mike 2:yoyo]//追加,map底层自动扩容,和append类似m1[1] = "c++"m1[3] = "go"fmt.Println("m1 = ", m1) // m1 = map[1:c++ 2:yoyo 3:go]}
map遍历
package main //必须有个main包import ("fmt""sort")func main234() {m := map[int]string{1: "mike", 2: "yoyo", 3: "go"}//第一个返回值为key, 第二个返回值为value, 遍历结果是无序的for k, v := range m {fmt.Printf("%d =======> %s\n", k, v)}// 1 =======> mike// 2 =======> yoyo// 3 =======> go//如何判断一个key值是否存在//第一个返回值为key所对应的value, 第二个返回值为key是否存在的条件,存在ok为truevalue, ok := m[3]if ok == true {fmt.Println("m[1] = ", value) // m[1] = go} else {fmt.Println("key不存在")}}/**range 一个Map的时候,也可以使用一个返回值,这个默认的返回值就是Map的键。*/func main() {dict := map[string]int{"王五": 60, "张三": 43}var names []string// 默认返回键for key := range dict {names = append(names, key)}fmt.Println(names) // [王五 张三]//排序sort.Strings(names)for _, key := range names {fmt.Println(key, dict[key])}// 张三 43// 王五 60}
map删除
package mainimport "fmt"func main() {m := map[int]string{1: "mike", 2: "yoyo", 3: "go"}fmt.Println("m = ", m)delete(m, 1) // 删除key为1的内容fmt.Println("m = ", m) // m = map[2:yoyo 3:go]}
map做函数参数 引用传递
在函数间传递映射并不会制造出该映射的一个副本,不是值传递,而是引用传递
package mainimport "fmt"func test(m map[int]string) {delete(m, 1)}// 引用//函数间传递Map是不会拷贝一个该Map的副本的,也就是说如果一个Map传递给一个函数,该函数对这个Map做了修改,那么这个Map的所有引用,都会感知到这个修改。func main() {m := map[int]string{1: "mike", 2: "yoyo", 3: "go"}fmt.Println("m = ", m) // m = map[1:mike 2:yoyo 3:go]test(m) //在函数内部删除某个keyfmt.Println("m = ", m) // m = map[2:yoyo 3:go]}
参考
- https://www.flysnow.org/2017/03/26/go-in-action-go-type.html
- Golang 中该使用指针类型还是值类型?
