Go_入坑笔记 - Go语言中文社区

Go_入坑笔记


Go_入坑笔记


环境配置

windows

下载及其文档 : https://golang.org/doc/install?download=go1.10.3.windows-amd64.zip

vscode 配置 go 及其 下载失败解决办法: https://blog.csdn.net/Yo_oYgo/article/details/79065966

debugger : https://zhuanlan.zhihu.com/p/26473355


基础语法教程

菜鸟教程: http://www.runoob.com/go/go-environment.html


对golang多核编程的一点了解

参考:


编码规范


构建, 打包

go build

通过go build加上要编译的Go源文件名,我们即可得到一个可执行文件,默认情况下这个文件的名字为源文件名字去掉.go后缀。

$ go build  hellogo.go
$ ls
hellogo* hellogo.go

当然我们也 可以通过-o选项来指定其他名字:

$ go build -o myfirstgo hellogo.go
$ ls
myfirstgo* hellogo.go

如果我们在go-examples目录下直接执行go build命令,后面不带文件名,我们将得到一个与目录名同名的可执行文件:

$ go build
$ ls
go-examples* hellogo.go

go install

与build命令相比,install命令在编译源码后还会将可执行文件或库文件安装到约定的目录下。

  • go install编译出的可执行文件以其所在目录名(DIR)命名
  • go install将可执行文件安装到与src同级别的bin目录下,bin目录由go install自动创建
  • go install将可执行文件依赖的各种package编译后,放在与src同级别的pkg目录下.

参考资料:


面向对象编程


package

  • 同一个目录下不能存在不同包名的文件

  • import 别的包规则

    package main
    
    import (
    pkg001 "GoLab/test_pkg/pkg001" // 重命名别名为 pkg001 在本文件中的使用, 一般不要这样干
    _ "GoLab/test_pkg/pkg002" // _ 防止 未被使用的包, 被格式化代码时被编辑器自动干掉这一行
    "fmt" // 导入内置包
    )
    • 包名路径: 是 GOPATH/src 为基础搜索.
  • import 的流程. 参考: https://blog.csdn.net/zhangzhebjut/article/details/25564457

    程序的初始化和执行都起始于main包。如果main包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到fmt包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数(如果存在的话),最后执行main函数。下图详细地解释了整个执行过程:

    func init() { // 是保留的内置方法, import时自动执行
    fmt.Println("--- init")
    }

空结构体

empty := struct{}{}
    println("empty len:", unsafe.Sizeof(empty)) // 0, 空结构体的长度为 0, 常用于 map里面做value值, 因为go里面集合没有set, 所以用map变相做set

Go 关键字和 channel 的用法

make

golang分配内存有一个make函数,该函数第一个参数是类型,第二个参数是分配的空间,第三个参数是预留分配空间. 例如a:=make([]int, 5, 10), len(a)输出结果是5,cap(a)输出结果是10,然后对a[4]进行赋值发现是可以得,但对a[5]进行赋值发现报错了,于是郁闷这个预留分配的空间要怎么使用呢,于是google了一下发现原来预留的空间需要重新切片才可以使用,于是做一下记录,代码如下。

func main(){
    a := make([]int, 10, 20)
    fmt.Printf("%d, %dn", len(a), cap(a))
    fmt.Println(a)
    b := a[:cap(a)]
    fmt.Println(b)
}
/*
10, 20
[0 0 0 0 0 0 0 0 0 0]
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
*/

make()分配:内部函数 make(T, args) 的服务目的和 new(T) 不同。
它只生成切片,映射和程道,并返回一个初始化的(不是零)的,type T的,不是 *T 的值。
这种区分的原因是,这三种类型的数据结构必须在使用前初始化.
比如切片是一个三项的描述符,包含数据指针(数组内),长度,和容量;在这些项初始化前,切片为 nil 。
对于切片、映射和程道,make初始化内部数据结构,并准备要用的值。
记住 make() 只用于 映射、切片、程道,不返回指针。要明确的得到指针用 new() 分配。

go 关键字用来创建 goroutine (协程),是实现并发的关键。go 关键字的用法如下:

