区块链 | Distributed Ledger Technology

Go 学习笔记

2018-09-05  本文已影响64人  gothicrush

[内容][111]
<span id="jump">Hello World</span>

Part 1 开发环境

环境变量设置

GOROOT

指定 golang sdk 的安装目录

GOPATH

golang 工作目录,项目的源码放在这个目录下

PATH

将 GOROOT/bin 放在 Path 路径下,方便命令行能直接运行 golang的命令行工具

Go项目目录结构

|--project                    // 位于GOPATH下
    |--src                    // 存放源代码
        |--packageA
            |--packageA.go
        |--packageB
            |--packageB.go
    |--pkg                    // 编译后生成的文件
    |--bin                    // 编译后生成的可执行文件

Part 2 基础知识

Go的注释

// 行注释
//块注释不可以嵌套

/*
Comment 1
Comment 2
Comment 3
...
Comment n
*/

Go的数据类型

基本数据类型

复杂数据类型

给类型起别名

type myInt int // 此时 myInt 可以作为 int 使用

Go的变量

变量创建的方法

// 方式1:指定变量类型
var i int      // 声明变量         // 可以声明的同时初始化
i = 10         // 为变量赋值       // 等价于 var i int = 10
fmt.Println(i) // 使用变量

// 方式2:使用类型推导 —— 必须显式初始化
var i          // 声明变量         // 可以声明的同时初始化
i = 10         // 为变量赋值       // 等价于 var i = 10
fmt.Println(i) // 使用变量

// 方式3:使用简洁语法 —— 不可以用于函数外的变量声明,只能用于局部变量
i := 10        // 声明变量并由类型推导初始化
fmt.println(i) // 使用变量

// 方式4:同时创建多个全局变量 —— 只能用于函数外的变量声明
var (                                      
    n1 = 100
    name = "gothicrush"
    n3 = 200
)

引用类型变量相关

变量作用域

变量的注意事项

Go类型转换

go中不存在自动(隐式)转换,只有显式转换,就算是低精度->高精度都要显式转换
语法:T(value)
var i int = 42
var b float64 = float64(i)

基本数据类型与string转换

使用 strconv 包

Go标识符

只能由 数字,英文字母和_组成

不能以数字开头

单独的 _ ,是一个特殊符号,不能用作标识符

var _ int = 64 //error

不能用保留关键字,int,float64 等等不是保留关键字,但不推荐使用

预定义标识符和保留关键字

Part 3 - 指针

Part 4 - 包管理

包的概述

包的作用

声明包

package db

导入包

包的搜索路径

包的init函数

包的注意事项

Part 5 - 运算符

运算符分类

算术运算符

比较运算符

逻辑运算符

赋值运算符

其他运算符

Part 6 - IO

从键盘获取数据

import fmt

// fmt.Scanln() // 换行时停止扫描

var name string
var age byte
var sal float32
var isPass bool

fmt.Scanln(&name)
fmt.Scanln(&age)
fmt.Scanln(&sal)
fmt.Scanln(&isPass)


// fmt.Scanf()  // 扫描文本,根据format参数指定格式将读取空白分隔的值保存进本函数中
var name string
var age byte
var sal float32
var isPass bool

fmt.Scanf("%s %d %f %t", &name, &age, &sal, &isPass)

读取命令行参数

文件的输入流与输出流

什么是输入流/输出流

文件操作的包

打开文件

func os.Open(path string) (f *File, err error)

关闭文件

func (f *File) Close() error
配合 defer 语句使用

读文件

// 默认缓冲区大小为4096
reader := bufio.NewReader(file)

for {
    str,err := reader.ReadString('\n')
    
    if err == io.EOF {
        break
    }
}
func ioutil.ReadFile(path string) (content []byte, err error) {}

file := "/home/test.text"
content, err := ioutil.ReadFile(file) // 不需要打开和关闭文件,这些操作都封装到ReadFile函数中了,适合小文件读取
if err != nil {
    fmt.Println(string(content))
}

