189 8069 5689

Go语言1

开发环境搭建

随便学学,平时用的都是WIndows10的操作系统,就在这下面搞了。

我们一直强调成都网站制作、成都网站设计、外贸营销网站建设对于企业的重要性,如果您也觉得重要,那么就需要我们慎重对待,选择一个安全靠谱的网站建设公司,企业网站我们建议是要么不做,要么就做好,让网站能真正成为企业发展过程中的有力推手。专业网站建设公司不一定是大公司,成都创新互联作为专业的网络公司选择我们就是放心。

下载安装

下载go安装程序,下载地址:https://golang.org/dl/ (被墙了,打不开)
墙内下载地址http://www.golangtc.com/download
我下的是这个:go1.9.2.windows-amd64.msi
安装就下一步就好了,装完之后验证一下。默认已经把Go的路径加到环境变量中去了,所以可以直接在cmd中输入go命令执行:
Go语言1

环境变量

装完之后,我装到了D:\Go\,已经帮我们添加好了2个环境变量:

  • GOROOT: D:\Go\
  • Path: D:\Go\bin
  • GOPATH: 这个环境变量也要,但是需要手动设置

GOPATH也是要的,设置工作区路径,不过如果没有设置的话,有一个默认位置(差不多就是系统的家目录)。执行帮助命令 go help gopath ,我这截了一段说明:

If the environment variable is unset, GOPATH defaults
to a subdirectory named "go" in the user's home directory
($HOME/go on Unix, %USERPROFILE%\go on Windows),
unless that directory holds a Go distribution.
Run "go env GOPATH" to see the current GOPATH.

暂时不设置也没影响,反正也有默认的,后面写 Hellow World 的时候再看设置在哪个目录

开发工具

开发工具先没有装,不过这里先记一下工具的名称:

Visual Studio Code (简称 VS Code / VSC) 是一款免费开源的现代化轻量级代码编辑器,支持几乎所有主流的开发语言的语法高亮、智能代码补全、自定义热键、括号匹配、代码片段、代码对比 Diff、GIT 等特性,支持插件扩展,并针对网页开发和云端应用开发做了优化。软件跨平台支持 Win、Mac 以及 Linux。

这是一个通用的代码编辑器,装完之后要再装一下go的扩展:

  1. 点开,菜单-查看>>扩展
  2. 搜索“go”,找到“Rich Go language support for Visual Studio Code”,作者“lukehoban”,安装
  3. 装好之后用的时候

调试工具

Delve是一个非常棒的golang调试工具,支持多种调试方式,直接运行调试,或者attach到一个正在运行中的golang程序,进行调试。
安装Delve非常简单,直接运行 go get 即可:

go get -u github.com/derekparker/delve/cmd/dlv

先记一下,我并没有装

写代码

在第一次写代码前,还要先把go的工作区设置好。

设置工作区

工作区是存放go源码文件的目录。一般情况,Go源码文件都需要存放到工作区中,不过命令源码文档不是必须的。
每个工作区的目录结构都类似,有3个文件夹:

  • src/: 用于存放源码以代码包为组织形式
  • pkg/: 用于存放归档文件(名称以.a为后缀的文件)
  • bin/: 用于存放当前工作区中的GO程序的可执行文件

找一个合适的位置,作为工作区。比如:E:\Go。这时就可以去把GOPATH环境变量设置为 “E:\Go” 。
按上面说的,还要工作区中还要创建3个文件夹,暂时只需要src,另外两个暂时不用。

Hello World

到这里需要一些仪式感,就是写一个 Hellow World 。在src目录下创建文件 “hellow.go” 。如果用了VS Code,会提示你装一些扩展,点下Install All,这样会自动把一些go的插件给装好。
下面是程序的源码:

package main

import(
    "fmt"
)

func main() {
    fmt.Println("Hello World")
}

第一部分,声明这是一个包。可执行文件的包名必须是“main”。
第二部分,导入包。所有的库,包括自己的库,用之前都需要导入。fmt是包名,里面有一个方法可以向终端打印信息。
第三部分,定义了一个函数。必须要定义一个main函数,这是个入口函数。
最后在cmd命令行里运行一下:

go run hello.go

做加法

这次写一个有参数有返回值的函数:

package main

import(
    "fmt"
)

