Golang 学习日志

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

安装

官网下载地址:https://golang.org/dl/ ,根据系统平台下载对应资源包,安装或解压到对应目录,然后配置环境变量

GOROOT:go安装(非默认安装目录时需要配置)
PATH: go安装目录/bin(必需)
GOPATH:go项目目录(命令安装依赖时需要配置)

GOPATH目录约定有三个子目录:bin编译后生成的可执行文件,pkg编译后生成的文件,src存放源代码


认知

命名
  1. 驼峰式命名,不要使用下划线
  2. 根据首字母的大小写来确定可以访问的权限,无论是方法名、常量、变量名还是结构体的名称,如果首字母大写,则可以被其他的包访问(公有);如果首字母小写,则只能在本包中使用(私有)
变量
  1. var声明变量,函数内可用 := 简化声明和初始化
  2. _ 忽略返回值
  3. const 用于定义常量
包导入
import (
    "test/pkg1"       //默认导入方式,调用包内函数:pkg1.func1()
    p2 "test/pkg2"    //别名方式,调用包内函数:p2.func2()
    . "test/pkg3"     //省略前缀,调用包内函数:func3()
    _ "test/pkg4"     //仅执行包内init()函数,无法调用包内其他函数
)

常使用命令行 go get xxx 可以从指定源上面下载或者更新指定的代码和依赖,并对他们进行编译和安装

函数
  1. main() 一个包内只能有一个main函数
  2. init() 一个包内可以存在多个init函数,导入时都会被调用,在main函数之前被执行
  3. 常见写法
package main

import (
    "errors"
    "fmt"
)

func f1(num1 int, num2 int) int {
    return num1 + num2
}

func f2(num1 int, num2 int) (rs int) {
    rs = num1 + num2
    return
}

func f3(num1 int, num2 int) (rs int, err error) {
    if num2 == 0 {
        err = errors.New("num2 is empty.")
        return
    }
    rs = int(num1 / num2)
    return rs, nil
}

func main() {
    num1 := 5
    num2 := 3
    if rs, err := f3(num1, num2); err != nil {
        fmt.Println(err)
    } else {
        fmt.Printf("%d / %d = %d", num1, num2, rs)
    }
}
  1. defer代码块,在函数内部使用,会在函数结束或返回时被调用(先进后出)
func main() {
    fmt.Println(0)
    for i := 1; i <= 3; i++ {
        defer fmt.Println(i)
    }
    fmt.Println(4)
    // 0 4 3 2 1 
}
类型
  1. string 字符串类型,相当于[]byte
  2. interface{} 空接口类型,可以放入其他所有类型,一般用来定义未知类型的变量

类型转换

Golang是静态类型的编程语言,所有数据的类型在编译期确定。 Golang中即使是底层存的是同一个类型,声明的类型不一样,也要强制转换才能互用。

  1. Go语言类型转换基本格式如:type_name(expression)
    func main() {
        var a int = 1
        fmt.Printf("%T", a)             // int
        fmt.Printf("%T", float64(a))    // float64
    }
    
    如果强制转换一些无法转换的类型,将会报错

控制语句

  1. if - else
    // demo1
    if a == 1 {
        fmt.Println(1)
    } else if a == 2 {
        fmt.Println(2)
    } else {
        fmt.Println(3)
    }
    
    // demo2
    if b := a; b == 1 {
        fmt.Println(1)
    } else {
        fmt.Println(2)
    }
    
  2. switch - case
    var a int = 1
    
    // demo1
    switch a {
    case 1:
        fmt.Println(1)
    case 2:
        fmt.Println(2)
        fallthrough //连接执行下一个case
    case 3, 4, 5:
        fmt.Println(345)
    default:
        fmt.Println(0)
    }
    
    // demo2
    switch b := a; b {
    case 1:
        fmt.Println(1)
    default:
        fmt.Println(0)
    }
    
    // demo3
    switch {
    case a == 1:
        fmt.Println(1)
    case a == 2, a == 3:
        fmt.Println(23)
    default:
        fmt.Println(0)
    }
    
    // demo4 接口类型判断
    t := func() interface{} {
        return 1
    }()
    switch t.(type) {
    case int:
        fmt.Println("int")
    case string:
        fmt.Println("string")
    }
    
  3. for
    //基于计数器的迭代
    for i := 0; i < 10; i++ {
        fmt.Println(i)
    }
    
    //基于条件判断的迭代
    i := 10
    for i > 0 {
        fmt.Println(i)
        i--
    }
    
    //无限循环
    for {
        fmt.Println(1)
    }
    for true {
        fmt.Println(1)
    }
    
    //for-range迭代器
    //map,slice,array,channel
    for key, value := range array1 {
        fmt.Printf("%d => %v \n", key, value)
    }
    
  4. select - case
    可以用来处理channel操作
    select {
    case <-ch1:
        fmt.Println(1)
    case <-ch2:
        fmt.Println(2)
    case <-time.After(time.Minute):
        fmt.Println(9)
    default:
        fmt.Println(0)
    }
    

