Golang学习

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

Golang

Go语言设计初衷

  • 针对其他语言的痛点进行设计
  • 并加入并发编程
  • 为大数据,微服务,并发而生的通用编程语言

Go语言与转型

  • 项目转型首选语言
  • 软件工程师转型、添加技术栈的首选语言
  • 这是一门为转型量身定制的课程

课程内容

  • 基本语法
    • 变量
    • 选择、循环
    • 指针、数组、容器
  • 面向接口
    • 结构体
    • duck typing的概念
    • 组合的思想
  • 函数式编程
    • 闭包的概念
    • 多样的例题
  • 工程化
    • 资源管理,错误处理
    • 测试和文档
    • 性能调优
  • 并发编程
    • gorutine和channel
    • 理解调度器
    • 多样的例题
image.png

实战项目的实现步骤

单任务版 ——> 并发版 ——> 分布式

2 Go语言基本语法

2-1 变量定义

  • 变量类型写在变量名之后
  • 编译器可推测变量类型
  • 没有char,只有rune(32位)
  • 原生支持复数类型

使用var 关键字

  • var a, b, c bool
  • var s1, s2 string = "Hello", "world"
  • 可放在函数内,或直接放在包内
  • 使用var()集中定义变量
package main

import "fmt"

// 变量名在变量类型之前,且有初始值
func variableZeroValue() {
    var a int
    var s string
    fmt.Printf("%d %q\n",a, s)
}

// 变量可以同时定义多个,定义的变量必须使用
func variableInitialValue() {
    var a, b int = 3, 4
    var s string = "abv"
    fmt.Println( a, b, s)
}

// 可以省略type,进行自动识别
func variableTypeDeduction() {
    var a, b, c, d  = 3, 4, true, "def"
    fmt.Println(a, b, c, d)
}

// := 为定义并赋值,只能在函数内使用
func variableShorter() {
    a, b, c, d := 3, 4, true, "Def"
    b = 5
    fmt.Println(a, b, c, d)
}

func main() {
    fmt.Println("Hello world")
    variableZeroValue()
    variableInitialValue()
    variableTypeDeduction()
    variableShorter()
}

2-2 内建变量类型

  • bool, string
  • (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr(指针)
  • byte, rune(Go语言的字符型)
  • float32, float64, complex64, complex128(复数)
// 复数计算;验证欧拉公式
func euler(){
    c := 3 + 4i
    fmt.Println(cmplx.Abs(c)) // 5
    fmt.Println(cmplx.Pow(math.E, 1i * math.Pi) + 1) //(0+1.2246467991473515e-16i) 因为浮点数存在误差,所以不等于0
    fmt.Printf("%3f", cmplx.Pow(math.E, 1i * math.Pi) + 1) //(0.000000+0.000000i)

}
// 强制类型转换
func triangle() {
    var a, b int = 3, 4
    var c int
    // math.Sqrt要求float类型,返回值也是float类型
    c = int(math.Sqrt(float64(a*a + b*b)))
    fmt.Println(c) // 5
}

常量与枚举

常量的定义

func consts() {
    const filename = "abc.txt" // 定义在方法体外也可以
    const a, b = 3, 4 // const数值可作为各种类型使用
    var c int
    c = int(math.Sqrt(a*a + b*b))
    fmt.Println(filename, c) //abc.txt 5
}

枚举类型

func enums() {
    // 用const块来定义枚举, iota为自增关键字
    const (
        cpp = iota
        _
        python
        golang
        javascript
    )
    const (
        b = 1 << (10 * iota)
        kb
        mb
        gb
        tb
        pb
    )
    fmt.Println(cpp, javascript, python, golang) // 0 4 2 3
    fmt.Println(b, kb, mb, gb, tb, pb) // 1 1024 1048576 1073741824 1099511627776 1125899906842624
}

2-4 条件语句

if

  • if 的条件里可以赋值
  • if 的条件里赋值的变量作用域就在这个if语句里
const filename = "abc.txt"
    //contents, err := ioutil.ReadFile(filename)
    //if err != nil {
    //  fmt.Println(err)
    //} else {
    //  fmt.Printf("%s\n", contents)
    //}
    if contents, err := ioutil.ReadFile(filename); err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("%s\n", contents)
    }

swith

  • swith 会自动break, 除非使用fallthrough
func eval(a, b int, op string) int {
    var result int
    switch op {
    case "+":
        result = a + b
    case "-":
        result = a - b
    case "*":
        result = a * b
    case "/":
        result = a / b
    default:
        panic("unsupported operation: " + op)
    }
    return result
}
func grade(score int) string {
    g := ""
    switch {
    case score < 0 || score > 100:
        panic(fmt.Sprintf(
            "Wrong score: %d", score))
    case score < 60:
        g = "F"
    case score < 80:
        g = "C"
    case score < 90:
        g = "B"
    case score <= 100:
        g = "A"
    }
    return g
}

2-5 循环

for

  • for的条件里不需要括号
  • for的条件里可以省略初始条件,结束条件,递增表达式
