Go Base

blog.golang.org

pkg.go.dev

文档-Go语言
编程语言规范

Download

https://golang.google.cn/dl/

windows 三步骤

  1. GOROOT

设置环境变量,定义go安装程序的目录位置。例如:c:/go

  1. GOPATH

设置环境变量,定义一个全局的包安装目录(目录自定义,最好与GOROOT区分开)

  1. PATH

设置go的环境变量,目录为GOROOT/bin

image-20191129080516878

Go 命令

项目目录:

image-20191129095418302

  • go build main/main.go
报错内容:
GOROOT=C:\All\Tools\Go #gosetup
GOPATH=F:\workSpace\htdosc_go\study\src;C:\All\Tools\GOPATH #gosetup
C:\All\Tools\Go\bin\go.exe build -o C:\Users\admin-mxp\AppData\Local\Temp\___go_build_main_go.exe F:/workSpace/htdosc_go/study/src/main.go #gosetup
main.go:4:8: cannot find package "project1/hello" in any of:
    C:\All\Tools\Go\src\project1\hello (from $GOROOT)
    F:\workSpace\htdosc_go\study\src\src\project1\hello (from $GOPATH)
    C:\All\Tools\GOPATH\src\project1\hello

错误原因:
GOPATH=F:\workSpace\htdosc_go\study\src;
修改为
GOPATH=F:\workSpace\htdosc_go\study;
  • go install main

编译 && 安装到GOOPATH的bin目录下

image-20191129101419967

Go 指南

变量,函数, 类型

  1. 函数

    func add(x int, y int) int {
        return x + y
    }
    // 当2个甚至多个参数都是同类型,则除了最后一个类型,其余都可用省略
    func add(x, y int) int {
        return x + y
    }
    // 函数可用返回多个值,已逗号[,]隔离
    func add(x, y int) (int, int) {
        return x + y, x, y
    }
    a, b := add(1, 5)
    
  1. 命名的返回值

    // go的返回值可以被命名,就像变量那样使用。返回值的名称应该具有一定的意义,可以作为文档使用。
    // 没有参数的return 语句返回结果的当前值。也就是`直接`返回。
    // 直接返回语句仅应当用在像下面这样的短函数中,在长函数中使用它们会影响代码的可读性
    func split(sum int) (x, y int) {
        x = sum * 4 / 9
        y = sum - x
        return
    }
    //a1, b1 := split(15)
    //fmt.Println(a1, b1)
    fmt.Println(split(15))
  2. 变量

    // var 语句定义一个变量列表;跟函数的参数列表一样,类型在后
    // var 语句可以定义在包或函数级别
    var c, python bool
    func main() {
        var i int
        fmt.Println(i, c, python)
    }
    
    // 变量可以包含初始值,如果初始化是使用表达式,则可以省略类型;变量从初始值中获取类型
    var j int = 1
    var php, golang bool = false, true
    var java = "no"  // 省略类型
    
    `:=` 短声明变量
    // 在函数中, `:=`简洁赋值语句在明确类型的地方,可以用于替代var定义。
    // 函数外的每个语句都必须以关键字开始(`var`、`func`、等等),`:=` 结构不能使用在函数外
    func test() {
        d, e := 1, 2
        fmt.Println(d, e, java)
        // 变量交换
        var d1, e1 = d, e
        fmt.Println(d1, e1)
    }
    
  3. 基本类型

    ?bool
    
    string
    
    int, uint  // 分别对应特定 CPU 平台的字长(机器字大小)
    int8  int16  int32  int64 //8、16、32、64 bit(二进制位)大小的有符号整数
    uint8 uint16 uint32 uint64 //无符号整数类型
    uintptr //它没有指定具体的 bit 大小但是足以容纳指针。uintptr 类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。
    
    //byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。
    byte // uint8 的别名
    
    rune // int32 的别名
         // 代表一个Unicode码
    
    float32  可以提供大约 6 个十进制数的精度
    float64 可以提供约 15 个十进制数的精度
    通常优先使用 float64 类型
    //常量 math.MaxFloat32 表示 float32 能取到的最大数值,大约是 3.4e38;
    //常量 math.MaxFloat64 表示 float64 能取到的最大数值,大约是 1.8e308;
    //float32 和 float64 能表示的最小值分别为 1.4e-45 和 4.9e-324。
    
    
    // 复数是由两个浮点数表示的,其中一个表示实部(real),一个表示虚部(imag)
    complex64(32 位实数和虚数) 
    complex128(64 位实数和虚数). 其中 complex128 为复数的默认类型。
    
    
    
    Go语言中有符号整数采用 2 的补码形式表示,也就是最高 bit 位用来表示符号位,一个 n-bit 的有符号数的取值范围是从 -2(n-1) 到 2(n-1)-1。
    无符号整数的所有 bit 位都用于表示非负数,取值范围是 0 到 2n-1。
    例如,int8 类型整数的取值范围是从 -128 到 127,而 uint8 类型整数的取值范围是从 0 到 255。
  4. 零值

    // 变量在定义时没有明确的初始化时会赋值为_零值_。
    //零值是:
    //    数值类型为 `0`,
    //    布尔类型为 `false`,
    //    字符串为 `""`(空字符串)
    
    
    var i float64
    var j int
    var s string
    fmt.Printf("%v %v %q\n", i, j, s)
    // 结果:0 0 ""
  5. 类型转换

    // 必须显式转换
    var i int = 42
    var f float64 = float64(i)
    var u uint = uint(f)
    // OR
    i := 42
    f := float64(i)
    u := uint(f)
    
    // 与 C 不同的是 Go 的在不同类型之间的项目赋值时需要显式转换。 试着移除例子中 float64 或 int 的转换看看会发生什么。
  6. 类型推导

    //在定义一个变量但不指定其类型时, 变量的类型由右值推导得出。
    var i int
    j := i // j 也是一个 int
    // 右边包含了未指名类型的数字常量时, 取决于常量
    i := 42           // int
    f := 3.142        // float64
    g := 0.867 + 0.5i // complex128
  7. 常量

    const 关键字:可以是字符,字符串,布尔或数字类型的值,常量类型不能用短声明变量【:=】语法定义

    // 数值常量
    const small = 1 >> 100
    
    // 常量
    const World = "世界"

