本文最后更新于:星期二, 六月 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

跟跨平台编译相关的是GOOSGOARCH这两个变量,分别代表了操作系统以及处理器架构,具体支持哪些操作系统以及架构可以参考官方文档

如果想要编译其他平台下的可执行程序,只需要改变这两个环境变量就可以了,例如生成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类型的默认零值为falseint类型为0string类型为""

nil则是代表指针、通道、函数、接口、映射或切片的零值,是一个预定义的标识符

nil只能赋值给指针、channelfuncinterfacemapslice类型的变量 (非基础类型) 否则会引发 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协议 。转载请注明出处!

2020高校战疫CTF笔记 上一篇
BUUCTF刷题笔记 下一篇