sum := 0
for i := 1; i <= 100; i++ {
    sum +=
}
package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
)
// 整数转成二进制,省略初始条件,相当于while
func convertToBin(n int) string {
    result := ""
    for ; n >0; n /=2 {
        lsb := n % 2
        result = strconv.Itoa(lsb) + result
    }
    return result
}
// 省略初始条件和递增条件,相当于while
func printFile(filename string) {
    file, err := os.Open(filename)
    if err != nil {
        panic(err)
    }
    scanner := bufio.NewScanner(file)

    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}
// 条件都省略,相当于死循环
func forever() {
    for {
        fmt.Println("abc")
    }
}
func main() {
    fmt.Println(
        convertToBin(5), // 101
        convertToBin(13), // 1101
        )
    printFile("abc.txt")
    forever()
}

2-6 函数

只返回一个值

package main

import "fmt"

func eval(a, b int, op string) int {
    switch op {
    case "+":
        return a + b
    case "-":
        return a - b
    case "*":
        return a * b
    case "/":
        return a / b
    default:
        panic("unsupported operation: " + op)
    }
}
func main() {
    fmt.Println(eval(3, 4, "*")) // 12
}

返回多个值

// 13 / 3 = 4 ... 1
func div(a, b int) (int, int) {
    return a / b, a % b
}

函数的参数也可以是函数

func apply(op func(int, int) int, a, b int) int{
    fmt.Printf("Calling %s with %d, %d\n", runtime.FuncForPC(reflect.ValueOf(op).Pointer()).Name(), a, b)
    return op(a, b)
}

2-7 指针

指针不能运算

var a int = 2
var pa *int = &a
*pa = 3
fmt.Println(a)

值传递和引用传递

  • Go 语言只有值传递一种方式
    • var a int --> func f(a int) 直接将a拷贝过去,函数体内对a 的改变不会影响变量a
    • var a int --> func f(pa *int)将&a 的地址进行拷贝
    • var cache Cache --> func f(cache Cache)


      image.png
func swap(a, b *int) {
    *b, *a = *a, *b
}
// a, b := 3, 4
// swap(&a, &b) //4, 3

3 内建容器

3-1 数组

  • 数量写在类型的前面
  • 数组的遍历
    • 可通过_省略变量
    • 不仅range,任何地方都可以通过_省略变量
package main

import "fmt"

func main() {
    // 数组的定义
    var arr1 [5]int // [0 0 0 0 0]
    arr2 := [3]int{1, 3, 5} // [1 3 5]
    arr3 := [...]int{2, 4, 6, 8, 10} // [2 4 6 8 10]
    var grid [4][5]int // [[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 0]]
    fmt.Println(arr1, arr2, arr3)
    fmt.Println(grid)
    
    // 遍历 方法1
    for i := 0; i<len(arr3); i++ {
        fmt.Println(arr3[i])
    }
    // 遍历 方法2
    for i := range arr3 {
        fmt.Println(arr3[i])
    }
    // 遍历 方法3 能同时返回下标
    for i, v := range arr3 {
        fmt.Println(i, v)
    }
}

数组是值类型

  • [10]int 和 [20]int是不同类型
  • 调用func f(arr [10]int),会拷贝数组
  • Go语言中一般不直接使用数组(切片)

3-2 slice(切片)的概念

arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s := arr[2:6] // s的值为[2 3 4 5]
s[0] = 10 //[0 1 10 3 4 5 6 7]
  • slice本身没有数据,是对底层array的一个view

3-3 切片的操作

arr := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := arr[2:6] // [2 3 4 5]
s2 := s1[3:5] // [5 6]
s3 := append(s2, 10)
// s4 and s5 no longer view arr.
s4 := append(s3, 11)
s5 := append(s4, 12) // [5 6 10 11 12]
// arr = [0 1 2 3 4 5 6 10]
image.png

image.png
  • slice可以向后扩展,不可以向前扩展
  • s[i]不可以超越len(s),向后扩展不可以超越底层数组cap(s)
  • 添加元素时如果超越cap,系统会重新分配更大的底层数组
  • 由于值传递的关系,必须接收append的返回值
  • s = append(s, val)
package main

import "fmt"

// slice 的一些操作

func printSlice(s []int) {
    fmt.Printf("%v, len=%d, cap=%d\n", s, len(s), cap(s))
}
func main() {
    fmt.Println("Creating slice")
    var s []int  // Zero value for slice is nil

    for i := 0; i < 100; i++ {
        printSlice(s)
        s = append(s, 2*I+1)
    }
    //fmt.Println(s)

    s1 := []int{2, 4, 6, 8}
    printSlice(s1) // [2 4 6 8], len=4, cap=4

    s2 := make([]int, 16)
    s3 := make([]int, 10, 32)
    printSlice(s2) // [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16
    printSlice(s3) //[0 0 0 0 0 0 0 0 0 0], len=10, cap=32

    fmt.Println("Copying slice")
    copy(s2, s1)
    printSlice(s2) // [2 4 6 8 0 0 0 0 0 0 0 0 0 0 0 0], len=16, cap=16

    fmt.Println("Deleting elements from slice")
    s2 = append(s2[:3], s2[4:]...)
    printSlice(s2) //[2 4 6 0 0 0 0 0 0 0 0 0 0 0 0], len=15, cap=16

    fmt.Println("Popping from front")
    front := s2[0]
    s2 = s2[1:]
    fmt.Println(front) // 2 

    fmt.Println("Popping from back")
    tail := s2[len(s2) - 1]
    s2 = s2[:len(s2) - 1]
    fmt.Println(tail) //  0
    printSlice(s2) // [4 6 0 0 0 0 0 0 0 0 0 0 0], len=13, cap=15


}

