本文更新时间: `2025-10-12` - 本文的**目的**: 记录Go语言学习的一些特殊点,便于以后快速使用,免得学了又忘了 ## 一般编译方法 - 编译环境: win7 x64(可以在其他环境例如linux,安卓termux,下载对应的平台版本即可) - 下载: https://golang.google.cn/dl/go1.17.2.windows-amd64.zip - 解压到: D:\,即存在D:\go - 下载项目源码: https://github.com/linpinger/reverse-ssh ,可以点击按钮Code,Download Zip来下载,或使用 `git clone https://github.com/linpinger/reverse-ssh.git` - 将压缩文件解压到 D:\,即存在D:\reverse-ssh-master - 打开cmd命令行(linux打开shell,下面的set改为export,路径改成实际路径)设置环境变量 ```bat echo 以下4行非必要,它的目的是建个临时文件夹,go编译项目的时候会将临时文件生成到这里 mkdir D:\go-tmp set GOCACHE=D:\go-tmp set TEMP=D:\go-tmp set TMP=D:\go-tmp echo 设置PATH变量,目的是在命令行下能找到go.exe set PATH=D:\go\bin;%PATH% echo 解压go1.17.2.windows-amd64.zip后的go目录 set GOROOT=D:\go echo GOPATH据说会取消,目前是放项目的目录,编译时会生成bin pkg src 目录,自己的项目,可以放在src目录下 set GOPATH=D:\myGoPrj echo 这是国内代理,方便下载go依赖库 set GOPROXY=https://goproxy.cn,direct echo 设置编译目标平台及架构 例如 windows linux amd64 386 arm64,默认生成当前平台 set GOOS=windows set GOARCH=amd64 echo go build -ldflags "-s -w" ``` - 命令行下进入项目目录 cd /d D:\reverse-ssh-master - 执行 go mod tidy 以下载依赖的库 - 下载完毕,执行 go build 即可编译成exe,若要减小体积执行 go build -ldflags "-s -w",若要交叉编译成linux下的程序 设置环境变量GOOS 和 GOARCH然后再执行go build - 其他:有些程序依赖gcc来编译c或c++文件,win下需要下载minGW64,linux下就简单些 - 目前的go版本在win下,起码是win7,若要编译为xp下可用的exe,需使用老版本的go,测试这个版本可以 https://golang.google.cn/dl/go1.11.13.windows-amd64.zip - 编译时若出错,根据出错信息排查,可能是项目源码及依赖源码使用的是新的库,在D:\go\src下找出新老版本的差异来解决问题 ### 2021-03-18 golang升级到1.16后的一些变化(对于个人的) - `io/ioutil`中的函数转移到`io`和`os`包中,尽管目前还能用 - `embed` 包很有意思,可以嵌入资源文件到二进制文件中,目前还没用上,但很有兴趣 - `GO111MODULE`变量的值现在默认为`on`,实际影响很大,习惯了使用旧版自己下载依赖库的我被玩坏了,需要适应新的module机制 - `go mod init xxx`用来初始化模块,不然无法编译了,除非把`GO111MODULE`设为off,不过据说下个版本会取消,到时候就蛋疼了 - `go mod tidy` 会下载依赖的包,放到`$GOPATH/pkg`下 - `go mod vendor` 会复制已下载的包到vendor文件夹下,这样就可以修改该库了,这个未测试,头疼 - `git tag v0.1.0` 学会打tag,推送 `git push --tags` - `export GOPROXY=https://goproxy.cn,direct` 可以加速下载module,当然翻一下也可以 ### 2020-12-31 编译为安卓上可以调用的库 - android自己写的apk下运行自己编译的elf貌似路被堵死了(不知道android terminal是个怎么原理),目前只有走jni/NDK这条路了,测试了一下是可以,不过那个cpp语言看起来很麻烦,搜到了据说jna可以简化,不过cpp不是强项,还是用golang方便 - 参考: (https://www.jianshu.com/p/fff1be6484c6) - 上面链接中的文章有几个细节有问题,最好还是看官方文档吧 (https://github.com/golang/go/wiki/Mobile) - 整理后步骤如下: - 设置环境变量(注意,ndk的版本,22.x版本编译会出错,目前测试21.1这个正常)及代理,`golang.org`被Q了,要下源码需要翻一下Q,可以用小飞机或v2rayN之类的 - 1.16以上: 清除GOPROXY变量的内容,使用小飞机(),删除pkg文件夹,保障成功率,怀疑gomobile每次运行都下载依赖到gocache里面,而不是和pkg里面共用mod,过程会调用go mod tidy来下载依赖,编译过程会调用cgo和ndk里面的clang来进行编译项目,故ndk不可缺少 ```shell export ANDROID_HOME=D:\\bin\\java\\AndroidSDK export ANDROID_NDK_HOME=D:\\bin\\java\\AndroidSDK\\ndk\\21.1.6352462 export http_proxy=http://127.0.0.1:10809 export https_proxy=http://127.0.0.1:10809 git config --global http.proxy 'http://127.0.0.1:10809' git config --global https.proxy 'http://127.0.0.1:10809' ``` - `go get golang.org/x/mobile/cmd/gomobile` - `go build golang.org/x/mobile/cmd/gomobile` - `go build golang.org/x/mobile/cmd/gobind` - 把生成的`gomobile.exe` `gobind.exe`放到`go.exe`所在bin文件夹 - 按照golang的规则写一个go文件,注意包名不要是main哦,然后函数第一个字母大写,毕竟是要给android调用的,例如`github.com/linpinger/foxbook-golang/fox` - `gomobile bind -target=android -ldflags "-s -w" fox` - 可能会提示: `go get golang.org/x/mobile/bind`,执行一下 - 这样会生成`fox.aar`和`fox-sources.jar` - 在android工程创建和src同级的libs文件夹,将fox.aar放到这里 - 修改build.gradle: - `android`内添加`ndkVersion "21.1.6352462"` - `android`内添加`dependencies { implementation fileTree(dir: 'libs', include: ['*.jar','*.aar']) }` - `android/defaultConfig`内修改`minSdkVersion 15`,可选添加`ndk { abiFilters "arm64-v8a", "armeabi-v7a" // arm64-v8a、armeabi-v7a、x86_64、x86 }` - 同步一下,java里调用比较简单了: `import fox.Fox; Fox.startHTTPServer("8080", "/sdcard/", "", "", true, true, false);` ### 2020-12-28 编译为安卓上可以执行的程序碰到的坑 - 编译时设置环境变量`GOOS`和`GOARCH`为`android`和`arm64`,会错误,不知道以后能不能改善,改成`linux`和`arm64`就可以编译通过 - 编译通过后会发现: 使用go自带的库进行http访问,会找不到站点,原因就是go的src目录下的`dnsconfig_unix.go`的第40行`dnsReadConfig`使用的是linux平台的方式去处理域名转ip,android平台有点不一样,所以需要把这个函数扩展一下 - 解决方法来自: (https://github.com/golang/go/issues/8877) - 操作方法,替换这行: `func dnsReadConfig(filename string) *dnsConfig {` 为: ```golang func dnsReadConfig(filename string) *dnsConfig { if _, err := os.Stat("/system/bin/getprop"); err == nil { return dnsReadConfigAndroid() } else { return dnsReadConfigUnix(filename) } } func dnsReadConfigAndroid() *dnsConfig { conf := &dnsConfig{ ndots: 1, timeout: 5 * time.Second, attempts: 2, rotate: false, } for _, prop := range []string{"net.dns1", "net.dns2"} { out, err := exec.Command("/system/bin/getprop", prop).Output() if err != nil { continue } ip := strings.TrimSpace(string(out)) if ParseIP(ip) != nil { conf.servers = append(conf.servers, JoinHostPort(ip, "53")) } } if len(conf.servers) == 0 { conf.servers = []string{"119.29.29.29:53", "168.95.1.1", "8.8.8.8:53", "8.8.4.4:53", "4.2.2.1:53"} } return conf } // See resolv.conf(5) on a Linux machine. func dnsReadConfigUnix(filename string) *dnsConfig { ``` - 另外在头部的import中加入两行依赖 ```golang "os/exec" "strings" ``` - 然后重新编译一下,就可以在android下正常下载网页了 ### 2018-06-12 交叉编译 - 原来go支持交叉编译,本人之前还傻傻的在多个平台上去编译,WTF ```shell #! /bin/bash export PATH="/dev/shm/go/bin:$PATH" export GOROOT="/dev/shm/go" # export GOPATH="/dev/shm" export GOPATH="$HOME" export GOARCH=amd64 export GOOS=linux echo "- $GOOS $GOARCH" go build -o foxbook-golang-x64-linux.elf -ldflags "-s -w" github.com/linpinger/foxbook-golang export GOARCH=386 export GOOS=linux echo "- $GOOS $GOARCH" go build -o foxbook-golang-x86-linux.elf -ldflags "-s -w" github.com/linpinger/foxbook-golang export GOARCH=amd64 export GOOS=windows echo "- $GOOS $GOARCH" go build -o foxbook-golang-x64.exe -ldflags "-s -w" github.com/linpinger/foxbook-golang export GOARCH=386 export GOOS=windows echo "- $GOOS $GOARCH" go build -o foxbook-golang-x86.exe -ldflags "-s -w" github.com/linpinger/foxbook-golang export GOARCH=amd64 export GOOS=darwin echo "- $GOOS $GOARCH" go build -o foxbook-golang-x64-MacOSX.bin -ldflags "-s -w" github.com/linpinger/foxbook-golang echo "- zip all" zip -9 foxbook-golang-bin.zip foxbook-golang-* echo "# done" ``` ### 包组织 - 当前项目的工作目录就是 环境变量 GOPATH 定义的目录,该目录下有 src, pkg, bin 子目录 - `GOPATH=c:\go_a\;d:\xxx\` - `GOPATH=/home/fox/go_a:/dev/shm/00/` - `package main` - `import "fmt"` - `import "github.com/axgle/mahonia"` - 可以通过 `go get github.com/axgle/mahonia` 下载 该包 会在 GOPATH 下的src目录中存放 ### 变量与运算符 - 结构体是值类型,在函数调用时,像切片(slice)、字典(map)、接口(interface)、通道(channel)这样的引用类型都是默认使用引用传递 - 大小写敏感,大小写影响变量及函数的作用范围,头字母小写算局部变量,头字母大写算全局变量,具体搜索一下 - 变量定义: ```go html := "aaa" var p = fmt.Println // alias, 可放在main函数外 var html string var html string = "aaa" var html1, html2, html3 string var html1, html2, html3 string = "aaa", "bbb", "ccc" html1, html2, html3 string := "aaa", "bbb", "ccc" ``` - `true` `false` - `nil` - `==` `!=` - `&&` `||` `!` - `=` `+=` `-+` `*=` `/=` ### 字符串 - 字符串与byte数组可互相转换: `str := string(bts)` `bts := []byte(str)` - 当byte数组没有填满,但需要转换为字符串: `str := string(data[0:5])` ,这里的0:5就是数组实际有效的长度,在socket编程里面常见 - 字符串连接符: `str1 + str2` - 字符串函数: 见包 [strings](https://golang.google.cn/pkg/strings/) - 是否包含: `if strings.Contains(iStr, "</html>") {` - 字符串替换: `html = strings.Replace(html, "<br>", "\n", -1)` - 字符串内容是否相同: `if "aaaa" == iStr {` - 字符串长度: `len(iStr)` ### 正则表达式 ```golang // 获取子匹配: reBody, _ := regexp.Compile("(?smi)<body[^>]*?>(.*)</body>") bd := reBody.FindStringSubmatch(html) if nil != bd { html = bd[1] } // 获取所有子匹配: FindAllStringSubmatch // 替换 reRC, _ := regexp.Compile("(?smi)<script[^>]*?>.*?</script>") html = reRC.ReplaceAllString(html, "") ``` ### 流程 ```go var p = fmt.Println if "0" == aaa { p("is 0") } else if "1" == aaa { p("is 1") } else { p("is not 0 and 1") } ``` ```go for { p("aaaa") } ``` ### 函数 - 支持多返回值 - 函数定义以 func 开头 - 函数不定参数,例如sql包的: ```go func xxQuery(query string, args ...interface{}) { rows, err := DBXX.Query(query, args...) } ``` ### 特殊 - 使用 struct 和 func 可以实现类似类的效果 - `data := make([]byte, 4096)` - 使用 go 关键字来将不想卡主线程的函数放到协程里运行: `go printTickTenTimes()` ```go // 遍历数组 xx := []string{"aaaa", "bbb", "cccc"} for i, ll := range xx { p(i, " : ", ll) } ``` ### 坑 - string -> []byte : `bb := []byte(str)` - []byte -> string : `str := string(bb)` - 坑点在于 str 与 bb 指向的内存不是同一块,内存不会释放 - 可以自己做个实验,在一个函数中读入一个超大文本,例如10M的文本,先转成string,然后再转为[]byte,这就有30M了 - https://sheepbao.github.io/post/golang_byte_slice_and_string/ ### 条件编译/编译约束 - 参考: https://www.cnblogs.com/FengZeng666/p/15689046.html ```golang //go:build !windows // +build !windows package main ``` - `//go:build` 是 Go 1.17 引入的新语法,使用布尔表达式 - `//go:build` 必须放在文件开头,在 package 声明之前 - `// +build` 是旧语法,使用空格分隔的条件列表 - `// +build` 也需要放在文件开头,但在 Go 1.17+ 中通常与 `//go:build` 配合使用 - `// +build` 的下一行必须是空行 - 利用ldflags在编译过程中为变量赋值 - `go build -ldflags "-w -s -X main.version=2025-10-08_test_version"` ### 其他 - 最后生成的exe支持xp的版本: `go1.11.13` https://golang.google.cn/dl/go1.11.13.windows-amd64.zip - 最后生成的exe支持w7的版本: `go1.20.11` https://golang.google.cn/dl/go1.20.11.windows-amd64.zip - `2025-10-08`: termux 下编译windows amd64: 生成的exe只能在10+系统运行 - 使用 `go vet` 或 `go vet xxx.go` 来检查一些可能的错误