写文件

func OpenFile(path string, flag int, perm FileMode) (file *File,err error)
O_RDONLY
O_WRONLY
O_RDWR
O_APPEND
O_CREATE
O_EXCL
O_SYNC
O_TRUNC
r -> 4
w -> 2
x -> 1

判断文件与目录是否存在

根据 os.Stat() 函数返回值进行判断
1. 如果返回值为nil,说明文件或目录存在
2. 如果返回值使用os.IsNotExist()判断为true,则说明文件或目录不存在
3. 如果返回值为其他类型,则不确定是否存在
func PathExisting(path string) (bool,error) {
    _, err := os.Stat(path)
    if err == nil {
        return true, nil
    }
    
    if os.IsNotExist(err) {
        return false, nil
    }
    
    return false, err
}

拷贝文件

func io.Copy(dst Writer, src Reader) (Written int64, err error)
func CopyFile(dstFilePath string, srcFilePath) (written int64, err error) {
    srcFile, err := os.Open(srcFilePath)
    
    if err != nil {
        fmt.Println("读取源文件错误")
        return
    }
    
    defer srcFile.Close()
    
    reader := bufio.NewReader(srcFile)
    
    dstFile, err := os.OpenFile(dstFilePath, os.O_WRONLY | os.O_CREATE, 0666) 
    
    if err != nil {
        fmt.Println("打开文件失败")
    }
    
    defer dstFile.Close()
    
    writer := bufio.NewWriter(dstFile)
    
    return io.Copy(writer, reader)
}

Part 7 - 流程控制

分支流程控制

循环分支流程控制

goto语句

Part 8 - 函数

定义函数

func 函数名 (形参列表) (返回值列表) { // 左大括号不能换行
    //执行语句
    return xxx //可有可无
}

函数参数简化

func test(a int, b int, c int) {}
等价于
func test(a,b,c int) {}

函数返回值

可变参数

func sum (args... int) total int {
    for _, val := range args {
        total += val
    }
    return
}

sum(1,2,3,4,5,6,7)

函数注意事项

init函数

1.png

匿名函数

闭包

闭包就是 一个函数A中定义另外一个函数B,且函数B中有使用到函数A中的局部变量,且函数A将函数B返回

defer

函数参数传递方式

内置函数

Part 9 - 字符串常用操作

Part 10 - 时间和日期常用操作

Part 11 - 错误处理机制

panic-defer-recover机制

func main() {
   
   test()
   fmt.Println("尽管发生了 panic,程序还是继续执行了")
   
}

func test() {
   defer func() {
       err := recover() // recover内置函数,可以捕获异常
       if err != nil {
           fmt.Println("err=",err)
       }
   }()
   
   num1 := 10
   num2 := 0
   res := num1 / num2
   fmt.Println(res)
}

自定义异常

注意事项

Part 12 - 数组

什么是数组

数组定义

var 数组名 [数组大小]数据类型
var a [5]int

数组初始化的4种方式

var numArray01 [3]int = [3]int{1,2,3}
var numArray02 = [3]int{1,2,3}
var numArray03 = [...]int{1,2,3}  // 长度自行推导
var numArray04 = [3]{0:1,2:3} // 指定下标

数组遍历

// 方法1:
for i := 0; i < len(arr); i++ {
    fmt.Println(arr[i])
}

// 方法2:
for index, value := range arr {
    fmt.Println(index, " ", value)
}

数组对比

数组相互赋值

数组注意事项

二维数组

6.png

Part 13 - 切片

什么是切片

定义切片

var 切片名 []类型
var s []int

切片初始化

// 如果没有给切片赋值,则是类型的默认值
// 如果切片没有初始化,也是可以使用的,这与 map 必须初始化后才能使用不同

// 方式1:直接初始化
var s []int = []int{1,3,5}

// 方式2:由已存在数组创建
var intArr [5]int = [...]int{1,2,3,4,5}
s := intArr[1:3] // [1,3)
// arr[0:end] 等价 arr[:end]
// arr[start:len(str)] 等价 arr[start:]
// arr[0:len(str)] 等价 arr[:]