3-4 Map

  • map[K]V, map[K1]map[K2]V
m := map[string]string {
    "name": "ccmouse",
    "course": "golang",
    "site": "imooc",
    "quality": "notbad",
}

Map 的操作

  • 创建: make(map[string]int)
  • 获取元素:m[key]
  • key不存在时,获取Value类型的初始值
  • 用value, ok := m[key]来判断是否存在key
  • 用delete删除一个key
package main

import "fmt"

func main() {

    //map 定义
    m := map[string]string {
        "name": "ccmouse",
        "course": "golang",
        "site": "imooc",
        "quality": "notbad",
    }
    // 定义空map
    m2 := make(map[string]int) // m2 == empty map
    var m3 map[string]int // m3 == nil

    fmt.Println(m) // map[course:golang name:ccmouse quality:notbad site:imooc]
    fmt.Println(m2) // map[]
    fmt.Println(m3) // map[]

    fmt.Println("Traversing map")
    for k, v := range m {
        fmt.Println(k, v)
    }

    fmt.Println("Getting values")
    courseName, ok := m["course"]
    fmt.Println(courseName, ok)
    if causeName, ok := m["cause"]; ok {
        fmt.Println(causeName)
    } else {
        fmt.Println("Key does not exist")
    }

    fmt.Println("Deleting values")
    name, ok := m["name"]
    fmt.Println(name, ok) // ccmouse true

    delete(m, "name")
    name, ok = m["name"]
    fmt.Println(name, ok) //  false
}

Map的遍历

  • 使用range遍历key,或者遍历key,value对
  • 不保证遍历顺序,如需顺序,需手动对key排序

Map的key

  • Map使用哈希表,必须可以比较相等
  • 除了slice,map,function的内建类型都可以作为key
  • Struct类型不包括上述字段,也可以作为key

3-5 Map的例题

例:寻找最长不含有重复字符的字串

  • abcabcbb -> abc
  • bbbbb -> b
  • pwwkew -> wke

对于每一个字母x

  • lastOccurred[x]不存在,或者< start -> 无需操作
  • lastOccurred[x] >= start -> 更新start
  • 更新lastOccurred[x],更新maxLength
package main

import "fmt"

func lengthOfNonRepeatingSubStr (s string) int {
    lastOccurred := make(map[byte]int)
    start := 0
    maxLength := 0
    for i, ch := range []byte(s) {
        if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
            start = lastI + 1
        }
        if i - start + 1 > maxLength {
            maxLength = i - start + 1
        }
        lastOccurred[ch] = I
    }
    return maxLength
}
func main() {
    fmt.Println(lengthOfNonRepeatingSubStr("abcabcbb"))
    fmt.Println(lengthOfNonRepeatingSubStr("bbbb"))
    fmt.Println(lengthOfNonRepeatingSubStr("pwwkew"))

}

3-6 字符和字符串处理

rune相当于go的char

  • 使用range遍历pos,rune对
  • 使用utf8.RuneCountInString获得字符数量
  • 使用len获得字节长度
  • 使用[]byte获得字节

例:寻找最长不含有重复字符的字串

package main

import "fmt"

func lengthOfNonRepeatingSubStr (s string) int {
    lastOccurred := make(map[rune]int)
    start := 0
    maxLength := 0
    for i, ch := range []rune(s) {
        if lastI, ok := lastOccurred[ch]; ok && lastI >= start {
            start = lastI + 1
        }
        if i - start + 1 > maxLength {
            maxLength = i - start + 1
        }
        lastOccurred[ch] = I
    }
    return maxLength
}
func main() {
    fmt.Println(lengthOfNonRepeatingSubStr("abcabcbb")) //3
    fmt.Println(lengthOfNonRepeatingSubStr("bbbb")) // 1
    fmt.Println(lengthOfNonRepeatingSubStr("pwwkew")) // 3
    fmt.Println(lengthOfNonRepeatingSubStr("这里是慕课网")) // 6
    fmt.Println(lengthOfNonRepeatingSubStr("一二三二一")) // 3
}

其他字符串操作

  • fields, split, join
  • contains, index
  • toLower, toUpper
  • trim, trimRight, trimLeft

4 面向对象

  • go语言仅支持封装,不支持继承和多态
  • go语言没有class,只用struct

4-1 结构体和方法

结构的定义

type TreeNode struct {
    Left, Right *TreeNode
    Value int
}

结构的创建

package main

import "fmt"

type treeNode struct {
    value int
    left, right *treeNode
}

// 工厂函数
func createNode(value int) *treeNode {
    return &treeNode{value: value}
}
func main() {
    var root treeNode
    fmt.Println(root) // {0 <nil> <nil>}

    root = treeNode{value: 3}
    root.left = &treeNode{}
    root.right = &treeNode{5, nil, nil}
    root.right.left = new(treeNode)
    root.left.right = createNode(2)

    nodes := []treeNode {
        {value: 3},
        {},
        {6, nil, &root},
    }
    fmt.Println(nodes) // [{3 <nil> <nil>} {0 <nil> <nil>} {6 <nil> 0xc00000c060}]
}
  • 使用自定义工厂函数
  • 注意返回了局部变量的地址!

