这篇文章就总结一下go 的细节
具体参考:
http://c.biancheng.net/golang/
但是里面有写 收费章节(其实他也是抄的别的,暂时没找到源头)
这个是 gitbook 的文档,但是好像有点岁月的痕迹
https://wizardforcel.gitbooks.io/gopl-zh/preface.html
- 数组声明
var 数组变量名 [元素数量]Type
数组的元素数量,可以是一个表达式,但最终通过编译期计算的结果必须是整型数值,元素数量不能含有到运行时才能确认大小的数值
func get_array_length() int {
return 3
}
func main() {
length := get_array_length()
fmt.Println(length)
var a [len("sffddd")] int // 定义三个整数的数组 但是内置
函数 比如 len 就不会报错
// var a [get_array_length()]int // 数组长度必须在编译的时候
就搞定,否
则编译报错 这个就包错:non-constant array bound length
// 现在算是理解了一点,解释语言和编译语言的区别,编译语
言关于内存
分配的初始化都要,在编译的时候就要搞定,
b := make([]int, get_array_length()) // 这种作为函数参数的
还是可以 使用
fmt.Printf("%v\n", b)
fmt.Println(a[0]) // 打印第一个元素
fmt.Println(a[len(a)-1]) // 打印最后一个元素
}
现在算是理解了一点,解释语言和编译语言的区别,编译语言关于内存分配的初始化都要在编译的时候就要搞定,不能间接间接计算获得(除了一些内置函数,如上面的len) 这个以后要多练习,才能理解了
-
go 多维数组
记住,go 数组是 值类型, 多维数组也是会按照 类型的初始值来填充的
var array_name [size1][size2]...[sizen] array_type
从前到后的 size 分别是外层到内层的
// 声明一个二维整型数组,两个维度的长度分别是 4 和 2
var array [4][2]int
array = [4][2]int{{10, 11}, {20, 21}, {30, 31}, {40, 41}}
// 写了索引就按照索引赋值,默认是按照顺序的
array = [4][2]int{1: {20, 21}, {40, 41}}
// [[0 0] [20 21] [40 41] [0 0]]
array = [4][2]int{1: {20, 21}, 3:{40, 41}}
// [[0 0] [20 21] [0 0] [40 41]]
// 声明并初始化数组中指定的元素
array = [4][2]int{1: {0: 20}, 3: {1: 41}}
// [[0 0] [20 0] [0 0] [0 41]]
如果指定了索引,初始化,后面不指定索引的话,就按照前面的索引+1 来赋值了
-
切片(slice)
是对数组的一个连续片段的引用,所以切片是一个引用类型
a := [3] int {1,2,3}
b:= a[:]
a[0] = 10
fmt.Println(b) // [10 2 3]
数组直接切片就是返回的就是切片类型, 他是原来数组的一个引
用,如果原来数组改变,切片的值也是跟着变的
而且切片是引用类型,索引当做函数参数,在函数内部的改变也是会影响外面的值的。
一般 [ num ] 或者 [...] 这种 有长度的是数组 ,而没有长度的是 切片
切片可以从数组或切片生成新的切片, 他们都是原来对象的引用
切片复位:
a := []int{1, 2, 3}
a = a[:] // [ ]
slice[0:0] // 两者同时为 0 时,等效于空切片
切片分配地址:
声明一个切片的时候:
a := [] int // 此时a 为 nil 0x0
a := [] int {} // 此时 a 为 [] 但不是 nil 已经分配了地址
a := make([] int , length, cap) // a 为 [ ] 也已经分配了地址
使用 make 的好处是 可以指定 切片初始 的大小和容量
(使用make 一定分配了内存,但是直接 [a:b] 只是把切片指向了已经存在的内存区域, 而没有重新分配内存)
- 切片 append()
append 给切片动态的添加元素
尾部追加
var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包
开头添加(一般导致内存重新分配,已有元素复制一份)
var a = []int{1,2,3}
a = append([]int{0}, a...) // 在开头添加1个元素
a = append([]int{-3,-2,-1}, a...) // 在开头添加1个切片
append 原地址空间不足就会扩容 和 py 类似都是 二倍空间 递增
var numbers []int
for i := 0; i < 10; i++ {
numbers = append(numbers, i)
fmt.Printf("len: %d cap: %d pointer: %p\n", len(numbers),
cap(numbers), numbers)
}
> len: 1 cap: 1 pointer: 0xc00004a080
len: 2 cap: 2 pointer: 0xc00004a0c0
len: 3 cap: 4 pointer: 0xc0000480e0
len: 4 cap: 4 pointer: 0xc000040e0
len: 5 cap: 8 pointer: 0xc000078080
len: 6 cap: 8 pointer: 0xc000078080
len: 7 cap: 8 pointer: 0xc000078080
len: 8 cap: 8 pointer: 0xc000078080
len: 9 cap: 16 pointer: 0xc00007a080
len: 10 cap: 16 pointer: 0xc00007a080
append 链式调用
var a []int
a = append(a[:i], append([]int{x}, a[i:]...)...) // 在第i个位置插入x
a = append(a[:i], append([]int{1,2,3}, a[i:]...)...) // 在第i个位置插入切片
每个添加操作中的第二个 append 调用都会创建一个临时切片,并将 a[i:] 的内容复制到新创建的切片中,然后将临时创建的切片再追加到 a[:i] 中
切片扩容之后,会更换内存地址,这样就和原来的没关系了:
a := [3] int {1,2,3}
b:= a[:]
b = append(b, 5,6,7)
b[0] = 10 // a [1 2 3] b [10 2 3 5 6 7]
所以扩容更换地址,就不是引用原来对象了,而是直接复制了一个 新的。
a := make([] int, 3, 5)
a = []int{1,2,3}
b := append(a, 4)
a[0] = 10
fmt.Printf("b 是 %v, a 是 %v \n", b, a)
fmt.Printf("b 地址是 %p, a 地址是 %p \n", b, a)
>>
b 是 [1 2 3 4], a 是 [10 2 3]
b 地址是 0xc000070060, a 地址是 0xc0000480c0
发现 新变量接受append 返回值,都是新的地址(旧变量不扩容,就还是原来的地址)
-
copy() 复制切片
copy( destSlice, srcSlice []T) int
// 将 srcSlice 复制到 destSlice
copy 函数直接操作 原切片 并返回替换的元素个数
slice1 := []int{1, 2, 3, 4, 5}
slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只会复制slice1的前3个元素到slice2中
> slice2 // [1,2,3]
copy(slice1, slice2) // 只会复制slice2的3个元素到slice1的前3个位置
> slice1 // [1 2 3 4 5]
**append 返回新的切片 , copy 直接在目的切片直接修改, 复制 第二个参数切片。(返回替换的元素数量)
-
Go语言从切片中删除元素
这个比较有意思
开头删除
普通做法(移动数据指针)
a = []int{1, 2, 3}
a = a[1:] // 删除开头1个元素
a = a[N:] // 删除开头N个元素
append实现 (后面的数据向开头移动, 不会导致内存空间结构的变化)
a = []int{1, 2, 3}
a = append(a[:0], a[1:]...) // 删除开头1个元素
a = append(a[:0], a[N:]...) // 删除开头N个元素
copy 实现
a = []int{1, 2, 3}
a = a[:copy(a, a[1:])] // 删除开头1个元素
a = a[:copy(a, a[N:])] // 删除开头N个元素
中间删除(对剩余的元素进行一次整体挪动)
a = []int{1, 2, 3, ...}
a = append(a[:i], a[i+1:]...) // 删除中间1个元素
a = append(a[:i], a[i+N:]...) // 删除中间N个元素
a = a[:i+copy(a[i:], a[i+1:])] // 删除中间1个元素
a = a[:i+copy(a[i:], a[i+N:])] // 删除中间N个元素
尾部删除
a = []int{1, 2, 3}
a = a[:len(a)-1] // 删除尾部1个元素
a = a[:len(a)-N] // 删除尾部N个元素
Go语言中删除切片元素的本质是,以被删除元素为分界点,将前后两个部分的内存重新连接起来
-
map
map 是引用类型:
var mapname map[keytype]valuetype
[keytype] 和 valuetype 之间允许有空格
使用和 slice 差不多
声明 可以直接初始化,如果只是声明,那就要用make 来分配地址
map 容量:
make(map[keytype]valuetype, cap)
如果提前知道map 大小,最好写一下 cap, 优化性能
map 遍历:
使用 for range 即可
只使用 值
for _, v := range scene {}
只是用 key
for k := range scene { }
同时 key 和值
for k, v := range scene { }
map元素的删除和清空
删除某个 key
delete(map, 键)
清空 map
没有清空map 的函数,还是重新 make分配内存吧
不用担心垃圾回收的效率,Go语言中的并行垃圾回收效率比写一个清空函数要高效的多
sync.Map
在并发环境中使用的map
pass
-
list(列表)
列表使用 container/list 包来实现,内部的实现原理是双链表,列表能够高效地进行任意位置的元素插入和删除操作
这个以前没看过,需要看一下吧
这个是要是 list 包的使用
列表与切片和 map 不同的是,列表并没有具体元素类型的限制,因此,列表的元素可以是任意类型
初始化列表:
变量名 := list.New()
或:
var 变量名 list.List
大概使用 参考:
http://c.biancheng.net/view/35.html
-
nil:空值/零值
指针、切片、映射、通道、函数和接口的零值则是 nil
数类型为 0
字符串为 ""
布尔为 false
nil 不是关键字或保留字, 你可以 var nil = xx(但是不规范)
两个为 nil 的值不能使用 == 比较, 只能单独和 nil 比较
nil 大小不同,默认是当前类型的大小
-
go 流程控制
pass
-
go 函数
挑几个重点讲一下吧
有疑问加站长微信联系(非本文作者)