func add(a int, b int) int {
    var sum int
    sum = a + b
    return sum
}

func main(){
    var c int
    c = add(11, 22)
    fmt.Println("11 + 22 =", c)
}

go是一个强类型语言,所有变量都要设置类型。
add函数,写参数的时候,参数后面要用类型。并且由于add函数有返回值,返回值的类型也要在函数名后面定义好。
声明变量用var,后面是变量名,在后面是变量的类型。
最后如果你的变量定义之后,没有被应用,也是会报错的。这样强制你去掉冗余的代码,这个变量既然没有用到,自然就不需要出现,比如把最后一句fmt.Println(c)去掉或者换成打印一个固定的字符串就会报错。

golang语言特性

  1. 垃圾回收
    • 内存自动回收,不需要开发人员管理内存
    • 开发人员专注业务实现,减轻负担
    • 只需要new分配内存,不需要释放
  2. 天然并发
    • 从语言层面支持并发,非常简单
    • goroute,轻量级线程,创建成千上万个goroute成为可能
    • 基于CSP(Communicating Sequential Process)模型实现
  3. channel
    • 管道,类似Linux中的pipe
    • 多个goroute之间通过channel进行通信
    • 支持任何类型
  4. 多返回值
    • 一个函数返回多个值

并发代码演示

先写一个程序goroute_print.go,很简单,就是传入参数稍微计算一下,然后打印结果:

package main

import (
    "fmt"
)

func goroute_print(a int) {
    b := a + 10000
    fmt.Println(b)
}

这里用了 “:=” ,是声明并赋值,并且系统自动推断类型,不需要var关键字。
然后再写一个程序goroute_for.go,里面是一个for循环,起若干个线程执行:

package main

import(
    "time"
)

func main(){
    for i := 0; i < 1000; i++ {
        go goroute_print(i)
    }

    // sleep1秒,保证上面的子线程运行结束
    time.Sleep(time.Second * 5)
}

在这个主程序要里调用另外一个文件里的程序。不过这2个文件都是同一个包,main包,开头package声明的。这里在前面加上了go调用,就是起了一个goroute。(不加go调用,就是在主线程里调用函数,如果调用前加上go,就是起了一个子线程调用这个函数)
主线程必须要等待子线程运行结束才能退出,否则主线程一旦结束,其他子线程也就没有了。这里简单粗暴的先用sleep来进行等待。导入了一个time包。参数 time.Second 就是1秒,如果不是等1秒而是几秒的话,就乘以几。
这里还有注释,注释的用法js一样。单行注释以 // 开头,多行注释以 /* 开始,以 */ 结尾。

执行的话在cmd命令行执行下面的命令:

go run goroute_for.go goroute_print.go

这个例子分在两个文件里了,所以run后面把两个文件都要写上。顺序是无所谓的。

管道代码示例

这里用了make()函数,是用来分配内存的。第一个参数 chan int ,表示要创建的是chan(管道),类型是int:

package main

import(
    "fmt"
)

func main(){
    pipe := make(chan int, 3)  // 创建一个管道,存的数据类型是int,容量是3
    pipe <- 11  // 往管道里存一个数
    fmt.Println(11, len(pipe))  // 打印长度
    pipe <- 22
    fmt.Println(22, len(pipe))

    var t1 int
    t1 =<- pipe  // 从管道里取出一个数,赋值给t1
    fmt.Println(t1, len(pipe))
    t1 =<- pipe
    fmt.Println(t1, len(pipe))
}

似乎就是个队列,先进先出,默认满了也会阻塞。之所以叫管道不叫队列,可能是它能支持多个goroute之间通过channel(管道)进行通信。暂时不需要这么深入。

通过管道在goroute之间传递参数

有两种方式可以实现。把管道定义为全局变量,或者把管道作为函数的参数传递。代码中使用全局变量不是好的做法,所以推荐用传参的方法。不过两种实现方法都看一下。
全局变量的代码示例:

package main

import(
    "fmt"
)

var pipe chan int  // 在全局声明pipe变量,下面两个方法里都会用到

func add(a int, b int){
    sum := a + b
    pipe <- sum
}

func main(){
    pipe = make(chan int, 1)
    go add(1, 2)
    sum :=<- pipe
    fmt.Println("sum =", sum)
}

传递参数的代码示例:

package main