结构创建在堆上还是栈上

由go语言的运行环境和编译器决定,不需要了解
为结构定义方法

func (node treeNode) print() {
    fmt.Println(node.value)
}
  • 显示定义和命名方法接收者
func (node *treeNode) setValue(value int) {
    node.value = value
}
  • 只有使用指针才可以改变结构内容
  • nil指针也可以调用方法!

树的遍历

package main

import (
    "fmt"
)

type treeNode struct {
    value int
    left, right *treeNode
}

func (node treeNode) print() {
    fmt.Println(node.value)
}

func (node *treeNode) setValue(value int) {
    if node == nil {
        fmt.Println("Setting value to nil " + "node. Ignored.")
        return
    }
    node.value = value
}

// 中序遍历
func (node *treeNode) traverse() {
    if node == nil {
        return
    }
    node.left.traverse()
    node.print()
    node.right.traverse()
}
// 工厂函数
func createNode(value int) *treeNode {
    return &treeNode{value: value}
}
func main() {
    var root treeNode
    fmt.Println(root) // {0 <nil> <nil>}

    root = treeNode{value: 3}
    root.left = &treeNode{}
    root.right = &treeNode{5, nil, nil}
    root.right.left = new(treeNode)
    root.left.right = createNode(2)
    root.right.left.setValue(4)

    nodes := []treeNode {
        {value: 3},
        {},
        {6, nil, &root},
    }
    fmt.Println(nodes) // [{3 <nil> <nil>} {0 <nil> <nil>} {6 <nil> 0xc00000c060}]
    root.traverse() // 0 2 3 4 5
}

值接收者 vs 指针接收者

  • 要改变内容必须使用指针接收者
  • 结构过大也考虑使用指针接收者
  • 一致性:如果有指针接收者,最好都是指针接收者
  • 值接收者是go语言特有
  • 值/指针接收者均可接受值/指针

4-2 包和封装

封装

  • 名字一般使用CamelCase
  • 首字母大写:public
  • 首字母小写:private

  • 每个目标一个包
  • main包包含可执行入口
  • 为结构定义的方法必须放在同一个包内
  • 可以是不同的文件

4-3 扩展已有类型

如何扩充系统类型或者别人的类型

  • 定义别名
  • 使用组合
package main

// 自己写后序遍历
import (
    "fmt"
    "learngo/tree"
)

type myTreeNode struct {
    node *tree.TreeNode
}

func (myNode *myTreeNode) postOrder() {
    if myNode == nil || myNode.node == nil {
        return
    }
    left := myTreeNode{myNode.node.Left}
    right := myTreeNode{myNode.node.Right}

    left.postOrder()
    right.postOrder()
    myNode.node.Print()
}

func main() {
    var root tree.TreeNode
    fmt.Println(root) // {0 <nil> <nil>}

    root = tree.TreeNode{Value: 3}
    root.Left = &tree.TreeNode{}
    root.Right = &tree.TreeNode{5, nil, nil}
    root.Right.Left = new(tree.TreeNode)
    root.Left.Right = tree.CreateNode(2)
    root.Right.Left.SetValue(4)

    nodes := []tree.TreeNode{
        {Value: 3},
        {},
        {6, nil, &root},
    }
    fmt.Println(nodes) // [{3 <nil> <nil>} {0 <nil> <nil>} {6 <nil> 0xc00000c060}]
    root.Traverse() // 0 2 3 4 5
    fmt.Println()
    myRoot := myTreeNode{&root}
    myRoot.postOrder()
    fmt.Println() // 2 0 4 5 3
}
package queue

type Queue []int

func (q *Queue) Push(v int) {
    *q = append(*q, v)
}

func (q *Queue) Pop() int {
    head := (*q)[0]
    *q = (*q)[1:]
    return head
}

func (q *Queue) IsEmpty() bool {
    return len(*q) == 0
}

package main

import (
    "fmt"
    "learngo/queue"
)

func main() {
    q := queue.Queue{1}
    q.Push(2)
    q.Push(3)
    fmt.Println(q.Pop()) // 1
    fmt.Println(q.Pop()) // 2
    fmt.Println(q.IsEmpty()) // false
    fmt.Println(q.Pop()) // 3
    fmt.Println(q.IsEmpty()) // true
}

4-4 GOPATH 环境变量

5 接口

Go语言的面向对象只支持封装,Go语言使用接口完成继承和多态

type Traversal interface {
    Traverse()
}

func main() {
    traversal := getTraversal()
    traversal.Traverse()
}

5-1 duck typing

  • “像鸭子走路,像鸭子叫(长得像鸭子),那么就是鸭子”
  • 描述事物的外部行为而非内部结构
  • 严格说GO属于结构化类型系统,类似duck typing

许多语言都支持Ducking Typing,通常Duck Typing是动态编程语言用来实现多态的一种方式。
补充:多态

python中的Duck Typing

def download(fetcher):
    return fetcher.get("http://xxx");

