Go语言1

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

内容简介:随便学学,平时用的都是WIndows10的操作系统,就在这下面搞了。装完之后,我装到了D:\Go\,已经帮我们添加好了2个环境变量:GOPATH也是要的,设置工作区路径,不过如果没有设置的话,有一个默认位置(差不多就是系统的家目录)。执行帮助命令

开发环境搭建

随便学学,平时用的都是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分别打印字符串、二进制、十进制、十六进制、浮点数。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

Purely Functional Data Structures

Purely Functional Data Structures

Chris Okasaki / Cambridge University Press / 1999-6-13 / USD 49.99

Most books on data structures assume an imperative language such as C or C++. However, data structures for these languages do not always translate well to functional languages such as Standard ML, Ha......一起来看看 《Purely Functional Data Structures》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

MD5 加密
MD5 加密

MD5 加密工具

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试