// 方式3:通过 make 来创建切片
// 通过 make 方法创建的切片,其底层数组是 make 内部维护的,外部不可见
// 所以切片的值是默认值
var s []int = make([]int, 4) // 只指定了 length,则capacity == length
var s []int = make([]int, 4, 10) // []type, length, capacity 

切片遍历

var arr [5]int = [...]int{10,20,30,40,50}
slice := arr[0:3]

// 方式1
for i := 0; i < len(slice); i++ {
    fmt.Println(slice[i])
}

// 方式2
for i, v := range slice {
    fmt.Println(i, v)
}

切片追加元素

var slice []int = []int{1,2,3}
slice = append(slice,5,6,7) // 返回新的 slice 
slice = append(slice,slice) // 可以把slice追加给slice

切片append操作原理

切片拷贝操作

copy(para1,para2) //para1 和 para2 都是切片类型,将para2的内容复制到para1

切片内存布局

切片和字符串

2.png

基于原有切片定义新切片

slice := []int{1, 2, 3, 4, 5}
slice1 := slice[:]
slice2 := slice[0:]
slice3 := slice[:5]
让新切片的长度和容量一样,这样我们在追加操作的时候就会生成新的底层数组,和原有数组分离,就不会因为共用底层数组而引起奇怪问题,因为共用数组的时候修改内容,会影响多个切片

空切片和nil切片

nil切片: var slice []int
空切片:  slice := make([]int,0)

Part 14 - 结构体

定义结构体

type Cat struct {
    Name string
    Age int
    Color string
    Hobby []string
}

结构体实例化的4种方法

// 方法1
var catInstance Cat // 该变量中的字段的值都是默认类型

// 方法2
var catInstance Cat = Cat{"小白", 2, "white", []string{"吃鱼","喝奶"}}
var catInstance Cat = Cat {
    Name: "小白",
    Age: 2,
    Color: "white",
    Hobby: []string{"吃鱼","喝奶"},
}

// 方法3
var catInstancePtr *Cat = new(Cat)
(*catInstancePtr).Name = "小白" //等价于 catInstancePtr.Name = "小白"

// 方法4
var catInstancePtr *Cat = &Cat{}
(*catInstancePtr).Name = "小白" //等价于 catInstancePtr.Name = "小白"
// 不能写 *catInstancePtr.Name = "小白" 因为 . 的优先级高于 *

结构体实例初始化问题

// 如果没有给字段赋值,则默认为零值
// 引用类型是nil,即还没有分配空间,对于这样的字段,需要先make,才能使用

var cat1 Cat 
             
fmt.Println(cat1.Name, cat1.Age, cat1.Color) //ok
fmt.Println(cat1.Hobby) //error,需要初始化后才能使用

结构体与tag

匿名结构

name := struct {
    字段名1 类型
    字段名2 类型
    字段名3 类型
} {
    字段名1:value,
    字段名2:value,
    字段名3:value
}
//注意匿名的东西都只能使用":="的形式,因为无法指定类型

匿名字段

type Person struct {
    string
    int
}
a := Person{"narlinen",20}

工厂模式

结构体注意事项

Part 15 - 方法

什么是方法

方法的定义与使用

type A struct {
    Name string
}

func (a A) test() {
    fmt.Println(a.Name)
}

func main() {
    a := A{Name:"111"}
    a.test()
}

可以添加方法的类型

方法注意事项

值接收者和指针接收这

Part 16 - 封装

Part 17 - 继承

继承的实现方法

如果一个 struct 嵌套了另外一个匿名结构体,那么这个结构体就可以直接访问匿名结构体的字段和方法,从而实现继承

继承实现的例子

type Goods struct {
    Name String
    Price float64
}

type Book struct {
    Goods // 这里就是嵌套匿名结构体 Goods
    Writer string
}

继承注意事项

继承的初始化

type Brand struct {
    Name string
    Address string
}

