Golang 学习日志

栏目: Go · 发布时间: 5年前

内容简介:官网下载地址:

安装

官网下载地址: 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 可以从指定源上面下载或者更新指定的代码和依赖,并对他们进行编译和安装

函数

main()
init()
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接口类型
}

接口类型转换

str, ok := value.(string)
控制语句 > 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
    }

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

实用语义网

实用语义网

2009-2 / 59.00元

《实用语义网RDFS与OWL高效建模(英文版)》是语义网的入门教程,详细讲述语义网的核心内容的语言,包括语义网的概念、语义建模等。语义网的发展孕育着万维网及其应用的一场革命,作为语义网核心内容的语言:RDF和OWL,逐渐得到广泛的重视和应用。 《实用语义网RDFS与OWL高效建模(英文版)》对于任何对语义网感兴趣的专业技术人员都是十分难得的参考书。一起来看看 《实用语义网》 这本书的介绍吧!

URL 编码/解码
URL 编码/解码

URL 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具