数组 Array

数组是类型相同的元素的集合

    var arr1 [5]int
    arr2 := [5]int{1, 2, 3}
    arr3 := [...]int{1, 2, 3}
    arr4 := [...]int{3: 3, 5: 5}
    arr5 := [4][2]int{{1, 2}, {3, 4}}

    fmt.Println(arr1, "\n", arr2, "\n", arr3, "\n", arr4, "\n", arr5)
    // [0 0 0 0 0]
    // [1 2 3 0 0]
    // [1 2 3]
    // [0 0 0 3 0 5]
    // [[1 2] [3 4] [0 0] [0 0]]

切片 Slice

切片是一个 长度可变的数组,是对数组一个连续片段的引用,是一个引用类型

//1.基于现有数组创建切片
var arr1 [10]int = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 0}
//array[start:end] 不包含end,其中start和end在为第一或最后一个时可以省略
slice1 := arr1[3:7]
slice2 := arr1[:4]

//2,直接创建切片
//make([]T, length, capacity)
slice3 := make([]int, 2, 2)
slice4 := []int{1, 2, 3, 4}

//切片可以通过append,copy函数来进行追加和复制
slice4 = append(slice4, 5)
slice4 = append(slice4, 6)
slice5 := make([]int, len(slice4), cap(slice4)*2)
copy(slice5, slice4)
//在追加时,当切片的len超过cap时,切片的内存会重新分配,返回的新的切片不再指向原数组,
//新切片的cap会变为原来的2倍

集合map

Map是一种无序的键值对的集合
Map通过 key 来快速检索数据,key 类似于索引, 指向数据的值

//声明定义
var map1 map[string]int //nil
//map1 = make(map[string]int)
map1 = map[string]int{"a": 1, "b": 2}

//添加
map1["c"] = 3
//删除
delete(map1, "b")

//是否存在
if _, ok := map1["c"]; ok {
    fmt.Println("C is exist")
} else {
    fmt.Println("C is not exist")
}

自定义结构体 Struct

结构体由一系列属性组成,每个属性都有自己的类型和值

package main

import (
    "fmt"
)

type A struct {
    b int
    c int
    d int
}

func main() {
    var a1 A
    a1.b = 2
    fmt.Println(a1)
    // {2 0 0} 值类型

    a2 := new(A)
    a2.b = 2
    fmt.Println(a2)
    // &{2 0 0} 引用类型

    a3 := A{b: 3, c: 4}
    fmt.Println(a3)
    // {3 4 0} 值类型

    a4 := &A{3, 4, 5} //此写法需要全部参数赋值
    fmt.Println(a4)
    // &{3 4 5} 引用类型
}
  1. 初始化后, 字段默认为该类型的空值
  2. 直接定义A类型或使用A{}来完成初始化返回的是值类型,
    使用new()&A{}初始化返回的是引用类型, 两者不一样
struct 嵌套与函数扩展
package main

import (
    "fmt"
)

type A struct {
    a1 int
    a2 int
}

type B struct {
    A  // B嵌套A,B将可以使用A的参数和方法
    b1 int
    b2 int
}

func (a *A) Af() {
    fmt.Println("a func")
}

func (b *B) Bf() {
    fmt.Println("b func")
}

func main() {
    b := new(B)
    b.A.a1 = 1
    b.a2 = 2
    b.b1 = 3
    fmt.Println(b) // &{{1 2} 3 0}

    b.Af() //a func
    b.Bf() //b func
}