//go 关键字放在方法调用前新建一个 goroutine 并让他执行方法体
go GetThingDone(param1, param2);

//上例的变种,新建一个匿名方法并执行
go func(param1, param2) {
}(val1, val2)

//直接新建一个 goroutine 并在 goroutine 中执行代码块
go {
    //do someting...
}

因为 goroutine 在多核 cpu 环境下是并行的。如果代码块在多个 goroutine 中执行,我们就实现了代码并行。那么问题来了,怎么拿到并行的结果呢?这就得用 channel 了。

//resultChan 是一个 int 类型的 channel。类似一个信封,里面放的是 int 类型的值。
var resultChan chan int
//将 123 放到这个信封里面,供别人从信封中取用
resultChan <- 123
//从 resultChan 中取值。这个时候 result := 123
result := <- resultChan

channel 详解

chan 是信号的关键字, 作用有点像c++多线程里面的 signal, 发送信号给其他线程, 通知其可以继续往下跑了.
在 go 里 chan 的使用是结合了 go,select 实现了 goroutine, 多并发.

func test_chan03() {
    c1 := make(chan string) // 声明 信号
    c2 := make(chan string)

    go func() { // go 关键字, 新建一个协程跑这个方法
        time.Sleep(time.Second * 1)
        c1 <- "one" // 往 c1 信号中丢数据, 也就是通知 c1 阻塞的地方可以继续跑了
    }()
    go func() {
        time.Sleep(time.Second * 2)
        c2 <- "two"
    }()

    for i := 0; i < 2; i++ {
        select {
        case msg1 := <-c1: // 阻塞, 等待 c1 信号通知, 如果收到通知, 这跑这个case, 并把数据丢该 msg1
            fmt.Println("received", msg1)
        case msg2 := <-c2:
            fmt.Println("received", msg2)
        }
    }
    fmt.Println("程序结束666")
    /*
        msg1 := <-c1 表示 阻塞, 等待c1信号通知, 收到通知后把数据 赋值给 msg1. 如果不需要信号中的数据, 可以可以这样写 <-c1

        c1 <- "one" 表示 通知 c1 信号阻塞的地方可以继续运行了, 并往里面丢了一个数据 "one"
    */
}

channel 读写加持

chan 在函数中定义形参是时可以指定是 读写,只读,只写 三个形式, 作用与 c++ 中 const关键字 差不多

fnRW := func(c chan int) { // c可以读写
    c <- 6
    val := <-c
    fmt.Println("val:", val)
}

fnR := func(c <-chan int) { // c只读
    // c <- 6 // 报错: send to receive-only type <-chan int
    val := <-c
    fmt.Println("val:", val)
}

fnW := func(c chan<- int) { // c只写
    // <-c // 报错: receive from send-only type chan<- int
    c <- 6
}

参考: https://www.cnblogs.com/baiyuxiong/p/4545028.html


defer

defer 的思想类似于C++中的析构函数,不过Go语言中“析构”的不是对象,而是函数,defer就是用来添加函数结束时执行的语句。注意这里强调的是添加,而不是指定,因为不同于C++中的析构函数是静态的,Go中的defer是动态的
defer 中使用匿名函数依然是一个闭包。

func test_defer() {
    x, y := 1, 2
    defer func(a int) {
        fmt.Printf("x:%d,y:%dn", a, y) // y 为闭包引用
    }(x) // 复制 x 的值
    x += 100
    y += 100
    fmt.Println(x, y)
}

defer 还有一个重要的作用是用于 panic 时的 恢复, panic 恢复也只能在 defer 中.
参考: http://wiki.jikexueyuan.com/project/the-way-to-go/13.3.html


Go 错误处理

Go 语言通过内置的错误接口提供了非常简单的错误处理机制。
error类型是一个接口类型,这是它的定义:

type error interface {
    Error() string
}

我们可以在编码中通过实现 error 接口类型来生成错误信息。
函数通常在最后的返回值中返回错误信息。使用errors.New 可返回一个错误信息:

func Sqrt(f float64) (float64, error) {
    if f < 0 {
        return 0, errors.New("math: square root of negative number")
    }
    // 实现
}

在下面的例子中,我们在调用Sqrt的时候传递的一个负数,然后就得到了non-nil的error对象,将此对象与nil比较,结果为true,所以fmt.Println(fmt包在处理error时会调用Error方法)被调用,以输出错误,请看下面调用的示例代码:

result, err:= Sqrt(-1)

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

select 语句的行为

// https://talks.golang.org/2012/concurrency.slide#32
select {
case v1 := <-c1:
    fmt.Printf("received %v from c1n", v1)
case v2 := <-c2:
    fmt.Printf("received %v from c2n", v1)
case c3 <- 23:
    fmt.Printf("sent %v to c3n", 23)
default:
    fmt.Printf("no one was ready to communicaten")
}

上面这段代码中,select 语句有四个 case 子语句,前两个是 receive 操作,第三个是 send 操作,最后一个是默认操作。代码执行到 select 时,case 语句会按照源代码的顺序被评估,且只评估一次,评估的结果会出现下面这几种情况:

  1. 除 default 外,如果只有一个 case 语句评估通过,那么就执行这个case里的语句;
  2. 除 default 外,如果有多个 case 语句评估通过,那么通过伪随机的方式随机选一个;
  3. 如果 default 外的 case 语句都没有通过评估,那么执行 default 里的语句;
  4. 如果没有 default,那么 代码块会被阻塞,指导有一个 case 通过评估;否则一直阻塞

以下描述了 select 语句的语法:

  • 每个case都必须是一个通信

  • 所有channel表达式都会被求值

  • 所有被发送的表达式都会被求值

  • 如果任意某个通信可以进行,它就执行;其他被忽略。

  • 如果有多个case都可以运行,Select会随机公平地选出一个执行。其他不会执行。

    否则:

    • 如果有default子句,则执行该语句。
    • 如果没有default字句,select将阻塞,直到某个通信可以运行;Go不会重新对channel或值进行求值。

接口 interface

Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口。

/* 定义接口 */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

/* 定义结构体 */
type struct_name struct {
   /* variables */
}

/* 实现接口方法 */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* 方法实现 */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* 方法实现*/
} 

protobuf 使用

参考: https://blog.csdn.net/wangshubo1989/article/details/72478014

报错: 语法没有指定
-> 在 test.proto 指定语法

syntax = "proto2";
package goprotobuf;

报错: –go_out: protoc-gen-go
-> 需要将 go get -u github.com/golang/protobuf/protoc-gen-go 下载的 protoc-gen-go.exe 所在目录加入环境变量


优秀开源项目


docker go环境

官网地址: /golang/”>https://hub.docker.com//golang/

容器内默认工作区是 /go , 所以可以挂载到 /go/src 目录下

常见问题

  • ssh 远程连进去, 不知道为啥 没有go指令, 需要自己添加到环境变量中 .bash_profile

    export PATH=$PATH:/usr/local/go/bin
    GO_BIN=/usr/local/go/bin
    export GO_BIN

    然后使其生效 # source .bash_profile


ubuntu 安装 go

  1. 下载 go1.10.3.linux-amd64.tar , 地址:https://golang.google.cn/dl/

  2. 解压: # tar zxvf go1.10.3.linux-amd64.tar.gz -C /usr/local

  3. 增加环境变量

    
    # vi ~/.bash_profile
    
    ...
    export GOROOT=/usr/local/go
    export GOPATH=/mytemp/GoLab # 项目地址
    export PATH=$PATH:$GOPATH:/usr/local/go/bin
    
    
    # source ~/.bash_profile # 使其生效
    
  4. 查看命名, ok

    
    # go version
    
    go version go1.10.3 linux/amd64

常见编译报错

  • Q: can’t load package: package test: found packages main (base.go) and testgo (test_go.go) in E:GoLabsrctest

    A: 同一个目录下不能存在不同包名的文件

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/yangxuan0261/article/details/81810395
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2019-08-27 23:29:05
  • 阅读 ( 1431 )
  • 分类:Go

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