type Goods struct {
    Name string
    Price float64
}

type TV struct {
    Goods
    Brand
}

func main() {
    tv := TV {
        Goods{
            Name: "电视机",
            Price: 15.5
        },
        Brand{"海尔","上东"},
    }
}

使用指针形式的继承

type Brand struct {
    Name string
    Address string
}

type Goods struct {
    Name string
    Price float64
}

type TV struct {
    *Goods
    *Brand
}

func main() {
    tv := TV {
        &Goods{
            Name: "电视机",
            Price: 15.5
        },
        &Brand{"海尔","上东"},
    }
}

Part 18 - 组合

组合与继承的关系

Part 19 - 接口

接口定义

type usb interface {
    Start()
    Stop()
}

接口实现

type Phone struct {
    
}

func (p Phone) Start() {
    fmt.Println("手机连接成功")
}

func (p Phone) Stop() {
    fmt.Println("手机停止连接")
}

接口注意事项

接口和继承的关系

Part 20 - 多态

Part 21 - 类型断言

什么是类型断言

类型断言语法

接口变量名.(具体类型) // 此处变量必须为 interface 类型

类型断言返回值

x := 变量名.(具体类型) // 如果转换成功则返回给x,转换失败则抛出 panic
x,ok := 变量名.(具体类型) // 如果转换成功则返回给x,ok为true,转换失败则 ok 为 false,x为类型默认值

类型断言例子

type Point struct {
    x int
    y int
}

func main() {
    var a interface{}
    var point = Point{1,2}
    
    a = point
    
    var b Point
    
    // b = a //不行,虽然 a 指向的是Point类型,但是现在 a 是 Point 类型
    b = a.(Point) // 可以,这就是类型断言,表示判断 a 是否指向 Point 类型的变量
                  // 如果是则转为 Point 类型并赋值给 b 变量,否则抛出 panic
}

类型断言的最佳实践

func checkType(items ...interface{}) {
    
    for index, x := range itmes {
        
        switch x.(type) { // 这里 type 是关键字,固定写法,只能用于 switch 语句
        case bool:
            fmt.Println("bool")
        case float64:
            fmt.Println("float64")
        case string:
            fmt.Println("string")
        case Student:
            fmt.Println("Student")
        case *Student:
            fmt.Println("*Student")
        }
    }
}

Part 22 - 单元测试

传统测试方法

单元测试框架

benchmark

Part 23 - JSON/序列化/反序列化

JSON概念以及作用

序列化所需的包以及函数

import "encoding/json"

func Marshal(v interface{}) ([]byte, error)

结构体进行序列化

map进行序列化

切片进行序列化

序列化所需的包以及函数

import "encoding/json"

func Unmarshal(s []byte, v interface{}) (err error) 

反序列化为结构体

package main

import "encoding/json"
import "fmt"

func main() {

    str := `{"address":"洪崖洞","age":30,"name":"红孩儿"}`

    var monster Monster

    err := json.Unmarshal([]byte(str), &m)

    if err != nil {
        fmt.Println("序列化失败", err)
    } else {
        fmt.Println("序列化后:", m)
    }
}

反序列化为map

package main

import "encoding/json"
import "fmt"

func main() {

    str := `{"address":"洪崖洞","age":30,"name":"红孩儿"}`

    var m map[string]interface{}
    m = make(map[string]interface{})
    m["one"] = 1

    err := json.Unmarshal([]byte(str), &m)

    if err != nil {
        fmt.Println("序列化失败", err)
    } else {
        fmt.Println("序列化后:", m)
    }
}

反序列化为切片

package main

import "encoding/json"
import "fmt"

func main() {

    str := `{"address":"洪崖洞","age":30,"name":"红孩儿"}`

    var m map[string]interface{}

    err := json.Unmarshal([]byte(str), &m)

    if err != nil {
        fmt.Println("序列化失败", err)
    } else {
        fmt.Println("序列化后:", m)
    }
}

注意事项

反射的概念与作用

