go学习记录——第九天

精密计算和big包

众所周知编程实现的计算一般来说是不准确的,会出现float64的最多15位等等情况。而当我们对精度有非常严格的要求的时候就不能使用浮点数了,毕竟在内存里是近似表示的。

那么我们就得用自带的big包,这个是包含在math包下的。举几个例子

  • big.Int 用来表示大整数
  • big.Rat用来表示大有理数(可以表示分数2/5或者小数3.1415926,不能表示π这种无理数)
  • big.NewInt(n)用来表示大的整型数字,其中nint64类型整数
  • big.NewRat(n, d)用来构造大有理数,其中n为分母d为分子,且均为init64类型

前面提到的数字类型其实包含了我们常见的所有数字类型,不管是整数浮点数有理数都算,只要我们的内存够大够能装,实际上都是可以装得下的能够运行的。但是这里需要说明的是由于更大的内存需要更多的开销,故而处理时间上来看会比我们内置的数字类型慢很多(当然客户不介意时间的话那就无所谓了)。

由于Golang不支持运算符重载,所以所有的大数字类型都有像Add() Mul()这些方法,主要作用就是作为receiver的整数和有理数,大多数情况下它们会修改receiver并以reciver作为返回结果。由于没必要创建big.Int类型的临时变量来存放中间结果,所以运算可以被链式的调用,故而可以节省内存(再牵扯到前面的消耗内存,这不妥妥风险对冲)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
"fmt"
"math"
"math/big"
)

func main() {
// Here are some calculations with bigInts:
im := big.NewInt(math.MaxInt64)
in := im
io := big.NewInt(1)
ip := big.NewInt(1956)
ip.Mul(im, in).Add(ip, im).Div(ip, io)
fmt.Printf("BIg Int: %v\n", ip)
// Here are some calculations with bigInts:
rm := big.NewRat(math.MaxInt64, 1956)
rn := big.NewRat(-1956, math.MaxInt64)
ro := big.NewRat(19, 56)
rp := big.NewRat(1111, 2222)
rq := big.NewRat(1, 1)
rq.Mul(rm, rn).Add(rq, ro).Mul(rq, rp)
fmt.Printf("Big Rat: %v\n", rq)
}

输出结果:

1
2
BIg Int: 85070591730234615856620279821087277056
Big Rat: -37/112

自定义包和可见性

这部分就是说下怎么去写自己的包

首先命名需要使用不含_的小写字母来命名,这里举一个小例子(抄的),以此来说明包是如何相互调用以及可见性是如何实现的。

在目录下有个叫package_mytest.goaddress)的程序,其使用了自定义包pack1中的pack1.go的代码,最后是生成了pack1.a并存放在pack1目录下。

示例pack1.go

1
2
3
4
5
6
7
package pack1
var Pack1Int int = 42
var Pack1Float = 3.14

func ReturnStr() string{
return "Hello main!"
}

这个就包含了一个整型变量Pack1Int和一个返回字符串的函数ReturnStr,由于没有main函数,所以运行了也无事发生。

在主程序package_mytest.go中这个包通过声明被导入

1
import "./pack1"

import的一般格式如下:

1
import “包的路径或URL地址”

for example:

1
import "github.com/org1/pack1"

路径指的是目录的相对路径

示例package_mytest.go

1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"fmt"
"myproject/chapter9/pack1"
)

func main() {
var test1 string
test1 = pack1.ReturnStr()
fmt.Printf("ReturnStr from package1: %s\n", test1)
fmt.Printf("integer from package1: %d\n", pack1.Pack1Int)
}

输出结果:

1
2
ReturnStr from package1: Hello main!
integer from package1: 42

根据实际检验,./pack1引用嘎嘎报错,所以还是老老实实写绝对路径吧。

1
fmt.Printf("Float from package1: %f/n", pack1.pack1Float)

上述代码试图访问一个没有引用的变量或函数,就会报错(有一说一,这个错误我前面遇到了,忘记加引用了)

1
cannot refer to unexported name pack1.pack1Float

需要注意的是,主程序利用的包都需要在主程序之前被编译,且主程序中引用的每个东西都需要以pack1.Item的形式来使用。

在使用.作为别名时,可以不通过包名来使用其中的项目,例如test := ReturenStr()

具体形式为

1
import . "myproject/chapter9/pack1"

然后在使用_作为别名时将导入包的副作用(没明白,我猜大概是只对包内容进行初始化,然后主程序无法进行调用相关函数或变量,不确定后面再研究研究)

导入外部安装包

如果想在程序中引入一个或多个外部包,就必须使用go install在本地机器上进行安装。

如果想使用被托管在Github Google Code Launchpad上的包,则需要使用类似如下命令:

1
go install codesite.ext/author/goExample/goex

将一个名为codesite.ext/author/goExample/goex的map安装在$GOROOT/src/目录下

通过这种方式一次性安装并导入代码

1
import goex "codesite.exit/author/goExample/goex"