如果A、 B两个struct不在同一个包中, B将无法读写A中的私有字段和方法


接口 Interface

接口是一些方法的集合, 只要实现了接口对应的方法, 就等于实现了此接口,
因此golang中所有的类型都实现了空接口interface{},在一些函数中如果传入参数类型不固定,都可以使用interface{}代替传入,但在具体使用该参数时仍需要将类型转换回对应类型。

package main

import (
    "fmt"
)

type Ai interface {
    a()
    b()
}
type Ci interface {
    c()
}

type AC interface { // 接口嵌套
    Ai
    Ci
}

type T1 struct{}
type T2 struct{}

func (t *T1) a() {
    fmt.Println("a1")
}

func (t *T1) b() {
    fmt.Println("b1")
}

func (t *T1) c() {
    fmt.Println("c1")
}

func (t *T2) a() {
    fmt.Println("a2")
}

func (t *T2) c() {
    fmt.Println("c2")
}
func main() {

    //定义接口类型变量
    var test1 Ai
    test1 = new(T1) // T1实现了a()和b(),所以可以初始化T1类型赋值给Ai接口类型
    test1.a()       // 输出 a1
    //  var test2 Ai
    //  test2 = new(T2) // 无法编译通过,T2没有实现Ai接口的b(),所以无法初始化T1类型赋值给Ai接口类型

    //定义接口类型变量
    var test3, test4 Ci
    test3 = new(T1) // T1实现了c(),所以可以初始化T1类型赋值给Ci接口类型
    test3.c()       // 输出 c1
    test4 = new(T2) // T2实现了c(),所以可以初始化T2类型赋值给Ci接口类型
    test4.c()       // 输出 c2

    //定义接口类型变量
    var test5 AC
    test5 = new(T1) // T1实现了a()、b()、c(),所以可以初始化T1类型赋值给AC接口类型
    test5.a()       // a1
    test5.b()       // b1
    test5.c()       // c1

    //  var test6 AC
    //  test6 = new(T2) // 无法编译通过,T2没有实现AC接口的b(),所以无法初始化T2类型赋值给AC接口类型
}
接口类型转换
  • 断言x.(T) , x是接口类型,如str, ok := value.(string)
  • Type switch,详情见上述 控制语句 > switch-case > demo4

协程 Goroutines

协程是在一个线程中模拟多个协程并发执行
没有优先级、 非抢占式调度: 任务不能主动抢占时间片
每个协程都有自己的堆栈和局部变量
golang调度学习: https://www.jianshu.com/p/9db2dcb1ccb7
给调用函数使用 go 关键字即可将函数放到协程中执行

package main

import (
    "fmt"
)

func f1() {
    fmt.Println(1)
}
func f2() {
    fmt.Println(2)
}
func main() {
    go f1()
    f2()
}

反复执行上面代码,我们会发现很大概率上只会输出2,极小概率会同时输出1、2,因为在使用go激活 f1 后,主线程直接就往下执行 f2 ,最后主线程退出,其中 f1 还没来的及执行打印就已经结束了。

如何固定让主线程的 f2 在 f1 执行只会在执行呢?

  1. sync.WaitGroup
    package main
    
    import (
        "fmt"
        "sync"
    )
    
    func f1() {
        fmt.Println(1)
        wg.Done() // 完成一个计数器,计数 -1
    }
    func f2() {
        fmt.Println(2)
    }
    
    var wg sync.WaitGroup
    
    func main() {
        wg.Add(1) // 添加一个等待计数器,计数 +1
        go f1()
        wg.Wait() // 主线程堵塞等待,在计数器为0时才会向下执行
        f2()
    }
    
  2. 信道Channel
    可以通过它们发送类型化的数据在协程之间通信, 可以避开一些内存共享导致的坑通过通道传递的数据同一时间只有一个协程可以访问。
    package main
    
    import (
        "fmt"
    )
    
    func f1() {
        fmt.Println(1)
        ch <- 1 //将数据传入ch
    }
    func f2() {
        fmt.Println(2)
    }
    
    var ch chan int //声明信道和信道数据的类型
    
    func main() {
        ch = make(chan int) //初始化,分配内存
        go f1()
        <-ch //取出ch中的数据,若ch中没有数据,则ch会堵塞,直到ch中有数据传入
        f2()
    }
    
    在默认无缓冲通道情况下,如果通道中没有数据, 那 <-ch 数据取出就阻塞,
    如果通道已有数据, 那 ch<-value 数据存入就阻塞。有缓冲通道是指在未填满指定数量之前不会堵塞, 数量满了之后就会像无缓冲一样堵塞, 直到通道数据被取出
    ch := make(chan ch_type)
    // 无缓冲信道,ch_type可以为任意类型
    
    ch := make(chan ch_type, buf_num)
    //有缓冲信道,buf_num为缓冲数量
    
    
    ch <- 123
    // 将数据传入信道
    // 无缓冲时,信道将会堵塞,直到ch取出数据
    // 有缓冲时,信道将无限制传入,直到传入数量大于缓冲数量,才会堵塞,直到ch取出数据才会继续传入
    
    data := <- ch
    // 将数据从信道中取出,并赋给data变量
    // 信道将会堵塞,直到ch有数据传入
    