import(
    "fmt"
)

func add(a int, b int, c chan int){
    sum := a + b
    c <- sum
}

func main(){
    var pipe chan int  // 在main函数里声明pipe
    pipe = make(chan int, 1)
    go add(3, 4, pipe)
    sum :=<- pipe
    fmt.Println("sum =", sum)
}

多返回值的代码示例

package main

import(
    "fmt"
)

func calc(a int, b int)(int, int){
    c1 := a + b
    c2 := (a + b)/2
    return c1, c2
}

func main(){
    sum , avg := calc(100, 200)
    fmt.Println("sum =", sum, "avg =", avg)
}

如果多返回值里只想取其中部分的值,可以把剩下的值传给占位符(“_”):

package main

import(
    "fmt"
)

func calc(a int, b int)(int, int){
    c1 := a + b
    c2 := (a + b)/2
    return c1, c2
}

func main(){
    sum , _ := calc(100, 200)
    fmt.Println("sum =", sum)
    fmt.Println(_)  // 打印下划线会报错
}

尝试打印下划线的报错信息如下:

>go run calc.go
# command-line-arguments
.\calc.go:17:16: cannot use _ as value

代码格式化(gofmt)

go默认提供一个代码格式化命令 gofmt ,会按照Go语言代码规范格式化文件中的代码。把之前的hello.go的代码搞乱一点,简单一点就改下缩进好了,然后运行下面的命令:

gofmt hello.go

最终还是能再命令行里看到整洁的代码
Go语言1
还截图的效果,缩进是8个字符的位置。
不过原文件里还是乱糟糟的,可以加上-w参数,改写原文件:

gofmt -w hello.go

实际写到文件里,缩进用的不是空格是Tab,具体效果是几个字符就看文件的设置了。
另外换行是 \n ,这个windows的记事本下会用问题,记事本的换行是 \r\n

编译Go源文件

创建Go工作目录:H:\Go\src\go_dev\day1\hello\。把之前写的示例hello.go放到hello目录下。再建个文件夹,其他示例的源文件放到examples目录下。这里src文件夹的名字是不能变的,其他文件夹的名字包括上级的都随意。
上面的演示都没有对go的原文件进行编译,编译要用 go build 命令:

H:\Go>go build go_dev\day1\hello
can't load package: package go_dev/day1/hello: cannot find package "go_dev/day1/hello" in any of:
        D:\Go\src\go_dev\day1\hello (from $GOROOT)
        C:\Users\Steed\go\src\go_dev\day1\hello (from $GOPATH)

H:\Go>

这里并没有编译成功,原因是找不到我们的源文件。之前因为没设置环境变量GOPATH,所以默认没有设置GOPATH就是把用户家目录作为GOPATH。这个GOPATH就是工作目录。
先去把GOPATH的环境变量设置好,我这里 “H:\Go\“ 就是我的工作目录,设置成:“H:\Go\“ 。建议设置用户变量,当然系统变量也没问题。
完成之后要新开一个cmd,也有可能有要注销当前用户,下次登录才能完全生效。然后可以验证一下:

C:\Users\Steed>go env GOPATH
H:\Go\

然后再编译一下,这次随便找个目录执行命令:

J:\tmp>go build go_dev\day1\hello

J:\tmp>hello.exe
Hello World

命令执行后会在当前目录下生成hello.exe的可执行文件,上面也直接执行了一下。文件名以及生成文件的路径都是可以用参数指定的。
前面只所以不把所有的示例都放一起,是因为其他示例也有main入口函数,存在多个入口函数编译会报错。
其实可以编译单个文件,但是路径要写全(绝对路径或相对路径),单文件编译似乎不受环境变量影响:

J:\tmp>go build H:\Go\src\go_dev\day1\hello\hello.go

复杂一点的程序一般都不是单个文件的,所以一个文件夹里就是一个小项目,只有一个入口函数。这样编译不会有问题。另外如果使用开发工具的话,这种情况开发工具直接也会提示错误的。
最后,下面这么搞比较方便:

H:\Go\src>go build go_dev\day1\hello

H:\Go\src>go build go_dev\day1\hello\hello.go

选好cmd的启动路径,之后源文件的路径用Tab就能自动补全出来了。

包的概念

