coursera_go.md

char3: go中常见的复杂数据结构

Array

属性

  1. 定长, 超出报错
  2. 类型统一
  3. for range循环
  4. 初始化, 数组每个元素值为所选类型的零值
    1
    2
    var x [5]int  // 长度为5的数组
    var y = [5]int{1, 2, 3, 4, 5} // 有初始值的数组

切片

  1. 容量和长度的概念
    1
    2
    len()
    cap()
  2. 初始化
    1
    2
    3
    arr :=[...]string{"a", "b"}
    s1 := arr[0: 1]
    s2 := arr[1]
    使用make初始化数组
    1
    2
    // 数组类型, 数组长度, 数组容量
    sli = make([]int, 10, 20)
  3. 可扩展
  • append: 数组尾部插入数组
    1
    2
    sli = make([]int, 0, 3)
    sli = append(sli, 100)
  1. 切片也为引用类型

Hash 我觉得有点像js的对象

  1. 展示类型为键值对
  2. key唯一
  3. 查找速度快
  4. 在go中体现为map数据结构

map

map是go中的hash结构

  1. 使用make创建一个map
    1
    2
    3
    4
    5
    6
    7
    // 1
    var idMap map[string]int
    // 2
    idMap = make(map([string]int))
    // 3
    idMap := map[string]int {
    "warning" : 1}
  2. 修改和新增: 和js对象一样
  3. 删除一个key-value——-delete(map, key)
  4. 判定一个key是否在对象中: 使用双赋值map
    1
    2
    // p是一个布尔值, true证明joe在idMap中,反之, 则不在
    id, p := idMap["joe"]
  5. len()获取map的key数量
  6. 遍历使用for-range

Structs

  1. 类似class但是并不是class, 有自己的属性, 自己的方法, 也可以根据这个结构体构造实例
  2. 定义:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type struct Person {
    name string
    addr string
    phone string
    }

    var p1 Person
    // 访问变量
    p1.name = "joe"
    x = p1.addr
  3. 初始化一个结构体
    1
    2
    3
    4
    // 1. use New keyword
    p1 := new(Person)
    // 2. 使用构造函数
    p2 := Person(name: "joe", addr: "s", phone: "yooooh")

charper4: RPC JSON file_io

  1. go提供了rpc协议的一些基础包
  • net/http : web communication protocol
  • net : TCP/IP and socket programming
  1. json: json比struct更为标准化和通用化

    1
    2
    // go struct
    p1 := Person(name: "joe", addr: "a st.")
  2. json的特性

  • 支持所有编码方式
  • 有好的可读性
  • 可以混如不同的类型
  • 使用json.marshalling来对struct与json进行格式转换
  • json.marshal额, 就是反过来
  1. file
  • 访问方式为线性访问, 而不是随机访问
  • 占用线程
  • 分片操作

基本操作步骤:

  • 打开文件
  • 读取byte
  • 写入byte
  • 关闭文件(解锁)
  • seek

文件io涉及的包: ioutil

1
2
3
4
5
6
7
8
// 文件读取操作
// 文件过大时 ,不可使用readfile, 因为会把内存挤爆
data, e := ioutil.ReadFile("test.txt")

// 文件写操作
data = "Hello world"
// 这里最后一个参数是, 新文件的权限, 参照linux文件系统
err := ioutil.WriteFile("output.txt", data, 0777)
  1. io
  • os.Open() 打开一个文件, 返回一个文件的描述
  • os.Close() 关闭一个文件
  • os.Read() 读取一个文件, 以字节流的形式, 可以控制流的大小
  • os.Write() 写一个文件, 以字节流的形式, 可以控制流的大小
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // opening and reading
    f, err := os.Open("test.txt")
    barr := make([]byte, 10)
    nb, err := f.Read(barr)
    f.Close()
    // creating and writing
    f, err := os.Create("output.txt")
    barr := []byte{1, 2, 3, 4}
    nb, err := f.Write(barr)
    nb, err := f.WriteString("Hi")

function: 最主要的是函数名和注释吧, 给足够的信息让后来人尽可能的读懂

  1. 定义: func

    1
    2
    3
    4
    func add(a, b int) int {
    result := a + b
    return result
    }
  2. 在go中, 传入的参数都会被复制一份, 与原数据独立开来。

  • 但是我们需要的话, 我们可以通过指针类型(*, &)传入, 然后对指针地址内的值进行操作, 这样会触发函数内部修改外部函数的效应.
1
2
3
4
5
6
7
8
9
func foo(y *int) {
*y = *y + 1
}