流程控制语句

for

sum := 0
for i:=0; i< 10; i++ {
    sum += 1
}

// 可以让前置、后置语句为空
for sum < 100 {
    sum += sum
}

// for 是 Go 的 “while”
//死循环 ,省略了循环条件,循环就不会结束
for {}

if

var x int = -1
if x < 0 {
    fmt.Println("jj")
}

// 跟for一样,if语句可以在条件之前执行一个简单的语句
var x, y float64 = 3, 1
var lim = 10.
if v := math.Pow(x, y); v < lim {
    fmt.Println(v)
}

switch

一个结构体(struct)就是一个字段的集合
除非以 fallthrough 语句结束,否则分支会自动终止。

fmt.Print("GO RUNS ON ")
switch os := runtime.GOOS; os {
case "linux":
    fmt.Printf("linux.")
case "WINDOWS":
    fmt.Printf("windows")
default:
    fmt.Printf("%s.", os)
}
// 结果: GO RUNS ON windows.

执行顺序

switch 的条件从上到下的执行,当匹配成功的时候停止。
没有条件的 switch
switch true一样。 这一构造使得可以用更清晰的形式来编写长的 if-then-else 链。

var t int = time.Now().Hour()
println(t)  //16
switch {
case t < 17:
    fmt.Println("afternoon")
case t < 12:
    fmt.Println("morning")
default:
    fmt.Println("evening")
}

defer

defer语句会延迟函数的执行直到上层函数返回。
延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。

func test() {
    defer fmt.Println("world") // 被延迟
    fmt.Println("hello")  // 先执行
}

延迟的函数调用被压入一个栈中 。 当函数返回时, 会按照后进先出的顺序调用被延迟的函数调用。

复杂类型

指针

Go 具有指针。 指针保存了变量的内存地址 。

// 类型 `*T` 是指向类型 `T` 的值的指针。其零值是 `nil`。
var p *int
// & 符号会生成一个指向其作用对象的指针。
i := 42
p = &i
// * 符号表示指针指向的底层的值。
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21         // 通过指针 p 设置 i
fmt.Println(*p)
i = 99
fmt.Println(*p)
//这也就是通常所说的“间接引用”或“非直接引用”。
//与 C 不同,Go 没有指针运算

结构体

一个结构体(struct)就是一个字段的集合。 (type的含义跟其字面意思相符)

package main
import "fmt"

type Vertex struct {
    X int 
    Y int
}

func main() {
    fmt.Println(Vertex{1, 2})
}
// 输出: {1 2}

结构体字段使用点号来访问

func main() {
    v := Vertex{1, 2}
    v.X = 4
    fmt.Println(v.X, v.Y)
}
// 输出: 4, 2

