内容纲要

常用标准库概述

像 fmt、os 等这样具有常用功能的内置包在 Go 语言中有 150 个以上,它们被称为标准库,大部分 (一些底层的除外) 内置于 Go 本身

  • unsafe: 包含了一些打破 Go 语言 “类型安全” 的命令,一般的程序中不会被使用,可用在 C/C++ 程序的调用中。

  • syscall-os-os/exec:

    • os: 提供给我们一个平台无关性的操作系统功能接口,采用类 Unix 设计,隐藏了不同操作系统间差异,让不同的文件系统和操作系统对象表现一致。
    • os/exec: 提供我们运行外部操作系统命令和程序的方式。
    • syscall: 底层的外部包,提供了操作系统底层调用的基本接口。
  • archive/tar/zip-compress:压缩 (解压缩) 文件功能。

  • fmt-io-bufio-path/filepath-flag:

    • fmt: 提供了格式化输入输出功能。
    • io: 提供了基本输入输出功能,大多数是围绕系统功能的封装。
    • bufio: 缓冲输入输出功能的封装。
    • path/filepath: 用来操作在当前系统中的目标文件名路径。
    • flag: 对命令行参数的操作。  
  • strings-strconv-unicode-regexp-bytes:

    • strings: 提供对字符串的操作。
    • strconv: 提供将字符串转换为基础类型的功能。
    • unicode: 为 unicode 型的字符串提供特殊的功能。
    • regexp: 正则表达式功能。
    • bytes: 提供对字符型分片的操作。
    • index/suffixarray: 子字符串快速查询。
  • math-math/cmath-math/big-math/rand-sort:

    • math: 基本的数学函数。
    • math/cmath: 对复数的操作。
    • math/rand: 伪随机数生成。
    • sort: 为数组排序和自定义集合。
    • math/big: 大数的实现和计算。
  • container-/list-ring-heap: 实现对集合的操作。

    • list: 双链表。
    • ring: 环形链表。
  • time-log:

    • time: 日期和时间的基本操作。
    • log: 记录程序运行时产生的日志。
  • encoding/json-encoding/xml-text/template:

    • encoding/json: 读取并解码和写入并编码 JSON 数据。
    • encoding/xml: 简单的 XML1.0 解析器。
    • text/template: 生成像 HTML 一样的数据与文本混合的数据驱动模板。
  • net-net/http-html:

    • net: 网络数据的基本操作。
    • net/http: 提供了一个可扩展的 HTTP 服务器和基础客户端,解析 HTTP 请求和回复。
    • html: HTML5 解析器。
  • runtime: Go 程序运行时的交互操作,例如垃圾回收和协程创建。

  • reflect: 实现通过程序运行时反射,让程序操作任意类型的变量。

regexp 包

在下面的程序里,我们将在字符串中对正则表达式进行匹配。

如果是简单模式,使用 Match 方法便可:

ok, _ := regexp.Match(pat, []byte(searchIn))

变量 ok 将返回 true 或者 false, 我们也可以使用 MatchString:

ok, _ := regexp.MatchString(pat, searchIn)

更多方法中,必须先将正则通过 Compile 方法返回一个 Regexp 对象。然后我们将掌握一些匹配,查找,替换相关的功能。

package main
import (
    "fmt"
    "regexp"
    "strconv"
)
func main() {
    //目标字符串
    searchIn := "John: 2578.34 William: 4567.23 Steve: 5632.18"
    pat := "[0-9]+.[0-9]+" //正则

    f := func(s string) string{
        v, _ := strconv.ParseFloat(s, 32)
        return strconv.FormatFloat(v * 2, 'f', 2, 32)
    }

    if ok, _ := regexp.Match(pat, []byte(searchIn)); ok {
        fmt.Println("Match Found!")
    }

    re, _ := regexp.Compile(pat)
    //将匹配到的部分替换为"##.#"
    str := re.ReplaceAllString(searchIn, "##.#")
    fmt.Println(str)
    //参数为函数时
    str2 := re.ReplaceAllStringFunc(searchIn, f)
    fmt.Println(str2)
}

锁和 sync 包

在一些复杂的程序中,通常通过不同线程执行不同应用来实现程序的并发。当不同线程要使用同一个变量时,经常会出现一个问题:无法预知变量被不同线程修改的顺序!(这通常被称为资源竞争,指不同线程对同一变量使用的竞争) 显然这无法让人容忍,那我们该如何解决这个问题呢?

经典的做法是一次只能让一个线程对共享变量进行操作。当变量被一个线程改变时 (临界区),我们为它上锁,直到这个线程执行完成并解锁后,其他线程才能访问它。

特别是我们之前章节学习的 map 类型是不存在锁的机制来实现这种效果 (出于对性能的考虑),所以 map 类型是非线程安全的。当并行访问一个共享的 map 类型的数据,map 数据将会出错。