有一个download函数,传过来一个fetcher参数,fetcher是可以获取URL链接的资源的。这个fetcher就是一个Ducking
Typing的对象,使用者约定好这个fetcher会有一个get函数就可以了。
只有运行时才知道传入的fetcher有没有get函数。

C++中的Duck Typing

C++不是动态语言,但是它能支持Ducking
Typing,通过模板的方法来实现。 在编译时,才知道传入的fetcher有没有get方法。

template <class F>
string download(const F& fetcher) {
   return fetcher.get("http://xxxx")
}

Java中的类似代码
Java 没有Ducking Typing,它只有类似的代码。Java的ducking typing:

<F extends FetcherInterface>
String download(F fetcher) {
   return fetcher.get("http://xxxx")
}

如果download函数只依赖fetcher的get方法,而FetcherInterface接口必须实现除get方法以外,还有其他方法,那么也要一一实现,非常不灵活。

Go中的Duck Typing

在Java的Duck Typing类似代码中,如果fetcher参数需要同时实现两个或两个以上的接口方法时,Java是没有办法做到的。但Go语言可以做到。

type Fetcher interface {
    Get(url string) string
}

type Saver interface {
    Save(content string)
}

type FetcherAndSaver interface {
    Fetcher
    Saver
}

func download(f fetcher) string {
    return f.Get("http://xxxx")
}

func save(f saver) {
    f.Save("some thing")
}

func downloadAndSave(f FetcherAndSaver) {
    content := f.Get("http://xxxx")
    f.Save(content)
}

// 实现者
type MyFetcherAndSaver struct {
}
func (f MyFetcherAndSaver) Get(url string) string {
    ...
}

func (f MyFetcherAndSaver) Save(content string) {
    ...
}

func main() {
    f := MyFetcherAndSaver{}
    download(f)
    save(f)
    downloadAndSave(f)
}

这里定义了三个接口,只要有Get方法的就是Fetcher,只要有Save方法的就是Saver,同时有Get方法和Save方法的就是FetcherAndSaver。
实现者MyFetcher并不需要声明它实现了哪些接口,只要它有相关接口的所定义的方法,那么它的实例就能作为Fetcher接口来使用,又能作为Saver接口来使用,也能作为FetcherAndSaver接口来使用。

Go的实现方法相对比较灵活,又不失类型检查。总的来说,有以下特点:

  • 即能同时实现多个接口
  • 又具有python,c++的Duck Typing灵活性
  • 又具有java的类型检查

5-2 接口的定义与实现

接口的定义

download(使用者)——> retriever(实现者)

  • 接口由使用者定义
// retriever/mooc/moocRetriver.go
package mooc

type Retriever struct {
    Contents string
}

func (r Retriever) Get(url string) string {
    return r.Contents
}

package real

import (
    "net/http"
    "net/http/httputil"
    "time"
)

type Retriever struct {
    UserAgent string
    TimeOut time.Duration
}

func (r Retriever) Get(url string) string  {
    resp, err :=  http.Get(url)
    if err != nil {
        panic(err)
    }

    result, err := httputil.DumpResponse(
        resp, true)

    resp.Body.Close()
    return string(result)
}
package main

import (
    "fmt"
    "learngo/retriever/real"
)

type Retriever interface {
    Get(url string) string
}
func download(r Retriever) string {
    return r.Get("http://www.imooc.com")
}
func main() {
    var r Retriever
    //r = mooc.Retriever{"this is a fake imooc.com"}
    r = real.Retriever{}
    fmt.Println(download(r))

}

接口的实现

  • 接口的实现是隐式的
  • 只需要实现里面的方法

5-3 接口的值类型

func main() {
    var r Retriever
    r = mooc.Retriever{"this is a fake imooc.com"}
    fmt.Printf("%T %v\n",r,r) // mooc.Retriever {this is a fake imooc.com}

    r = real.Retriever{}
    fmt.Printf("%T %v\n", r, r) // real.Retriever { 0s}
    //fmt.Println(download(r))

}

接口变量里面有什么

接口变量中包括:

  • 实现者的类型
  • 实现者的值,或者说是实现者的指针,指向实现者

查看接口变量

  • 表示任何类型:interface{}
  • type assertion
  • type switch
    var r Retriever

r 是什么,r 的类型判断

func main() {
    var r Retriever
    r = mooc.Retriever{"this is a fake imooc.com"}
    fmt.Printf("%T %v\n",r,r) // mooc.Retriever {this is a fake imooc.com}

    r = real.Retriever{}
    fmt.Printf("%T %v\n", r, r) // real.Retriever { 0s}
    //fmt.Println(download(r))
}

将Retriever定义为指针接收者

func (r *Retriever) Get(url string) string 
    r = &real.Retriever{
        UserAgent: "Mozilla/5.0",
        TimeOut: time.Minute,
    }
    fmt.Printf("%T %v\n", r, r) // *real.Retriever &{Mozilla/5.0 1m0s}

如何知道r的类型
方法一:switch

package main

import (
    "fmt"
    "learngo/retriever/mooc"
    "learngo/retriever/real"
    "time"
)

type Retriever interface {
    Get(url string) string
}
func download(r Retriever) string {
    return r.Get("http://www.imooc.com")
}