结构体指针:结构体字段可以通过结构体指针来访问。通过指针间接的访问是透明的。

func main() {
    v := Vertex{1, 2}
    p := &v
    p.X = 1e9
    fmt.Println(*p) // 等同于fmt.Println(v)
}
// 输出:{1000000000 2}

结构体文法

结构体文法:表示通过结构体字段的值作为列表来新分配一个结构体。
使用Name语法可以仅列出部分字段。(字段名的顺序无关)。
特殊的前缀&返回一个指向结构体的指针

var (
    v1 = Vertex{1, 2} // 类型为 Vertex
    v2 = Vertex{X: 1} // Y:0被省略
    v3 = Vertex{}    // X:0 和 Y:0 省略
    p = &Vertex{1, 2} // 类型为 *Vertex
)
func main() {
    fmt.Println(v1, p)
    fmt.Println(v2, v3)
}
// 输出: {1 2} &{1 2}
//          {1 0} {0 0}

数组

类型 [n]T 是一个有 n 个类型为 T 的值的数组。 example:var a [10]int ,定义变量a 是一个有10个整数的数组
数组的长度是其类型的一部分,因此数组不能改变大小。这看起来是一个制约,但是不用担心,Go提供了更加方便的方式来使用数组。

func main() {
    var a [2]string
    a[0] = "hello"
    a[1] = "world"
    fmt.Println(a[0], a[1])
    fmt.Println(a)
}
// 输出: hello world
//        [hello world]

slice(切片)

了解更多关于slice的内容,可以阅读官方文章 slice: 用法和内部原理

切片是数组段的描述符。它由一个指向数组的指针,段的长度及其容量(段的最大长度)组成 。 长度是切片所引用的元素数 。 容量是基础数组中元素的数量(从切片指针所指的元素开始)

切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型 (有点像Python的list类型)。这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集。注意:终止索引标识的项不包含在切片内。

一个 slice 会 指向一个序列的值,并且包含了长度信息 。 []T 是一个元素类型为 T 的 slice。

func main() {
    p := []int{2, 3}
    fmt.Println("p ==", p)
    
    for i:= 0; i < len(p); i++ {
        fmt.Println("p[%d] == %d\n", i, p[i])
    }
}
/* 输出:
p == [2 3]
p[%d] == %d
 0 2
p[%d] == %d
 1 3
*/

// 也可以对数组进行切片处理
var s [4]int
s[0] = 1
s[1] = 22
s[2] = 44
s[3] = 99
fmt.Println("s[0:2]", s[0:2])
// 输出:s[0:2] [1 22]

slice切片

slice可以重新切片,创建一个新的slice值来指向相同的数组。 表达式:s[lo:hi] 从开始lo到结束hi -1slice元素,含两端。

  • s[lo:lo] 表示空
  • s[lo:lo+1]表示有一个元素
  • s[0:0]表示切片复位, 等效于空切片
p := []int{11, 32, 3, 555}
p[1:]    // 从上标到len(p)结束
p[:]
p[:2]    // 从0开始

构造slice

slice有函数make创建。这会分配一个零长度的数组并返回<u>一个slice指向这个数组</u>(引用该数组的切片);

func make([] T,len,cap)[] T
// 其中T代表要创建的切片的元素类型。该make函数具有类型,长度和可选容量(Capacity)
a :=make([]int ,5)    //如果省略Capacity参数,则默认为指定的长度
fmt.Println(a, len(a), cap(a))    // [0 0 0 0 0] 5 5

//为了指定容量,可传递第三个参数到 `make`:
b := make([]int, 0, 5)
fmt.Println(b, len(b), cap(b)) //[] 0 5
c := b[:2]
fmt.Println(c, len(c), cap(c))    //[0 0] 2 5
// cap(x) :the maximum length the slice can reach when resliced
d := c[1:cap(c)]
fmt.Println(d, len(d), cap(d))    //[0 0 0 0] 4 4

slice的零值是nil。一个nilslice的长度和容积是0

var z []int
fmt.Println(z, len(z), cap(z))    
if z == nil {
    fmt.Println("nil!")
}
//输出
/* 
[] 0 0
nil!
*/

向 slice 添加元素

向 slice 添加元素: 添加元素是一种常见的操作,因此 Go 提供了一个内建函数 append