Http web服务

使用Golang的标准库 net/http可以搭建一个Go Web服务器

package main

import (
    "fmt"
    "log"
    "net/http"
)

func hello(w http.ResponseWriter, req *http.Request) {
    fmt.Fprintln(w, "hello world")
}

func main() {
    http.HandleFunc("/", hello)
    err := http.ListenAndServe("127.0.0.1:80", nil)
    if err != nil {
        log.Fatal("ListenAndServe:", err.Error())
    }
}

Request: 用户请求的信息,用来解析用户的请求信息,包括post、get、file等信息
Response: 服务器需要反馈给客户端的信息
Handler: 处理请求和生成返回信息的处理逻辑

在编写项目代码时,有时会觉得每个函数都要写(w http.ResponseWriter, req *http.Request),就会觉得很麻烦,所以做了以下测试

package main

import (
    "fmt"
    "log"
    "net/http"
)

type CTX struct {
    ResponseWriter http.ResponseWriter
    Request        *http.Request
}

var Ctx *CTX

func Handle(url string, f func()) {
    http.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) {
        Ctx = &CTX{
            ResponseWriter: w,
            Request:        r,
        }
        f()
    })
}

func main() {
    Handle("/", home)
    Handle("/hello", hello)
    Handle("/ping", new(Ping).ping)
    err := http.ListenAndServe("127.0.0.1:80", nil)
    if err != nil {
        log.Fatal("ListenAndServe:", err.Error())
    }
}

////////////////////////////////////////////

func home() {
    fmt.Fprintln(Ctx.ResponseWriter, "home")
}

func hello() {
    fmt.Fprintln(Ctx.ResponseWriter, "hello world")
}

type Ping struct{}

func (p *Ping) ping() {
    fmt.Fprintln(Ctx.ResponseWriter, "pong")
}

错误类型

  1. 实现error接口
    type error interface{
        Error() string
    }
    
    package main
    
    import (
        "fmt"
    )
    
    type Myerror struct {
        msg string
    }
    
    func (e *Myerror) Error() string {
        return e.msg
    }
    
    func main() {
        var err error
        err = &Myerror{"test error"}
        fmt.Println(err) //test error
    }
    
  2. 使用errors包函数
    package main
    
    import (
        "errors"
        "fmt"
    )
    
    func main() {
        var err error
        err = errors.New("test error")
        fmt.Println(err) //test error
    }
    
  3. 使用fmt包函数
    package main
    
    import (
        "fmt"
    )
    
    func main() {
        var err error
        err = fmt.Errorf("%s error", "test")
        fmt.Println(err) //test error
    }
    
  4. error可以使用在函数返回
    package main
    
    import (
        "errors"
        "fmt"
    )
    
    func main() {
        if v, err := div(2, 0); err != nil {
            fmt.Println("error:", err)
        } else {
            fmt.Println("2/0 = ", v)
        }
    }
    
    func div(a, b int) (rs int, err error) {
        if b == 0 {
            err = errors.New("division by zero")
            return
        }
        return a / b, nil
    }
    

本文来自:简书

感谢作者:鱼籽灬

查看原文:Golang 学习日志

入群交流(和以上内容无关):Go中文网 QQ 交流群:798786647 或加微信入微信群:274768166 备注:入群;关注公众号:Go语言中文网

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