go 学习笔记

天空蓝雨 · · 330 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

这篇文章就总结一下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 函数

挑几个重点讲一下吧


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:天空蓝雨

查看原文:go 学习笔记

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

330 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传