综上,如果想使用被托管的包,则需要使用URL作为路径,并将包名放在路径前。

包的初始化

程序的执行开始于包,初始化main包然后调用main()函数。

在一个包中,可能有多个init()函数,且他们的执行是无序的。

init()函数是不能被调用的(只是不能被我们显性调用,他会在包被导入时自动被调用,故而我们在代码中是不能直接写init()来调用的。

导入的包在包自身被初始化前被初始化(当前包导入了其他包,则在当前包被初始化前其他包会先被初始化,其中包括分配包级变量的初始值和被导入包中的init()函数。在被导入包初始化完成后,当前包才会进行初始化)

为自定义包使用godoc

godoc工具在显示自定义包中的注释也有很好的效果(虽然但是,那部分内容我是学的别的教程的,后续再补吧,先贴个链接第3.6节);注释必须以//开始并无空行放在声明前(包,类型,函数)。godoc会为每个文件生成一系列的网页。

例如:

  • doc_examples目录下有第11.7节中的用来排序的go文件,文件中有一些煮熟(文件需未编译)
  • 命令行输入godoc -http=:6060 -goroot=".".指当前路径,-goroot参数可以是/path/to/my/package1这样的形式指出package1在源码中的位置或接受用冒号形式分隔的路径,吴根目录的路径为相对于当前路径的相对路径)
  • 浏览器打开https://localhost:6060

完成后就能看到本地的godoc页面,从左到右依次显示出目录中的包

1
2
3
doc_example:

doc_example | Packages | Commands | Specification

下面是链接到源码和所有对象时有序概述(很好的浏览和查询源代码的方式),连同文件/注释

sort

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
func Float64sAreSorted

type IntArray

func IntsAreSortedfunc IsSortedfunc Sort

func (IntArray) Len

func SortFloat64s

func (IntArray) Less

func SortInts

func (IntArray) Swap

func SortStrings type Interface

func StringsAreSorted type StringArray type Float64Array

func (StringArray) Len

func (Float64Array) Len

func (StringArray) Less

func (Float64Array) Less

func (StringArray) Swap

func (Float64Array) Swap

// Other packages
import "doc_example"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func Float64sAreSorted[Top]
func Float64sAreSorted(a []float64) bool

func IntsAreSorted[Top]
func IntsAreSorted(a []int) bool

func IsSorted[Top]
func IsSorted(data Interface) bool
Test if data is sorted

func Sort[Top]
func Sort(data Interface)
General sort function

func SortInts[Top]
func SortInts(a []int)

Convenience wrappers for common cases: type IntArray[Top]
Convenience types for common cases: IntArray type IntArray []int

如果在团队内,将源代码树存储在网络硬盘上,就可以使用godoc给所有团队成员连续支持文档。通过设置sync_minutues = n,就可以设置为每n分钟更新一次

使用 go install 安装自定义包

go install是 Go 中自动包安装工具,如果需要将包安装到本地它就会从远端仓库下载包:检出、编译和安装一气呵成。

在包安装之前的先决条件是要自动处理包自身依赖关系的安装(和python倒是一样,不过python通过pip或者coda安装都会自动去得到没安装的依赖包然后安装)

go install使用了GOPATH变量(参考第2.2节

远端包(参考前面内容)

假设我们要安装tideland(包含了很多帮助示例,参考项目主页

由于我们需要创建目录在 Go 安装目录下,所以我们需要使用到root or su(Linux的,不知道的叉出去)

确保 Go 环境变量已经设置在 root 用户下的 ./bashrc文件中(好熟悉的东西,之前搞Java也设置过,没写过Java,就是因为Spark和Hadoop要用)。

使用命令go install tideland-cgl.googlecode.com/hg进行安装。

可执行文件hg.a被放在了$GOROOT/pkg/linux_amd64/tideland-cgl.googlecode.com目录下,源码文件被放在了 $GOROOT/src/tideland-cgl.googlecode.com/hg 目录下,同样也有 hg.a 放置在 _obj的子目录下。

现在就可以在 Go 的代码中使用包中的功能了,就比如使用报名cgl导入

1
import cgl "tideland-cgl.googlecode.com/hg"

从Go 1开始 go install 安装Google Code 的导入路径形式是 "code.google.com/p/tideland-cgl"

升级到新版本的 Go 之后本地安装包的二进制文件将被全部删除。如果想更新,重编译、重安装所有的 Go 安装包可以使用:

go install -a

由于 Go 的版本发布的非常频繁,所以还是需要注意发布版本和包的兼容性的(python实在不行搞个coda虚拟环境还能设置个指定版本,go不知道有没有类似的功能,理论上应该有吧)。Go 1之后都是自己编译自己了

go install 同样可以使用 go install 编译链接并安装本地自己的包(详细见下一节)


go学习记录——第九天
https://www.lx02918.ltd/2024/11/20/go-study-ninth-day/
作者
Seth
发布于
2024年11月20日
许可协议