代码不能单独存在,任何代码都属于一个包。所以第一行代码,一定写的是package。

  1. 把相同功能的代码放到一个目录,称之为包。
  2. 包可以被其他包调用。
  3. main包是用来生成可执行文件的,没个程序只有一个main包
  4. 包的主要用途是提高代码的可复用性。

使用多个包的代码示例

新建个文件夹package_exampl,写一个由多个包多个文件构成的代码,具体如下:

// go_dev/day1/package_example/calc/add.go
package calc

func Add(x int, y int) int {
    return x + y
}

// go_dev/day1/package_example/calc/sub.go
package calc

func Sub(x int, y int) int {
    return x - y
}

// go_dev/day1/package_example/main/main.go
package main

import(
    // "calc"
    "go_dev/day1/package_example/calc"
    "fmt"
)

func main(){
    sum := calc.Add(100, 200)
    sub := calc.Sub(10, 5)
    fmt.Println(sum, sub)
}

calc包里的Add和Sub函数都是首字母大写的。在Go语言里首字母大写有特殊的意义,就是全局变量。如果都小写的话,在main函数里这两个函数,由于作用域的问题,都是不可见的。
结论:就是说一个变量(包括函数),如果要被外部的包调用,必须要首字母大写。不过变量不推荐这么做,因为这样这个变量就是全局变量,而全局变量是不推荐使用的方法(别的语言里也同样是不推荐使用全局变量的)。
main函数里导入包的时候,calc包的路径要写全,不能只写 “calc” ,下面会具体分析。
编译过程
先把import改成这样,一步一步排错:

import(
    "calc"
    // "go_dev/day1/package_example/calc"
    "fmt"
)

多个包的编译

H:\Go\src>go build go_dev\day1\package_example
can't load package: package go_dev/day1/package_example: no Go files in H:\Go\src\go_dev\day1\package_example

这里报错,因为文件夹下确实没有包,包的源文件在下一级的目录里:

H:\Go\src>go build go_dev\day1\package_example\main
go_dev\day1\package_example\main\main.go:4:5: cannot find package "calc" in any of:
        D:\Go\src\calc (from $GOROOT)
        H:\Go\src\calc (from $GOPATH)

继续报错,这是找不到calc包,并且这里也看到了默认查找包的路径。现在包的路径应该怎么写全就清除了,把import的内容改回来,之后就能顺利的编译执行了:

H:\Go\src>go build go_dev\day1\package_example\main

H:\Go\src>main.exe
300 5

指定路径和文件名
一般编译后生成的可执行文件是放在工作目录下的bin目录下的。还是用go build命令,加上-o参数,windows系统下得加上扩展名exe之后才能执行:

H:\Go\src>go build -o ../bin/test.exe go_dev\day1\package_example\main

H:\Go\src>cd ../bin

H:\Go\bin>test
300 5

H:\Go\bin>

线程和管道的代码示例

程序分成2部分。一部分是业务逻辑,放在goroute目录下。还有一部分是函数的入口,放在main目录下。文件结构:

H:\GO\SRC\GO_DEV\DAY1\GOROUTE_EXAMPLE
├─goroute
└─main

业务逻辑就是,获取3个变量,2个数和1个管道,把2个数的和计算后,存到管道里。
主函数里则是,单独起一个线程goroute进行计算,之后从管道里获取到计算结果,打印出来。
这个例子里用到了线程goroute,以及管道用于goroute之间的通信。之前已经演示过了,这里就是把代码分成2个包再写一遍:

// goroute/add.go
package goroute

func Add(a int, b int, c chan int){
    sum := a + b
    c <- sum
}

// goroute/main.go
package main

import(
    "go_dev/day1/goroute_example/goroute"
    "fmt"
)

func main(){
    var pipe chan int
    pipe = make(chan int, 1)

    go goroute.Add(100, 200, pipe)
    res := <- pipe
    fmt.Println(res)    
}

官网 Go 指南

都是中文版的。
Go 语言之旅:https://tour.go-zh.org/welcome/1
Go 编程语言:https://go-zh.org/
Go 语言之旅可以作为入门教程,并且可以Web在线写代码,提交执行在Web上显示结果。

课后作业

使用fmt分别打印字符串、二进制、十进制、十六进制、浮点数。


分享文章:Go语言1
当前链接:http://jkwzsj.com/article/pihjgo.html

其他资讯