在 Go 语言中这种锁的机制是通过 sync 包中 Mutex 来实现的。sync 来源于 "synchronized" 一词,这意味着线程将有序的对同一变量进行访问。

sync.Mutex 是一个互斥锁,它的作用是守护在临界区入口来确保同一时间只能有一个线程进入临界区。

假设 info 是一个需要上锁的放在共享内存中的变量。通过包含 Mutex 来实现的一个典型例子如下:

import  "sync"

type Info struct {
    mu sync.Mutex
    // ... other fields, e.g.: Str string
}

如果一个函数想要改变这个变量可以这样写:

func Update(info *Info) {
    info.mu.Lock()
    // critical section:
    info.Str = // new value
    // end critical section
    info.mu.Unlock()
}

还有一个很有用的例子是通过 Mutex 来实现一个可以上锁的共享缓冲器:

type SyncedBuffer struct {
    lock    sync.Mutex
    buffer  bytes.Buffer
}

精密计算和 big 包

我们知道有些时候通过编程的方式去进行计算是不精确的。如果你使用 Go 语言中的 float64 类型进行浮点运算,返回结果将精确到 15 位,足以满足大多数的任务。当对超出 int64 或者 uint64 类型这样的大数进行计算时,如果对精度没有要求,float32 或者 float64 可以胜任,但如果对精度有严格要求的时候,我们不能使用浮点数,在内存中它们只能被近似的表示。

对于整数的高精度计算 Go 语言中提供了 big 包。其中包含了 math 包:有用来表示大整数的 big.Int 和表示大有理数的 big.Rat 类型(可以表示为 2/5 或 3.1416 这样的分数,而不是无理数或 π)。这些类型可以实现任意位类型的数字,只要内存足够大。缺点是更大的内存和处理开销使它们使用起来要比内置的数字类型慢很多。

大的整型数字是通过 big.NewInt(n) 来构造的,其中 n 为 int64 类型整数。而大有理数是通过 big.NewRat(N,D) 方法构造。N(分子)和 D(分母)都是 int64 型整数。因为 Go 语言不支持运算符重载,所以所有大数字类型都有像是 Add() 和 Mul() 这样的方法。它们作用于作为 receiver 的整数和有理数,大多数情况下它们修改 receiver 并以 receiver 作为返回结果。因为没有必要创建 big.Int 类型的临时变量来存放中间结果,所以这样的运算可通过内存链式存储。

// big.go
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(1956)
    ip := big.NewInt(1)
    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)
}

/* Output:
Big Int: 43492122561469640008497075573153004
Big Rat: -37/112
*/

自定义包和可见性

包是 Go 语言中代码组织和代码编译的主要方式。关于它们的很多基本信息已经在 4.2 章节中给出,最引人注目的便是可见性。现在我们来看看具体如何来使用自己写的包。在下一节,我们将回顾一些标准库中的包,自定义的包和标准库以外的包。

当写自己包的时候,要使用短小的不含有 _(下划线) 的小写单词来为文件命名。这里有个简单例子来说明包是如何相互调用以及可见性是如何实现的。

当前目录下(examples/chapter_9/book/)有一个名为 package_test.go 的程序,它使用了自定义包 pack1 中 pack1.go 的代码。这段程序 (连同编译链接生成的 pack1.a) 存放在当前目录下一个名为 pack1 的文件夹下。所以链接器将包的对象和主程序对象链接在一起。

package pack1
var Pack1Int int = 42
var PackFloat = 3.14

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

它包含了一个整型变量 Pack1Int 和一个返回字符串的函数 ReturnStr。这段程序在运行时不做任何的事情,因为它不包含有一个 main 函数。

在主程序 package_test.go 中这个包通过声明的方式被导入

import "./pack1"

编译并安装一个包

在 Linux/OS X 下可以用类似 Makefile 脚本做到这一点

include $(GOROOT)/src/Make.inc
TARG=pack1
GOFILES=\
    pack1.go\
    pack1b.go\
include $(GOROOT)/src/Make.pkg

通过 chmod 777 ./Makefile 确保它的可执行性。

上面脚本内的 include 语引入了相应的功能,将自动检测机器的架构并调用正确的编译器和链接器。

然后终端执行 makegomake 工具:他们都会生成一个包含静态库 pack1.a 的 _obj 目录。

go install(参见第 9.7 节,从 Go1 的首选方式) 同样复制 pack1.a 到本地的 $GOROOT/pkg 的目录中一个以操作系统为名的子目录下。像 import "pack1" 代替 import "path to pack1",这样只通过名字就可以将包在程序中导入

为自定义包使用 godoc