反射的使用

reflect.Value

3.png

reflect.Type

Part 25 - 常量

Part 26 - 网络编程

网络编程分类

端口

Socket 的使用流程

4.png

Socket 实例例子

Part 27 - 映射

映射类型声明的三种方式

映射 key 和 value 的要求

映射的赋值 [增,改]

映射的删除 [删]

映射的查找 [查]

映射的遍历

映射的排序

映射注意事项

Part 28 - goroutine(协程)

Go的进程和Go的协程

Go协程的使用

// 编写一个程序,满足以下功能:
// 1. 在进程中,开启一个 goroutine,该协程每隔1秒输出 "hello world"
// 2. 在进程中,也每隔1秒输出 "hello golang",输出10次后,进程结束
// 3. 要求进程和 goroutine 同时执行

package main

import (
    "fmt"
    "time"
    "strconv"
)

func test() {
    for i := 0; i < 10; i++ {
        fmt.Println("[test] hello world" + strconv.Itoa(i))
        time.Sleep(time.Second)
    }
}

func main() {
    
    go test() // 开启一个协程,执行这个 test
    
    for i := 0; i < 10; i++ {
        fmt.Println("[main] hello golang" + strconv.Itoa(i))
        time.Sleep(time.Second)
    }
}

goroutine协程的调度模型

查看和设置Go运行的cpu数目

goroutine中使用 recover

Part 29 - channel

goroutine资源竞争的问题

// 下面程序会发成 资源竞争问题(concurrent map writes)
// 查看是否发生资源竞争问题可以用命令:go build -race test.go
// 现采用goroutine计算1-200各个数的阶乘,并把每个数的阶乘放进map中,最后显示出来

package main

import (
    "fmt"
    "time"
)

var (
    myMap = make(map[int]int, 10)
)

func calculate(n int) {
    
    res := 1
    for i := 1; i <= n; i++ {
        res *= i
    }
    
    myMap[n] = res
}

func main() {
    for i := 1; i <= 200; i++ {
        go test(i)
    }
    
    time.Sleep(20 * time.Second) // 避免goroutine还没执行,主线程就结束了
    
    for k,v := range myMap {
        fmt.Printf("map[%d]%d\n", k, v)
    }
}

解决资源竞争问题 - 通过全局变量加锁

package main

import (
    "fmt"
    "time"
    "sync"
)

var (
    myMap = make(map[int]int, 10)
    lock sync.Mutex // 全局的互斥锁
)

func calculate(n int) {
    
    res := 1
    for i := 1; i <= n; i++ {
        res *= i
    }
    
    lock.Lock()    // 加锁
    myMap[n] = res
    lock.Unlock()  // 解锁
}

func main() {
    for i := 1; i <= 200; i++ {
        go calculate(i)
    }
    
    time.Sleep(20 * time.Second) // 避免goroutine还没执行,主线程就结束了
    
    lock.Lock()    // 加锁
    for k,v := range myMap {
        fmt.Printf("map[%d]%d\n", k, v)
    }
    lock.Unlock()  // 解锁
}

解决资源竞争问题 - 通过channel

全局变量锁的缺点

channel 介绍

channel 声明

var 变量名 chan 数据类型

var intChan chan int
var mapChan chan map[int]string
var perChan chan Person

channel 初始化

var intChan chan int = make(chan int, 3)

channel 写入数据

intChan <- 10

// channel数据放满后就不能再放了,容量不是动态增长的

channel读取数据

var num int
num = <- intChan

// 在没有使用协程的情况下,当channel已经为空,但是依然继续取时,会报 deadlock

channel长度和容量

len(intChan)
cap(intChan)

channel关闭

// 关闭 channel 后,就不能再往里面写数据了,但如果 channel 里面还有数据,则可以继续读取
close(intChan)

channel遍历

// 在遍历时,如果 channel 还没关闭,则会报 deadlock
// 在遍历时,如果 channel 已经关闭,则遍历正常执行