func inspect(r Retriever) {
    fmt.Printf("%T %v\n",r,r)
    switch v := r.(type) {
    case mooc.Retriever:
        fmt.Println("Contents:", v.Contents)
    case *real.Retriever:
        fmt.Println("UserAgent:", v.UserAgent)


    }
}
func main() {
    var r Retriever
    r = mooc.Retriever{"this is a fake imooc.com"}
    inspect(r) //mooc.Retriever {this is a fake imooc.com} Contents: this is a fake imooc.com
    r = &real.Retriever{
        UserAgent: "Mozilla/5.0",
        TimeOut: time.Minute,
    }
    inspect(r) //  *real.Retriever &{Mozilla/5.0 1m0s} UserAgent: Mozilla/5.0
    //fmt.Println(download(r))
}

方法二:type assertion

    realRetriever := r.(*real.Retriever)
    fmt.Println(realRetriever.TimeOut)

或是

    if realRetriever, ok := r.(*real.Retriever); ok {
        fmt.Println(realRetriever.TimeOut)
    } else {
        fmt.Println(ok)
    }

接口变量里面有什么

  • 接口变量自带指针
  • 接口变量同样采用值传递,几乎不需要使用接口的指针
  • 指针接收者只能以指针方式使用;值接收者都可

5-4 接口的组合

package main

import (
    "fmt"
    "learngo/retriever/mooc"
    "learngo/retriever/real"
    "time"
)

type Retriever interface {
    Get(url string) string
}

type Poster interface {
    Post(url string, form map[string]string) string
}

const url  = "http://www.imooc.com"
func download(r Retriever) string {
    return r.Get(url)
}

func post(poster Poster) {
    poster.Post(url,
        map[string] string {
            "name": "ccmouse",
            "course": "golang",
        })
}

type RetrieverPoster interface {
    Poster
    Retriever
}

// 接口的组合
func session(s RetrieverPoster) string {
    s.Post(url, map[string]string{
        "contents": "another fake imooc.com",
    })
    return s.Get(url)
}

func inspect(r Retriever) {
    fmt.Printf("%T %v\n",r,r)
    switch v := r.(type) {
    case mooc.Retriever:
        fmt.Println("Contents:", v.Contents)
    case *real.Retriever:
        fmt.Println("UserAgent:", v.UserAgent)


    }
}
func main() {
    var r Retriever
    retriever := &mooc.Retriever{"this is a fake imooc.com"}
    inspect(r) //mooc.Retriever {this is a fake imooc.com} Contents: this is a fake imooc.com
    r = &real.Retriever{
        UserAgent: "Mozilla/5.0",
        TimeOut: time.Minute,
    }
    inspect(r) //  *real.Retriever &{Mozilla/5.0 1m0s} UserAgent: Mozilla/5.0
    //fmt.Println(download(r))

    // type assertion
    if realRetriever, ok := r.(*real.Retriever); ok {
        fmt.Println(realRetriever.TimeOut)
    } else {
        fmt.Println(ok)
    }

    fmt.Println("Try a session")
    fmt.Println(session(retriever))  // this is a fake imooc.com
}
package mooc

type Retriever struct {
    Contents string
}

func (r Retriever) Get(url string) string {
    return r.Contents
}

func (r Retriever) Post(url string, form map[string]string) string  {
    r.Contents = form["contents"]
    return "ok"
}

session(retriever) 的输出为this is a fake imooc.com,并没有被post所改变,因为Retriever是值接收者,改为指针接收者

5-5 常用系统接口

stringer相当于toString功能,还有reader writer等接口

func (r *Retriever) String() string  {
    return fmt.Sprintf("Retriever: {Contents=%s}", r.Contents)
}
package main

import (
    "fmt"
    "learngo/retriever/mooc"
    "learngo/retriever/real"
    "time"
)

type Retriever interface {
    Get(url string) string
}

type Poster interface {
    Post(url string, form map[string]string) string
}

const url  = "http://www.imooc.com"
func download(r Retriever) string {
    return r.Get(url)
}

func post(poster Poster) {
    poster.Post(url,
        map[string] string {
            "name": "ccmouse",
            "course": "golang",
        })
}

type RetrieverPoster interface {
    Poster
    Retriever
}

// 接口的组合
func session(s RetrieverPoster) string {
    s.Post(url, map[string]string{
        "contents": "another fake imooc.com",
    })
    return s.Get(url)
}

func inspect(r Retriever) {
    fmt.Println("Inspecting", r)
    fmt.Printf(" > %T %v\n",r,r)
    fmt.Print(" > Type switch:")
    switch v := r.(type) {
    case *mooc.Retriever:
        fmt.Println("Contents:", v.Contents)
    case *real.Retriever:
        fmt.Println("UserAgent:", v.UserAgent)
    }
    fmt.Println()
}
func main() {
    var r Retriever
    retriever := mooc.Retriever{"this is a fake imooc.com"}
    r = &retriever
    inspect(r) //mooc.Retriever {this is a fake imooc.com} Contents: this is a fake imooc.com
    r = &real.Retriever{
        UserAgent: "Mozilla/5.0",
        TimeOut: time.Minute,
    }
    inspect(r) //  *real.Retriever &{Mozilla/5.0 1m0s} UserAgent: Mozilla/5.0
    //fmt.Println(download(r))

    // type assertion
    if realRetriever, ok := r.(*real.Retriever); ok {
        fmt.Println(realRetriever.TimeOut)
    } else {
        fmt.Println(ok)
    }

    fmt.Println("Try a session")
    fmt.Println(session(&retriever))  // this is a fake imooc.com
}
// 输出
Inspecting Retriever: {Contents=this is a fake imooc.com}
 > *mooc.Retriever Retriever: {Contents=this is a fake imooc.com}
 > Type switch:Contents: this is a fake imooc.com

