本文最后更新于:星期二, 六月 16日 2020, 1:04 下午
记录关于Golang的学习笔记
持续更新
环境安装及配置
Windows下的安装配置比较简单,可以直接在官网下载安装版,直接一步一步按提示走即可,安装程序会帮你自动配置环境变量
安装完成后打开环境变量,其中有一个变量$GOPATH
这个变量影响到你之后开发程序所使用的目录,可以自行修改,我将其修改成了E:\GoSpace
,其文件树如下:
/mnt/e/GoSpace
├── bin
├── pkg
└── src
bin
目录是使用go install
命令安装应用时应用的安装位置,将%GOPATH%/bin
配置到环境变量PATH
中之后,就可以在命令行直接执行开发的程序了
src
目录存放Go源代码,不同项目的代码以包名区分
pkg
目录存放Go编译生成的文件
项目工程结构
这个结构和包管理看起来就很java,虽然我并不会java..
当src
目录下有多个项目同时存在时,要通过包来组织项目目录结构,使用个人域名来开头就不会有包名重复的情况出现。比如我现在用我的域名diaossama.work
和我的github.com/diaossama
分别创建两个文件夹,就可以保证不跟其他人的项目冲突
/mnt/e/GoSpace/src
├── diaossama.work
│ └── study
│ ├── start.exe
│ └── start.go
└── github.com
└── diaossama
这样当我们要引用一个包时就可以用如下方法引用
import (
"diaossama.work/study/start"
)
简单程序
上手语言惯例是个Hello World
package main
import "fmt"
func main() { // 注意 '{' 不能在单独的行上
fmt.Println("Hello, world!")
}
运行程序有两种方式
# 直接运行
go run .\start.go
# 编译后运行
go build .\start.go
.\start.go
测试一下安装
go install diaossama.work/study
然后就可以直接在命令行运行
PS E:\GoSpace\src\diaossama.work\study> study
Hello, world!
跨平台编译
默认情况下,执行go install
或者go build
都是基于当前平台以及环境编译的程序
但是Go有一个特点,就是它的可移植性特别好,可以跨平台编译(术语好像叫做交叉编译?)。Go的编译基于当前的系统变量,可以通过命令go env
查看
E:\GoSpace\src
λ go env
set GO111MODULE=
set GOARCH=amd64
set GOBIN=
set GOCACHE=C:\Users\Diaos\AppData\Local\go-build
set GOENV=C:\Users\Diaos\AppData\Roaming\go\env
set GOEXE=.exe
set GOFLAGS=
set GOHOSTARCH=amd64
set GOHOSTOS=windows
set GONOPROXY=
set GONOSUMDB=
set GOOS=windows
set GOPATH=E:\GoSpace
set GOPRIVATE=
set GOPROXY=https://proxy.golang.org,direct
set GOROOT=c:\go
set GOSUMDB=sum.golang.org
set GOTMPDIR=
set GOTOOLDIR=c:\go\pkg\tool\windows_amd64
set GCCGO=gccgo
set AR=ar
set CC=gcc
set CXX=g++
set CGO_ENABLED=1
set GOMOD=
set CGO_CFLAGS=-g -O2
set CGO_CPPFLAGS=
set CGO_CXXFLAGS=-g -O2
set CGO_FFLAGS=-g -O2
set CGO_LDFLAGS=-g -O2
set PKG_CONFIG=pkg-config
set GOGCCFLAGS=-m64 -mthreads -fmessage-length=0 -fdebug-prefix-map=C:\Users\Diaos\AppData\Local\Temp\go-build640004106=/tmp/go-build -gno-record-gcc-switches
跟跨平台编译相关的是GOOS
和GOARCH
这两个变量,分别代表了操作系统以及处理器架构,具体支持哪些操作系统以及架构可以参考官方文档
如果想要编译其他平台下的可执行程序,只需要改变这两个环境变量就可以了,例如生成linux下,处理器架构为amd64的程序,可以执行
GOOS=linux GOARCH=amd64 go build diaossama.work/study
获取远程包
Python有他自带的包管理工具pip,何况便携性这么好的Go,Go自带一个获取远程包的工具go get
只要这个完成的包名是可访问的,就可以被获取到,比如我们获取一个CLI的开源库:
>go get -v github.com/spf13/cobra/cobra
就可以下载这个库到我们
$GOPATH/src
目录下了,这样我们就可以像导入其他包一样import了。特别提醒,go get的本质是使用源代码控制工具下载这些库的源代码,比如git,hg等,所以在使用之前必须确保安装了这些源代码版本控制工具。
如果我们使用的远程包有更新,我们可以使用如下命令进行更新,多了一个
-u
标识。
>go get -u -v github.com/spf13/cobra/cobra
常用命令行工具
go build # 编译包
go clean # 清除编译产生的文件
go run # 直接运行某个.go文件
go install # 将某个应用安装到$GOPATH/bin下
go env # 查看go环境变量
go get # 获取远程包
go fmt # 格式化源代码(神仙功能
go vet # 检测代码中常见的非编译错误(又是个神仙功能
go test # 用于Go的单元测试
go doc # 查看包的使用文档
语法
package main // 当前包名
import [别名] "fmt" // 导入包 别名用于调用 例如 import f "fmt"
import . "math" // 省略用法,调用该包方法时可以不使用包名(不建议使用)
// 同时导入多个包
import (
"fmt"
"math"
)
const PI = 3.14 // 常量定义
var name = "test" // 变量声明+初始化
var name string // 变量声明 不作初始化
name := "test" // 变量声明+初始化(只能在函数体中出现)
// 函数声明 main()函数为程序入口
func main() {
fmt.Println("Hello, world!")
}
这里需要注意的是,有一种特殊的变量_
,它是一个只写变量,事实上无法得到它的值。这个变量出现的原因是Go语言中所有被声明的变量都必须被使用,否则会报错
所以用这个只写变量来抛弃一些不需要的返回值
值与引用
这一部分Go和C++就有点相似了,跟Python有区别
所有像 int、float、bool 和 string 这些基本类型都属于值类型,使用这些类型的变量直接指向存在内存中的值,复制时也为值复制
可以通过&i
来获取变量i
的内存地址
iota
iota
是一个特殊常量,在const
关键字出现时将被重置为0(const
内部的第一行之前),const
中每新增一行常量声明将使iota
计数一次(可以将iota
理解为const
语句块中的行索引)
比如a=0,b=1,c=2
可以简写为
const (
a = iota
b
c
)
nil
nil
不是一个关键字,当声明了一个变量并还未赋值时,Go会自动给变量一个对应类型对应的默认零值,比如bool
类型的默认零值为false
,int
类型为0
,string
类型为""
。
nil
则是代表指针、通道、函数、接口、映射或切片的零值,是一个预定义的标识符
nil
只能赋值给指针、channel
、func
、interface
、map
或slice
类型的变量 (非基础类型) 否则会引发 panic
数组
var array [5]int // 定义数组 未作初始化
array = [5]int{1,2,3,4,5} // 为数组作初始化
array := [5]int{1,2,3,4,5} // 定义数组并作初始化
var array = [...]int{1,2,3,4,5} // 定义数组并作初始化, ...省略长度,由编译器自己推导
array := [5]int{1:0,3:1} // 为索引1,3赋值,其他默认为0
切片(动态数组)
先看看切片的定义:
切片也是一种数据结构,它和数组非常相似,因为他是围绕动态数组的概念设计的,可以按需自动改变大小,使用这种结构,可以更方便的管理和使用数据集合。
切片底层是基于数组实现的,它很好的解决了使用数组为函数传值时,传递过大数组带来的效率问题。并且可以通过索引获得数据,可以迭代和进行垃圾回收,为什么切片效率高于传递数组的原因如下:
切片对象非常小,是因为它是只有3个字段的数据结构:一个是指向底层数组的指针,一个是切片的长度,一个是切片的容量。这3个字段,就是Go语言操作底层数组的元数据,有了它们,我们就可以任意的操作切片了。
// 创建切片
slice := make([]int, 5) // 第一种方式,5指定切片的长度,此时切片的容量也为5
slice := make([]int, 5, 10) // 5指定切片的长度,切片的容量为10(容量>=长度)
slice := []int{1,2,3,4,5} // 第二种方式
slice := []int{4:1} // 仅为特定索引赋值
第二种创建切片的方式与数组非常相像,仅少了长度的声明。这样创建出来的切片的长度容量相同,并且会由编译器自己推导长度
slice := []int{1,2,3,4,5}
newSlice := slice[1:3] // 第三种方式,基于现有的切片或数组创建
用第三种方式新建的切片,新切片和旧切片公用一个底层数组,所以改变其中一个都会导致所有切片和数组的值改变。
且基于这种方式新建的切片,大小和容量可以这样计算
对于底层数组容量是k的切片slice[i:j]来说
长度:j-i
容量:k-i
也可以使用内置函数len()
和cap()
来分别计算切片的长度和容量
slice := []int{1,2,3,4,5}
newSlice := slice[1:2:3] // 第四种方式,3个索引
第四种方式是通过3个索引来创建新切片,第三个索引用于确定新切片的容量,上述例子创建了一个长度2-1=1
,容量3-1=2
的新切片。注意:第三个索引不能超过原切片或数组的最大索引值
append()
函数可以为切片新增一个元素,并使它的长度+1(当切片的容量==长度时,append()函数会新建一个切片,并将值复制过去,但是当长度<容量时,则不会创建一个新的切片,而是直接在原数组的切片上增加长度,例子如下)
slice := []int{1,2,3,4,5}
newSlice = slice[1:3]
newSlice = append(newSlice, 10)
fmt.Println(newSlice)
fmt.Println(slice)
//Output
[2 3 10]
[1 2 3 10 5]
循环
这一部分跟C++还是很像,Go的for语句相当于C++的for+while
//第一种形式,与C的for类似
for init; condition; post { }
//第二种形式,与C的while类似
for condition { }
//第三种形式,与C的for(;;)类似
for { }
For-Each循环
numbers := [6]int{1, 2, 3, 5}
for i,x:= range numbers {
fmt.Printf("第 %d 位 x 的值 = %d\n", i,x)
}
/*
输出:
第 0 位 x 的值 = 1
第 1 位 x 的值 = 2
第 2 位 x 的值 = 3
第 3 位 x 的值 = 5
第 4 位 x 的值 = 0
第 5 位 x 的值 = 0
*/
函数
func function_name ([parameter list]) [return_types] {
function_body
}
// 例如
func max(num1, num2 int) int {
var result int
if (num1 > num2) {
result = num1
} else {
result = num2
}
return result
}
Go的函数默认为值传递
闭包
闭包相当于Go的匿名函数,它的优越性在于可以直接使用函数内的变量而不必声明(有点像套娃x
package main
import "fmt"
func getSequence() func() int {
i := 0
return func() int {
i += 1
return i
}
}
// 这个getSequence很像是返回了一个Python里的迭代器
func main() {
nextNumber := getSequence() //初始化了一个nextNumber()函数,其中i=0
fmt.Println(nextNumber()) // 1
fmt.Println(nextNumber()) // 2
fmt.Println(nextNumber()) // 3
}
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!