func main() {
X := 2
foo(&x)
fmt.Print(x) // 3
}
  • 入参为数组或者slice,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 数组
    func foo(x [3]int) int {
    return x[0]
    }

    func main() {
    a := [3]int{1, 2, 3}
    fmt.Print(foo(a))
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    func foo(sli []int) {
    sli[0] = sli[0] + 1
    }

    func main() {
    a := []int{1, 2, 3}
    foo(a)
    fmt.Print(a)
    }
  1. 复杂度:

    • 超大函数分解: 要求语义化和可读性
    • 控制流复杂度: 减少嵌套的控制流, 切分函数, 函数分层可以让你的控制流复杂度降低
  2. 写好函数

    • 可读性
  3. 函数组织方式

    • 函数名要有意义
    • 单一职责
    • 更少的参数

函数类型

  1. first-class funciton

    • 函数在go中其实也是一种类型
    • 函数可以赋值给变量, 这样可以以变量名调用函数
    • 可以当作变量在函数中充当入参和出参
  2. go中, 也存在类似js的变量作用域, 叫词法作用域

  3. 闭包

  • function + its environment
  • 当函数完成后, 还可以访问他的environment的能力(数据持久化)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 函数赋值
    var funcVar func(int) int
    func incFn(x int) int{
    return x + 1
    }

    func main() {
    funcVar = incFn
    fmt.Print(funcVar(1))
    }
    1
    2
    3
    4
    // 函数作为参数
    func applyIt (afunc func (int) int, val int) int{
    return afunc(val)
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 匿名函数
    func applyIt (afunc func (int) int, val int) int{
    return afunc(val)
    }
    func main() {
    v := applyIt(func (x int) int {
    return x + 1
    }, 2)
    fmt.Print(v)
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 返回一个函数
func MakeDisOrigin(o_x, o_y, float64) func (float64, float64) float64 {
fn := func (x, y float64) float64 {
return math.Sqrt(math.Pow(x - o_x, 2) + math.Pow(y - o_y, 2))
}

return fn
}

func main() {
Dist1 := MakeDisOrigin(0, 0)
Dist2 := MakeDisOrigin(2, 2)
fmt.Print(Dist1(3, 3))
fmt.Print(Dist2(4, 4))
}

可变函数参数

  1. 使用…收集不确定数量的函数参数, 然后把这个参数当作slice处理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    func getMax(vals ...int) int {
    maxV := -1
    for _, v := range vals {
    if v > maxV {
    maxV = v
    }
    }

    return maxV
    }
  2. 使用…展开slice

    1
    2
    3
    4
    5
    func main() {
    fmt.PrintIn(getMax(1, 2, 3, 4, 5))
    vslice := []int{1, 2, 3, 4, 5}
    fmt.PrintIn(getMax(vslice...))
    }

异步函数-defer

  1. 普通模式, 在同步代码调用后, 执行def异步代码
    1
    2
    3
    4
    func testDef() {
    defer fmt.Print("Defer")
    fmt.Print("Hello")
    }
  2. 参数判定
    在go中的def后面执行的代码段相当于一个匿名函数.所以,对于原语类型, 则为其分配一个新的内存控件,对于引用类型, 则通过引用的方式使用.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 原语类型
    func main() {
    i := 1
    defer fmt.Println(i + 1) // 2
    i = i + 3 // 4
    fmt.Println(i)
    fmt.Println("Hello")
    }
    // 4
    // Hello
    // 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 引用类型
func main() {
testDefArgs()
}

func testDefArgs () {
sli1 := []string{"1", "2", "3"}
defer testDef(sli1)
sli1[1] = "123"
fmt.Println("__end__")
}

func testDef(s []string) {
fmt.Println(s)
}
// __end__
// [1, 123, 3]

Class and encapsulation

  1. class的基本概念
    • 模板和实例的关系
    • 私有变量的概念
  2. 链接变量和func
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type MyInt int
    func (mi MyInt) Double () int {
    return int(mi * 2)
    }

    func main() {
    v := MyInt(3)
    // 注意, 这里触发了隐式传值
    fmt.Println(v.Double())
    }
  3. 自定义Struct
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    type Point struct {
    x float64
    y float64
    }

    func (p Point) DistToOrig () {
    t := math.Pow(p.x, 2) + math.Pow(p.y, 2)
    return math.Sqrt(x)
    }

    func main() {
    p1 := Point(3, 4)
    fmt.Println(p1.DisToOrig())
    }

封装

  1. 封装公共方法/也可以说是go中的模块化机制(外部可调用方法, 方法内部存私有变量)
    (类似js中的export/import)

如果方法或者变量使用大写, 那么会被go自动导出.

1
2
3
4
5
6
7
// data.go
package data // 定义data包

var x int = 1
func PrintX() {
fmt.Println(x)
}
1
2
3
4
5
package main
import "data"
func main() {
data.PrintX() // 此时data中封装了PrintX方法, 并且在data中的x是私有变量
}
  1. 把变化封装进struct中
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // data.go file
    package data

    type Point struct {
    x float64
    y float64
    }

    func (p *Point) InitMe(xn, yn float64) {
    p.x = xn
    p.y = yn
    }

    func (p *Point) Scale(v float64) {
    p.x = p.x * v
    p.y = p.y * v
    }

    func (p *Point) PrintMe() {
    fmt.Println(p.x, p.y)
    }

    // and we can use it
    // go.go file

    func main() {
    var p data.Point
    p.Init(3, 4)
    p.Scale(2)
    p.PrintMe()
    }

通过这样的方式, 模拟了class的思想,并且把数据隐藏了起来, 只能通过方法访问和修改.

记住, 在你往struct上挂方法的时候, 一定要使用*StructName。因为我们需要的是struct本身, 而不是他的copy。如果不传入指针, 那么对其的修改实际上是修改copy而不是struct.

class抽象相关概念

  1. 多态
    一个函数在不同的上下文中, 起不同的作用。例如,计算面积的函数

    1
    2
    3
    4
    5
    func Area() float64
    // Rect
    area = base * height
    // Triangle
    area = 0.5 * base * height
  2. 继承
    go没有继承, 取而代之是组合.

  3. 重写
    对父类定义的方法重写.

接口类型

定义

  • 接口是一系列方法的集合
  • 包含接口名称,入参, 返回值
  • 接口是实现而不是接口定义

满足一个接口的条件是

  • 包含接口类型所定义的所有接口, 并且入参和出餐符合接口定义
  • 除此之外, 可以有别的方法(接口)

go通过接口类型来完成多态.

举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type Shape2D interface {
Area() float64
Perimeter() float64
}

type Triangle {}
func (t *Triangle) Area() float64 {

}

func (t *Triangle) Perimeter() float64 {

}

// 这样我们可以说Triangle满足Shape2D的接口类型

接口类型和组合比较

复杂Struct定义了data和方法.

接口只对我们需要的东西暴露出来.

举个例子: 接口类型的定义和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import "fmt"

type Speaker interface {
Speak()
}

type Dog struct {
name string
}

func (d Dog) Speak() {
fmt.Println(d.name)
}

func main() {
// 实例化接口和struct
var s1 Speaker
var d1 = Dog{"dog"}
// 接口过滤Dog中不需要的方法
s1 = d1
fmt.Println(s1)
s1.Speak()
}

使用*调用接口(此时还未实例化接口)

就是我以struct的指针赋予接口实例, 然后使用接口实例调用对应接口.此时, 在接口中,我们值为nil(零值)

1
2
3
4
5
6
7
8
9
10
11
12
13
func (d *Dog) Speak() {
if d == nil { // 当我们没有得到实例后的数据时, 此时d为Struct的零值, 也就是nil
fmt.Println("<noise>")
} else {
fmt.Println(d.name)
}
}

var s1 Speaker
var d1 *Dog

s1 = d1
s1.Speak() // <noise>

使用接口

  1. 函数可变入参类型
    举个栗子:我有个函数fn, 这个函数可以传入x类型也可以传入Y类型
  • 定义一个接口类型z
  • 该接口类型满足fn(x)
  • 该接口类型满足fn(y)
  • 实例化并挂载fn1 在fn1中去判定入参类型然后做什么事
  1. 扩展方法
    1
    2
    3
    4
    5
    6
    7
    // 明确一下, 接口类型也是类型, 我们可以通过这个方式往接口扩展原有方法
    func FitInYard(s Shape2D) bool {
    if(s.Area() > 100 && s.Perimeter() > 100) {
    return true
    }
    return false
    }

空接口

  1. 空接口类型不包含任何方法
  2. 空的接口类型满足任何Struct
  3. 使用空接口扩展的方法可以接受任意类型的参数
    举个栗子
    1
    2
    3
    func PrintMe(val interface{}) { // 这样可以让这个函数接受任何类型的参数
    fmt.Println(val)
    }

类型断言

在上面, 我们提出了, 如果我们使用空接口作为类型, 那么,我们可以让类型系统失效.可是我们想控制这个失效的范围, 那么就出现了类型断言.

1
2
3
4
5
6
7
8
9
10
func DrawShape(s Shape2D) bool {
rect, ok := s.(Rectangle)
if ok {
DrawRect(rect)
}
tri, ok := s.(Triangle)
if ok {
DrawRect(tri)
}
}
1
2
3
4
5
6
7
8
9
// 使用switch优化类型断言
func DrawShape(s Shape2D) bool {
switch:= sh := s.(type) {
case Rectangle:
DrawRect(sh)
case Triangle:
DrawRect(sh)
}
}

错误处理

go程序中,如果出现错误时, 会选择返回error接口类型来指示错误.

如果没有错误则返回nil指示一切正常.

1
2
3
type error interface{
Error() string
}

常见错误处理

1
2
3
4
5
6
7
func readFile() {
f, err := os.Open("/harris/test.txt")
if err != nil {
fmt.Println(err)
return
}
}