Inspecting &{Mozilla/5.0 1m0s}
 > *real.Retriever &{Mozilla/5.0 1m0s}
 > Type switch:UserAgent: Mozilla/5.0

1m0s
Try a session
another fake imooc.com

6 函数与闭包

6-1 函数式编程

package main

import "fmt"

// 累加器
func adder() func(int) int  {
    sum := 0
    return func(v int) int {
        sum += v
        return sum
    }
}
func main() {
    a := adder()
    for i := 0; i < 10; i++ {
        fmt.Print(a(i), " ") // 0 1 3 6 10 15 21 28 36 45
    }
}

函数式编程 VS 函数指针

  • 函数是一等公民:参数,变量,返回值都可以是函数
  • 高阶函数
  • 函数 -> 闭包

变量的作用域有两种:全局变量和局部变量;函数内部可以直接读取全局变量;在函数外部无法读取函数内的局部变量。
能够读取其他函数内部变量的函数,就是闭包。本质上,闭包就是将函数内部和函数外部连接起来的一座桥梁。

闭包的用途:

  • 从外部读取函数内部的变量
  • 将创建的变量的值始终保持在内存中
  • 封装对象的私有属性和私有方法
image.png

python 中的闭包

  • python 原生支持闭包
  • 使用closure来查看闭包内容
def adder():
    sum = 0
    
    def f(value):
        nonlocal sum
        sum += value
        return sum
    return f

Java中的闭包

Function<Integer, Integer> adder() {
    final Holder<Integer> sum = new Holder<>(0);
    return (Integer value) -> {
        sum.value += value;
        return sum.value;
    }
}

6-2 函数式编程例一

斐波那

package main

import "fmt"

func fibonacci() func() int {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}
func main() {
    f := fibonacci()

    fmt.Println(f()) // 1
    fmt.Println(f()) // 1
    fmt.Println(f()) // 2
    fmt.Println(f()) // 3
    fmt.Println(f()) // 5
    fmt.Println(f()) // 8
    fmt.Println(f()) // 13
    fmt.Println(f()) // 21
}

为斐波那实现reader

package main

import (
    "bufio"
    "fmt"
    "io"
    "strings"
)

func fibonacci() intGen {
    a, b := 0, 1
    return func() int {
        a, b = b, a+b
        return a
    }
}

type intGen func() int

func (g intGen) Read(p []byte) (n int, err error) {
    next := g()
    if next > 10000 {
        return 0, io.EOF
    }
    s := fmt.Sprintf("%d\n", next)
    // TODO: incorrect if p is too small!
    return strings.NewReader(s).Read(p)
}