func append(s [], elems ...T) []T
// append 的第一个参数 s 是一个类型为 T 的数组,其余类型为 T 的值将会添加到 slice
// 如果 s 的底层数组太小,而不能容纳所有值时,会分配一个更大的数组。返回的 slice 会指向这个新分配的数组。
p := []int{2, 3, 51, 44, 532}
p1 := []int{1, 3, 34}
p = append(p, p1...) // 等同于append(p, p1[0], p1[1], p1[2])
fmt.Println(p)    // [2 3 51 44 532 1 3 34]

var a []int
a1 = append(a, 0)
fmt.Println(a)    // [0]

range

for 循环的 range 格式可以对 slice 或者 map 进行迭代循环 。

var pow = []int{1, 2, 4, 8, 16, 32}
for i, v := range pow {
    fmt.Println("2**%d = %d\n", i, v)
}
/*
2**%d = %d
 0 1
2**%d = %d
 1 2
.... 
*/

可以通过赋值给 _ 来忽略序号和值。 如果只需要索引值,去掉“, value”的部分即可。

pow := make([]int, 10)
for i := range pow {
    pow[i] = 1 << uint(i)
}
for _, value := range pow {
    fmt.Printf("%d\n", value)
}

map

map映射键到值。
map在使用之前必须用make而不是new来创建;值为nilmap是空的,并且不能赋值。

type Vertex struct {
    lat, long float64
}
var m map[string]Vertex

m = make(map[string]Vertex)
m["Bei Jin"] = Vertex{
    lat:  40.68433,
    long: 74.46454,
}
fmt.Println(m)
fmt.Println(m["Bei Jin"])

map的文法

map的文法与结构体文法类型,不过必须有键名

type Vertex struct {
    lat, long float64
}
var m =  map[string]Vertex{
    "Bei Jin": {
        lat:  40.68433,
        long: -74.46454,
    },
    "Chang Zhou": {
        lat:  37.4512,
        long: -122.08408,
    },
}

fmt.Println(m)
fmt.Println(m["Bei Jin"])

修改map

  • map中插入或修改一个元素

m[key] = elem

  • 获取元素

elem = m[key]

  • 删除元素

delete(m, key)

  • 通过双赋值检测某个键存在

var elem, ok = m[key]

如果 keym 中,oktrue 。否则, okfalse,并且 elem 是 map 的元素类型的零值

同样的,当从 map 中读取某个不存在的键时,结果是 map 的元素类型的零值。

m := make(map[string]int)
m["key"] = 12
fmt.Println("插入元素", m)    // map[key:12]
m["key"] = 99
fmt.Println("修改元素", m)    // map[key:99]

a := m["key"]
fmt.Println("获取元素", a) // 99

delete(m, "key")
fmt.Println("删除元素", m)     //  map[]

var elem, ok = m["key"]
fmt.Println("检测某个键存在",elem, ok)    // 0 false

函数值

函数也是值

hook := func(x, y int) int {
    return (x + y) * 2
}

fmt.Println(hook(1, 3))

函数的闭包

Go 函数可以是闭包的。闭包是一个函数值,它来自函数体的外部的变量引用。 函数可以对这个引用值进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。

// 例如,函数 adder 返回一个闭包。每个闭包都被绑定到其各自的 sum 变量上。
func adder() func(int) int {
    sum :=0
    return func(x int) int {
        sum = 2 * x
        return sum
    }
}

func main() {
    pos := adder()
     fmt.Println(pos(99))
}

方法和接口

学习如何为类型定义方法;如何定义接口;以及如何将所有内容贯通起来。

Go 没有类。然而,仍然可以在结构体类型上定义方法
方法接收者 出现在func关键字和方法名之间的参数中

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v:= &Vertex{3, 4}    // &{3 4}
    fmt.Println(v.Abs()) // 5
}

你可以对包中的 <u>任意 类型定义任意方法</u>,而不仅仅是针对结构体。
但是,不能对来自其他包的类型或基础类型定义方法。

type MyFloat float64

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

func main() {
    v:= MyFloat(-math.Sqrt2)
    fmt.Println(v)    //-1.4142135623730951
    fmt.Println(v.Abs())    //1.4142135623730951
}

接收者为指针的方法

方法可以与命名类型或命名类型的指针关联。
刚刚看到的两个 Abs 方法。一个是在 *Vertex 指针类型上,而另一个在 MyFloat 值类型上。 有两个原因需要使用指针接收者。首先避免在每个方法调用中拷贝值(如果值类型是大的结构体的话会更有效率)。其次,方法可以修改接收者指向的值。