for v := range intChan {
    fmt.Println("v=", v)
}

channel阻塞机制

如果只向 channel 中写入数据而不读取,导致 channel 满了,还继续写,就会出现阻塞而 deadlock

如果读的频率远低于写的频率也没有问题,也不会发生 deadlock,关键是要去读

channel 只读与只写

var chan1 chan int // 可读可写
var chan2 chan <- int // 只写
var chan3 <- chan int // 只读

channel与select

// 使用 select 可以解决从管道取数据阻塞的问题

// 1. 定义一个管道 10个数据int
intChan := make(chan int, 10)
for i := 0; i < 10; i++ {
    intChan <- i
}
// 2. 定义一个管道  5个数据string
strChan := make(chan string, 5)
for i := 0; i < 10; i++ {
    intChan <- "hello" + fmt.Sprintf("%d", i)
}

// 在 for-range 遍历管道时,如果不关闭管道,会发生 deadlock 现象
// 但是我们可能不好确定什么时候该关闭管道
// 可以使用 select 方法解决
for {
    select {
        // 这里,如果管道没有关闭,也不会因为一直阻塞而 deadlock
        // 会自动到下一个 case 匹配
        case v := <- intChan:
            fmt.Println("从 intChan 读取数据%v\n", v)
        case v :=  <- strChan:
            fmt.Println("从 strChan 读取数据%v\n", v)
        default:
            fmt.Println("都没取到")
    }
}

通过 channel 解决素数问题

思路图示

5.png

代码实现

package main
import (
    "fmt"
)

func putNum(intChan chan int) {
    
    for i := 0; i < 1000; i++ {
        intChan <- i
    }
    
    close(intChan)
}

func primeNum(intChan chan int, primeChan chan int, exitChan chan bool) {
    var flag bool
    for {
        num, ok := <- intChan
        
        if !ok {
            break
        }
        
        flag = true
        for i := 2; i < num; i++ {
            if num % i == 0 {
                flag = false
                break
            }
        }
        
        if flag {
            primeChan <- num
        }
    }
    
    fmt.Println("有一个primeChan因为取不到数了而退出")
    exitChan <- true
}

func main() {
    
    intChan := make(chan int, 1000)
    primeChan := make(chan int, 1000)
    exitChan := make(chan bool, 4)
    
    go putNum(intChan)
    
    for i := 0; i < 4; i++ {
        go primeNum(intChan, primeChan, exitChan)
    }
    
    go func() {
        for i := 0; i < 4; i++ {
            <- exitChan
        }

        close(primeNum)
    }()
    
    for {
        res, ok := <- primeNum
        if !ok {
            break
        }
        fmt.Printf("素数:%d\n", res)
    }
    
    fmt.Println("main线程退出")
}

Part 30 - go命令行工具

Part 31 - Go操作Redis

安装 Go 的 redis 插件

go get github.com/garyburd/redigo/redis

Go 中连接 redis

connect, err := redis.Dial("tcp","localhost:6379")

Go 中执行 redis 命令

_, err := connect.Do("Set", "key1", 998)

result, err := connect.Int(c.Do("Get", "key1")) // 要使用类型断言

Go 中使用 redis 连接池

var pool *redis.Pool

pool = &redis.Pool {
    MaxIdle: 8,     // 最大空闲连接数
    MaxActive: 0,   // 表示和数据库的最大连接数,0表示不限制
    IdleTimeout: 100, // 最大空闲时间,单位秒
    Dial: func() (redis.Conn, error) { // 初始化连接池的代码
        return redis.Dial("tcp", "localhost:6379")
    },
}

connect := pool.Get() // 从连接池中取出连接

pool.Close() // 关闭连接池

Part Max - 补充内容

随机数使用

import "rand"

// 设置随机数种子
rand.Seed(time.Now().UnixNano())

// 产生随机数
rand.Intn(100) // 0<=n<100

CGO

VS Code 使用技巧

反射再看一遍

网络再看一遍

上一篇下一篇

猜你喜欢

热点阅读