func printFileContents(reader io.Reader) {
    scanner := bufio.NewScanner(reader)

    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

func main() {
    f := fibonacci()
    printFileContents(f)
    //fmt.Println(f()) // 1
    //fmt.Println(f()) // 1
    //fmt.Println(f()) // 2
    //fmt.Println(f()) // 3
    //fmt.Println(f()) // 5
    //fmt.Println(f()) // 8
    //fmt.Println(f()) // 13
    //fmt.Println(f()) // 21
}

6-3 函数式编程例二

使用函数来遍历二叉树

package tree

import (
    "fmt"
)

type TreeNode struct {
    Value int
    Left, Right *TreeNode
}

func (node TreeNode) Print() {
    fmt.Print(node.Value, " ")
}

func (node *TreeNode) SetValue(value int) {
    if node == nil {
        fmt.Println("Setting value to nil " + "node. Ignored.")
        return
    }
    node.Value = value
}

// 中序遍历
func (node *TreeNode) Traverse() {
    node.TraverseFunc(func(node *TreeNode){
        node.Print()
    })
    fmt.Println()
}

func (node *TreeNode) TraverseFunc(f func(*TreeNode)) {
    if node == nil{
        return
    }

    node.Left.TraverseFunc(f)
    f(node) // 可以定义任何操作
    node.Right.TraverseFunc(f)
}
// 工厂函数
func CreateNode(value int) *TreeNode {
    return &TreeNode{Value: value}
}
// 还可以做其他操作
    nodeCount := 0
    root.TraverseFunc(func(node *tree.TreeNode) {
        nodeCount++
    })
    fmt.Println("nodeCount: ", nodeCount)

7 资源管理与出错处理(catch all the errors)

7-1 defer调用

  • 确保调用在函数结束时发生
  • 参数在defer语句时计算
  • defer列表为后进先出
package main

import "fmt"

func tryDefer()  {
    defer fmt.Println(1)
    defer fmt.Println(2)
    fmt.Println(3)
}
func main() {
    tryDefer() // 输出为3 2 1 可以将defer定义为栈,先进后出,用defer定义不担心提前return或者出现Panic
}

7-2 错误处理概念

    // 进行错误处理
    file, err := os.OpenFile(filename, os.O_EXCL|os.O_CREATE, 0666)
    if err != nil {
        if pathError, ok := err.(*os.PathError); !ok {
            panic(err)
        } else {
            fmt.Println(pathError.Op, pathError.Path, pathError.Err) // open fib.txt file exists
        }
        return
    }

7-3 服务器统一出错处理

package main

import (
    "io/ioutil"
    "net/http"
    "os"
)

// 文件列表服务器
func main() {
    http.HandleFunc("/list/", func(writer http.ResponseWriter, request *http.Request) {
        path := request.URL.Path[len("/list/"):]
        file, err := os.Open(path)
        // 文件不存在出现Panic错误不会关闭服务器
        if err != nil {
            http.Error(writer, err.Error(), http.StatusInternalServerError)
            return
        }
        defer file.Close()

        all, err := ioutil.ReadAll(file)
        if err != nil {
            panic(err)
        }

        writer.Write(all)
    })

    err := http.ListenAndServe(":8888", nil)
    if err != nil {
        panic(err)
    }
}
fib_txt.png
open_file_error.png

实现统一的错误

func HandleFileList (writer http.ResponseWriter, request *http.Request) error{
    path := request.URL.Path[len("/list/"):]
    file, err := os.Open(path)
    // 文件不存在出现Panic错误不会关闭服务器
    if err != nil {
        return err
    }
    defer file.Close()

    all, err := ioutil.ReadAll(file)
    if err != nil {
        return err
    }

    writer.Write(all)
    return nil
}
type appHandler func(writer http.ResponseWriter, request *http.Request) error

func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
    return func(writer http.ResponseWriter, request *http.Request) {
        err := handler(writer, request)
        if err != nil {
            code := http.StatusOK
            switch  {
            case os.IsNotExist(err):
                code = http.StatusNotFound
            default:
                code = http.StatusInternalServerError
            }
            http.Error(
                writer,
                http.StatusText(code),
                code)

        }
    }
}
error_handler.png

7-4 Panic和recover

Go语言不支持传统的try...catch...finally这种异常,在Go语言中,使用多值返回来返回错误,引入Exception处理:defer,panic,recover
panic

  • 停止当前函数执行
  • 一直向上返回,执行每一层的defer
  • 如果没有遇见recover,程序退出、

recover

  • 仅在defer调用中使用
  • 获取panic的值
  • 如果无法处理,可重新panic

7-5 服务器统一出错处理

如果直接访问http://localhost:8888/fib.txt,则服务器直接出错,但因为系统有保护,服务器不会关闭
自己定义保护

package filelisting

import (
    "io/ioutil"
    "net/http"
    "os"
    "strings"
)

const prefix  = "/list/"

type userError string

func (e userError) Error() string {
    return e.Message()
}

func (e userError) Message() string {
    return string(e)
}

func HandleFileList (writer http.ResponseWriter, request *http.Request) error{
    strings.Index(request.URL.Path, prefix)
    if strings.Index(
        request.URL.Path, prefix) != 0 {
            return userError("path must start with " + prefix)
    }
    path := request.URL.Path[len(prefix):]
    file, err := os.Open(path)
    // 文件不存在出现Panic错误不会关闭服务器
    if err != nil {
        return err
    }
    defer file.Close()

    all, err := ioutil.ReadAll(file)
    if err != nil {
        return err
    }

    writer.Write(all)
    return nil
}
package main

import (
    "learngo/errhanding/filelisteningserver/filelisting"
    "log"
    "net/http"
    "os"
)

type appHandler func(writer http.ResponseWriter, request *http.Request) error

func errWrapper(handler appHandler) func(http.ResponseWriter, *http.Request) {
    return func(writer http.ResponseWriter, request *http.Request) {
        defer func(){
            if r := recover(); r != nil {
                log.Printf("panic: %v", r)
                http.Error(writer,
                    http.StatusText(http.StatusInternalServerError),
                    http.StatusInternalServerError)
            }
        }()
        err := handler(writer, request)
        if err != nil {
            log.Printf("Error occurred handing request: %s", err.Error())
            if userErr, ok := err.(userError); ok {
                http.Error(writer,
                    userErr.Message(),
                    http.StatusBadRequest)
                return
            }
            code := http.StatusOK
            switch  {
            case os.IsNotExist(err):
                code = http.StatusNotFound
            case os.IsPermission(err):
                code = http.StatusForbidden
            default:
                code = http.StatusInternalServerError
            }
            http.Error(
                writer,
                http.StatusText(code),
                code)

        }
    }
}

// 可以展示给用户的error
type userError interface {
    error
    Message() string
}

// 文件列表服务器
func main() {
    http.HandleFunc("/", errWrapper(filelisting.HandleFileList))

    err := http.ListenAndServe(":8888", nil)
    if err != nil {
        panic(err)
    }
}
userError.png

systemError.png

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

本文来自:简书

感谢作者:Susu_8601

查看原文:Golang学习

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

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