尝试修改 Abs 的定义,同时 Scale 方法使用 Vertex 代替 *Vertex 作为接收者。
vVertex 的时候 Scale 方法没有任何作用。Scale 修改 v。当 v 是一个值(非指针),方法看到的是 Vertex 的副本,并且无法修改原始值。

Abs 的工作方式是一样的。只不过,仅仅读取 v。所以读取的是原始值(通过指针)还是那个值的副本并没有关系。

type Vertex struct {
    X, Y float64
}

func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v:= &Vertex{3, 4}
    v.Scale(5.)
    fmt.Println(v) // &{15, 20}
    fmt.Println(v.Abs()) // 25
}

接口

接口类型是由一组方法定义的集合。
接口类型的值可以存放实现这些方法的任何值。
`type 接口类型名 interface{

方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2

}`

type MyFloat float64
func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

type Vertex struct {
    X, Y float64
}
func (v *Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

// 接口定义
type Abser interface {
    Abs() float64
}

func main() {
    var a Abser
    f := MyFloat(-math.Sqrt2)
    v := Vertex{3, 4}

    a = f    // a MyFloat 实现了 Abser
    a = &v    // a *Vertex 实现了Abser

    /*
    下面一行, v 是一个Vertex(而不是 *Vertex)
    所以没有实现 Abser
     */
    //a = v
    fmt.Println(a.Abs())
}

隐式接口

类型通过实现那些方法来实现接口。 没有显式声明的必要;所以也就没有关键字“implements“。

隐式接口解藕了实现接口的包和定义接口的包:互不依赖。

因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。

Stringers

一个普遍存在的接口是fmt包中定义的StringerStringer 是一个可以用字符串描述自己的类型 。
`type Stringer struct {

String() string

}`

type Person struct {
    Name string
    Age  int
}

// 我目前的理解是自定义的Person类型String 覆盖了包 fmt 中定义的String
func (p Person) String() string {
    return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
    a := Person{"mp", 42}
    z := Person{"xx", 12}
    
    fmt.Println(a, z)
}

错误

Go程序使用erro值来表示错误状态。与fmt.String类似,error类型是一个内建接口;
`type error interface{

Error() string

}`
通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 nil, 来进行错误处理。 error 为 nil 时表示成功;非 nil 的 error 表示错误。

type Myerror struct {
    When  time.Time
    What string
}

func (e *Myerror) Error() string  {
    return fmt.Sprintf("at %v, %s", e.When, e.What)
}

func run() error  {
    return &Myerror{time.Now(), "it didn't work."}
}

func main() {
    //hello.Test("b.go is package main.")

    err := run()
    if err != nil {
        fmt.Println(err)
    }
}

并发

作为语言的核心部分,Go 提供了并发的特性。
这一部分 概览了 goroutein 和 channel,以及如何使用它们来实现不同的并发模式。

goroutine

goroutine 是由 Go 运行时环境管理的轻量级线程。 goroutine 在相同的地址空间中运行,因此访问共享内存必须进行同步。sync 提供了这种可能,不过在 Go 中并不经常用到,因为有其他的办法

go f(x, y, z)

channel

channel 是有类型的管道,可以用 channel 操作符 <- 对其发送或者接收值

ch <- v    // 将 v 送入 channel ch。
v := <-ch  // 从 ch 接收,并且赋值给 v。

channel 使用前必须创建: ch := make(chan int)

channel 可以是 _带缓冲的_。为 make 提供第二个参数作为缓冲长度来初始化一个缓冲 channel: ch := make(chan int, 100)
向缓冲 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。当缓冲区清空的时候接受阻塞。

注意: 只有发送者才能关闭 channel,而不是接收者。

func test(n int, c chan int) {
    x, y := 0, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x+y
    }
    close(c)
}

func main() {
    //hello.Test("b.go is package main.")

    c := make(chan int, 10)

    go test(cap(c), c)
    //循环不断从channel中接收值,直到它关闭 
    for i := range c{
        fmt.Println(i)
    }
    // 发送完后, 检查channel通道是否关闭
    v, ok := <-c
    fmt.Println(v, ok) // 0 , false
}

select

没弄明白原理
select 语句使得一个 goroutine 在多个通讯操作上等待。 select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支 。

为了非阻塞的发送或者接收,可使用 default 分支 。 当 select 中的其他条件分支都没有准备好的时候,default 分支会被执行。

func fib(c, quit chan int)  {
    var x, y = 0, 1
    for  {
        select {
        case c <- x:
            x, y = y, x+y
        case <- quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    //hello.Test("b.go is package main.")

    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()

    fib(c, quit)
}

标签: none

添加新评论