godoc 工具在显示自定义包中的注释也有很好的效果:注释必须以 // 开始并无空行放在声明(包,类型,函数)前。godoc 会为每个文件生成一系列的网页。

例如:

在 doc_examples 目录下我们有用来排序的 go 文件,文件中有一些注释(文件需要未编译)
命令行下进入目录下并输入命令:

godoc -http=:6060 -goroot="."

(. 是指当前目录,-goroot 参数可以是 /path/to/my/package1 这样的形式指出 package1 在你源码中的位置或接受用冒号形式分隔的路径,无根目录的路径为相对于当前目录的相对路径)

在浏览器打开地址:localhost:6060
然后你会看到本地的 godoc 页面从左到右一次显示出目录中的包:

doc_example:

doc_example | Packages | Commands | Specification

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

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" 

使用通用的接口排序:

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_minutes=n,你甚至可以让它每 n 分钟自动更新您的文档!

使用 go install 安装自定义包

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

在包安装前的先决条件是要自动处理包自身依赖关系的安装。被依赖的包也会安装到子目录下,但是没有文档和示例:可以到网上浏览。

go install 使用了 GOPATH 变量。

远端包:

假设我们要安装一个有趣的包 tideland。

因为我们需要创建目录在 Go 安装目录下,所以我们需要使用 root 或者 su 的身份执行命令。

确保 Go 环境变量已经设置在 root 用户下的 ./bashrc 文件中。

使用命令安装: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 导入:

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

自定义包的目录结构、go install 和 go test

为了示范,我们创建了一个名为 uc 的简单包,它含有一个 UpperCase 函数将字符串的所有字母转换为大写。当然这并不值得创建一个自己包,同样的功能已被包含在 strings 包里,但是同样的技术也可以应用在更复杂的包中。

自定义包的目录结构

下面的结构给了你一个好的示范:

/home/user/goprograms
    ucmain.go   (uc包主程序)
    Makefile (ucmain的makefile)
    ucmain
    src/uc   (包含uc包的go源码)
        uc.go
        uc_test.go
        Makefile (包的makefile)
        uc.a
        _obj
            uc.a
        _test
            uc.a
    bin      (包含最终的执行文件)
            ucmain
    pkg/linux_amd64
            uc.a    (包的目标文件)

将你的项目放在 goprograms 目录下 (你可以创建一个环境变量 GOPATH,在 .profile.bashrc 文件中添加 export GOPATH=/home/user/goprograms),而你的项目将作为 src 的子目录。uc 包中的功能在 uc.go 中实现

package uc
import "strings"

func UpperCase(str string) string {
    return strings.ToUpper(str)
}

包通常附带一个或多个测试文件,在这我们创建了一个 uc_test.go 文件

package uc
import "testing"

type ucTest struct {
    in, out string
}

var ucTests = []ucTest {
    ucTest{"abc", "ABC"},
    ucTest{"cvo-az", "CVO-AZ"},
    ucTest{"Antwerp", "ANTWERP"},
}

func TestUC(t *testing.T) {
    for _, ut := range ucTests {
        uc := UpperCase(ut.in)
        if uc != ut.out {
            t.Errorf("UpperCase(%s) = %s, must be %s", ut.in, uc,
            ut.out)
        }
    }
}

通过指令编译并安装包到本地:go install uc, 这会将 uc.a 复制到 pkg/linux_amd64 下面。

另外,使用 make ,使用以下内容在 src/uc 目录下创建一个包的 Makefile :

include $(GOROOT)/src/Make.inc

TARG=uc
GOFILES=\
        uc.go\

include $(GOROOT)/src/Make.pkg

本地安装包

本地包在用户目录下,使用给出的目录结构,以下命令用来从源码安装本地包:

go install /home/user/goprograms/src/uc # 编译安装uc
cd /home/user/goprograms/uc
go install ./uc     # 编译安装uc(和之前的指令一样)
cd ..
go install .    # 编译安装ucmain

安装到 $GOPATH 下:

如果我们想安装的包在系统上的其他 Go 程序中被使用,它一定要安装到 $GOPATH 下。
这样做,在 .profile.bashrc 中设置 export GOPATH=/home/user/goprograms

然后执行 go install uc 将会复制包存档到 $GOPATH/pkg/LINUX_AMD64/uc

现在,uc 包可以通过 import "uc" 在任何 Go 程序中被引用。

依赖系统的代码

在不同的操作系统上运行的程序以不同的代码实现是非常少见的:绝大多数情况下语言和标准库解决了大部分的可移植性问题。

你有一个很好的理由去写平台特定的代码,例如汇编语言。这种情况下,按照下面的约定是合理的:

prog1.go
prog1_linux.go
prog1_darwin.go
prog1_windows.go
© 版权声明
THE END
喜欢就支持一下吧
点赞13 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容