标签 - Go语言四十二章经

Go语言四十二章经    2021-01-27 10:09:44    13    0    0

《Go语言四十二章经》第三十二章 fmt包

作者:李骁

32.1 fmt包格式化I/O

上一章我们有提到fmt格式化I/O,这一章我们就详细来说说。在fmt包,有关格式化输入输出的方法就两大类:Scan 和 Print ,分别在scan.go 和 print.go 文件中。

print.go文件中定义了如下函数:

  1. func Printf(format string, a ...interface{}) (n int, err error)
  2. func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
  3. func Sprintf(format string, a ...interface{}) string
  4. func Print(a ...interface{}) (n int, err error)
  5. func Fprint(w io.Writer, a ...interface{}) (n int, err error)
  6. func Sprint(a ...interface{}) string
  7. func Println(a ...interface{}) (n int, err error)
  8. func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
  9. func Sprintln(a ...interface{}) string

这9个函数,按照两个维度来说明,基本上可以说明白了。当然这两个维度是我个人为了记忆而分,并不是官方的说法。

一:如果把"Print"理解为核心关键字,那么后面跟的后缀有"f"和"ln"以及"",着重的是内容输出的结果;

如果后缀是"f", 则指定了format
如果后缀是"ln", 则有换行符

  1. PrintlnFprintlnSprintln 输出内容时会加上换行符;
  2. PrintFprintSprint 输出内容时不加上换行符;
  3. PrintfFprintfSprintf 按照指定格式化文本输出内容。
Go语言四十二章经    2021-01-27 10:09:44    9    0    0

《Go语言四十二章经》第三十一章 文件操作与IO

作者:李骁

31.1 文件系统

对于文件和目录的操作,Go主要在os 提供了的相应函数:

  1. func Mkdir(name string, perm FileMode) error
  2. func Chdir(dir string) error
  3. func TempDir() string
  4. func Rename(oldpath, newpath string) error
  5. func Chmod(name string, mode FileMode) error
  6. func Open(name string) (*File, error) {
  7. return OpenFile(name, O_RDONLY, 0)
  8. }
  9. func Create(name string) (*File, error) {
  10. return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
  11. }
  12. func OpenFile(name string, flag int, perm FileMode) (*File, error) {
  13. testlog.Open(name)
  14. return openFileNolog(name, flag, perm)
  15. }

从上面函数定义中我们可以发现一个情况:那就是os中不同函数打开(创建)文件的操作,最终还是通过OpenFile来实现,而OpenFile由编译器根据系统的情况来选择不同的底层功能来实现,对这个实现细节有兴趣可以根据标准包来仔细了解,这里就不展开讲了。

  1. os.Open(name string) 使用只读模式打开文件;
  2. os.Create(name string) 创建新文件,如文件存在则原文件内容会丢失;
  3. os.OpenFile(name string, flag int, perm FileMode) 这个函数可以指定flagFileMode 。这三个函数都会返回一个文件对象。
  1. Flag
  2. O_RDONLY int = syscall.O_RDONLY // 只读打开文件和os.Open()同义
  3. O_WRONLY int =
Go语言四十二章经    2021-01-27 10:09:44    75    0    0

《Go语言四十二章经》第三十章 OS包

作者:李骁

30.1 启动外部命令和程序

os 包有一个 StartProcess 函数可以调用或启动外部系统命令和二进制可执行文件;它的第一个参数是要运行的进程,第二个参数用来传递选项或参数,第三个参数是含有系统环境基本信息的结构体。

这个函数返回被启动进程的 id(pid),或者启动失败返回错误。

exec 包中也有同样功能的更简单的结构体和函数;主要是 exec.Command(name string, arg ...string) 和 Run()。首先需要用系统命令或可执行文件的名字创建一个 Command 对象,然后用这个对象作为接收器调用 Run()。下面的程序(因为是执行 Linux 命令,只能在 Linux 下面运行)演示了它们的使用:

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. )
  6. func main() {
  7. // 1) os.StartProcess //
  8. /*********************/
  9. /* Linux: */
  10. env := os.Environ()
  11. procAttr := &os.ProcAttr{
  12. Env: env,
  13. Files: []*os.File{
  14. os.Stdin,
  15. os.Stdout,
  16. os.Stderr,
  17. },
  18. }
  19. // 1st example: list files
  20. Pid, err := os.StartProcess("/bin/ls", []string{"ls", "-l"}, procAttr)
  21. if err != nil {
  22. fmt.Printf("Error %v starting process!", err) //
  23. os.Exit(1)
  24. }
  25. fmt.Printf("The process id is %v", pid)
  26. }

30.2 os/signal 信号处理

信号处理在平

Go语言四十二章经    2021-01-27 10:09:44    22    0    0

《Go语言四十二章经》第二十九章 排序(sort)

作者:李骁

29.1 sort包介绍

Go语言标准库sort包中实现了3种基本的排序算法:插入排序、快排和堆排序。和其他语言中一样,这三种方式都是不公开的,他们只在sort包内部使用。所以用户在使用sort包进行排序时无需考虑使用那种排序方式,sort.Interface定义的三个方法:获取数据集合长度的Len()方法、比较两个元素大小的Less()方法和交换两个元素位置的Swap()方法,就可以顺利对数据集合进行排序。sort包会根据实际数据自动选择高效的排序算法。

  1. type Interface
  2. type Interface interface {
  3. Len() int // Len 为集合内元素的总数
  4. Less(i, j int) bool //如果index为i的元素小于index为j的元素,则返回true,否则false
  5. Swap(i, j int) // Swap 交换索引为 i 和 j 的元素
  6. }

sort包里面已经实现了[]int, []float64, []string的排序。

任何实现了 sort.Interface 的类型(一般为集合),均可使用该包中的方法进行排序。这些方法要求集合内列出元素的索引为整数。

  1. package main
  2. import (
  3. "fmt"
  4. "sort"
  5. )
  6. func main() {
  7. a := []int{3, 5, 4, -1, 9, 11, -14}
  8. sort.Ints(a)
  9. fmt.Println(a)
  10. ss := []string{"surface", "ipad", "mac pro", "mac air", "think pad", "idea pad"}
  11. sort.Strings(ss)
  12. fmt.Println(ss)
  13. sort.Sort(sort.Reverse(sort.StringSlice(ss)))
  14. fmt.Printf("After reverse: %v\n", ss)
  15. }
  1. 程序输出:
  2. [-14 -1 3 4 5 9 11
Go语言四十二章经    2021-01-27 10:09:44    6    0    0

《Go语言四十二章经》第二十八章 unsafe包

作者:李骁

28.1 unsafe 包

  1. func Alignof(x ArbitraryType) uintptr
  2. func Offsetof(x ArbitraryType) uintptr
  3. func Sizeof(x ArbitraryType) uintptr
  4. type ArbitraryType int
  5. type Pointer *ArbitraryType

这个包中,只提供了3个函数,两个类型。就这么少的量,却有着超级强悍的功能。学过C语言的都可能比较清楚,通过指针,知道变量在内存中占用的字节数,就可以通过指针加偏移量的操作,在地址中,修改,访问变量的值。在Go 语言中,怎么去实现这么疯狂的操作呢?就得靠unsafe包了。

ArbitraryType 是int的一个别名,但是Go 语言中,对ArbitraryType赋予了特殊的意义,千万不要死磕这个后边的int类型。通常,我们把interface{}看作是任意类型,那么ArbitraryType这个类型,在Go 语言系统中,比interface{}还要随意。

Pointer 是int指针类型的一个别名,在Go 语言系统中,可以把Pointer类型,理解成通用指针类型,用于转换不同类型指针。

Go 语言的指针类型长度与int类型长度,在内存中占用的字节数是一样的。ArbitraryType类型的变量也可以是指针。所以,千万不要死磕type后边的那个int。

  1. func Alignof(x ArbitraryType) uintptr
  2. func Offsetof(x ArbitraryType) uintptr
  3. func Sizeof(x ArbitraryType) uintptr

通过分析发现,这三个函数的参数均是ArbitraryType类型,就是接受任何类型的变量。
1. Alignof返回变量对齐字节数量
2. Offsetof返回变量指定属性的偏移量,这个函数虽然接收的是任何类型的变量,但是这个又一个前提,就是变量要是一个struct类型,且还不能直接将这个struct类型的变量当作参数,只能将这个struct

Go语言四十二章经    2021-01-27 10:09:44    23    0    0

《Go语言四十二章经》第二十七章 反射(reflect)

作者:李骁

27.1 反射(reflect)

反射是用程序检查其所拥有的结构,尤其是类型的一种能力;这是元编程的一种形式。

反射可以在运行时检查类型和变量,例如它的大小、方法和 动态 的调用这些方法。这对于没有源代码的包尤其有用。

这是一个强大的工具,除非真得有必要,否则应当避免使用或小心使用。

变量的最基本信息就是类型和值。反射包的 Type 用来表示一个 Go 类型,反射包的 Value 为 Go 值提供了反射接口。

两个简单的函数,reflect.TypeOf 和 reflect.ValueOf,返回被检查对象的类型和值。

例如,x 被定义为:var x float64 = 3.4,那么 reflect.TypeOf(x) 返回 float64,reflect.ValueOf(x) 返回

实际上,反射是通过检查一个接口的值,变量首先被转换成空接口。这从下面两个函数签名能够很明显的看出来:

  1. func TypeOf(i interface{}) Type
  2. func ValueOf(i interface{}) Value

接口的值包含一个 type 和 value。

反射可以从接口值反射到对象,也可以从对象反射回接口值。

reflect.Type 和 reflect.Value 都有许多方法用于检查和操作它们。一个重要的例子是 Value 有一个 Type 方法返回 reflect.Value 的 Type。另一个是 Type 和 Value 都有 Kind 方法返回一个常量来表示类型:Uint、Float64、Slice 等等。同样 Value 有叫做 Int 和 Float 的方法可以获取存储在内部的值(跟 int64 和 float64 一样)

问题的原因是 v 不是可设置的(这里并不是说值不可寻址)。是否可设置是 Value 的一个属性,并且不是所有的反射值都有这个属性:可以使用 CanSet() 方法测试是否可设置。反射中有些内容是需要用地址去改变它的状态的。
当 v := reflect.ValueOf(x) 函数通过传递一个 x 拷贝创建

Go语言四十二章经    2021-01-27 10:09:44    21    0    0

《Go语言四十二章经》第二十六章 测试

作者:李骁

26.1 单元测试

首先所有的包都应该有一定的必要文档,然后同样重要的是对包的测试。

名为 testing 的包被专门用来进行自动化测试,日志和错误报告。并且还包含一些基准测试函数的功能。

对一个包做(单元)测试,需要写一些可以频繁(每次更新后)执行的小块测试单元来检查代码的正确性。于是我们必须写一些 Go 源文件来测试代码。测试程序必须属于被测试的包,并且文件名满足这种形式 *_test.Go,所以测试代码和包中的业务代码是分开的。

_test 程序不会被普通的 Go 编译器编译,所以当放应用部署到生产环境时它们不会被部署;只有 Gotest 会编译所有的程序:普通程序和测试程序。

测试文件中必须导入 "testing" 包,并写一些名字以 TestZzz 打头的全局函数,这里的 Zzz 是被测试函数的字母描述,如 TestFmtInterface,TestPayEmployees 等。

测试函数必须有这种形式的头部:

  1. func TestAbcde(t *testing.T)

T 是传给测试函数的结构类型,用来管理测试状态,支持格式化测试日志,如 t.Log,t.Error,t.ErrorF 等。在函数的结尾把输出跟想要的结果对比,如果不等就打印一个错误。成功的测试则直接返回。

用下面这些函数来通知测试失败:

1)func (t *T) Fail()

  1. 标记测试函数为失败,然后继续执行(剩下的测试)。

2)func (t *T) FailNow()

  1. 标记测试函数为失败并中止执行;文件中别的测试也被略过,继续执行下一个文件。

3)func (t *T) Log(args ...interface{})

  1. args 被用默认的格式格式化并打印到错误日志中。

4)func (t *T) Fatal(args ...interface{})

  1. 结合 先执行 3),然后执行 2)的效果。

运行 Go test 来编译测试程序,并执行程序中所有的 TestZZZ 函数。如果所有的测试都通过会打印出 PASS。

对不能导出的函数不能进行单元或者基准测试。

Gote

Go语言四十二章经    2021-01-27 10:09:44    24    0    0

《Go语言四十二章经》第二十五章 面向对象

作者:李骁

25.1 Go 中的面向对象

我们总结一下前面看到的:Go 没有类,而是松耦合的类型、方法对接口的实现。

OO 语言最重要的三个方面分别是:封装,继承和多态,在 Go 中它们是怎样表现的呢?

封装(数据隐藏):

和别的 OO 语言有 4 个或更多的访问层次相比,Go 把它简化为了 2 层:
1)包范围内的:通过标识符首字母小写,对象 只在它所在的包内可见
2)可导出的:通过标识符首字母大写,对象 对所在包以外也可见类型只拥有自己所在包中定义的方法。

继承:

用组合实现,内嵌一个(或多个)包含想要的行为(字段和方法)的类型;多重继承可以通过内嵌多个类型实现。

多态:

用接口实现,某个类型的实例可以赋给它所实现的任意接口类型的变量。类型和接口是松耦合的,并且多重继承可以通过实现多个接口实现。Go 接口不是 Java 和 C# 接口的变体,而且:接口间是不相关的,并且是大规模编程和可适应的演进型设计的关键。

25.2 多重继承

多重继承指的是类型获得多个父类型行为的能力,它在传统的面向对象语言中通常是不被实现的(C++ 和 Python 例外)。因为在类继承层次中,多重继承会给编译器引入额外的复杂度。但是在 Go 语言中,通过在类型中嵌入所有必要的父类型,可以很简单的实现多重继承。

Go语言四十二章经    2021-01-27 10:09:44    22    0    0

《Go语言四十二章经》第二十四章 指针和内存

作者:李骁

24.1 指针

一个指针变量可以指向任何一个值的内存地址。它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。当然,可以声明指针指向任何类型的值来表明它的原始性或结构性;你可以在指针类型前面加上*号(前缀)来获取指针所指向的内容,这里的*号是一个类型更改器。使用一个指针引用一个值被称为间接引用。

当一个指针被定义后没有分配到任何变量时,它的值为 nil。

一个指针变量通常缩写为 ptr。

符号 "*" 可以放在一个指针前,如 “*intP”,那么它将得到这个指针指向地址上所存储的值;这被称为反引用(或者内容或者间接引用)操作符;另一种说法是指针转移。

对于任何一个变量 var, 如下表达式都是正确的:var == *(&var)

注意事项:

你不能得到一个数字或常量的地址,下面的写法是错误的。

例如:

  1. const i = 5
  2. ptr := &i // error: cannot take the address of i
  3. ptr2 := &10 // error: cannot take the address of 10

所以说,Go 语言和 C、C++ 以及 D 语言这些低级(系统)语言一样,都有指针的概念。

但是对于经常导致 C 语言内存泄漏继而程序崩溃的指针运算(所谓的指针算法,如:pointer+2,移动指针指向字符串的字节数或数组的某个位置)是不被允许的。

Go 语言中的指针保证了内存安全,更像是 Java、C# 和 VB.NET 中的引用。

因此 c = *p++ 在 Go 语言的代码中是不合法的。

指针的一个高级应用是你可以传递一个变量的引用(如函数的参数),这样不会传递变量的拷贝。指针传递是很廉价的,只占用 4 个或 8 个字节。当程序在工作中需要占用大量的内存,或很多变量,或者两者都有,使用指针会减少内存占用和提高效率。被指向的变量也保存在内存中,直到没有任何指针指向它们,所以从它们被创建开始就具有相互独立的生命周期。

另一方面(虽然不太可能),

Go语言四十二章经    2021-01-27 10:09:44    27    0    0

《Go语言四十二章经》第二十三章 锁

作者:李骁

23.1 同步锁

Go语言包中的sync包提供了两种锁类型:sync.Mutex和sync.RWMutex,前者是互斥锁,后者是读写锁。

互斥锁是传统的并发程序对共享资源进行访问控制的主要手段,在Go中,似乎更推崇由channel来实现资源共享和通信。它由标准库代码包sync中的Mutex结构体类型代表。只有两个公开方法:调用Lock()获得锁,调用unlock()释放锁。

  • 使用Lock()加锁后,不能再继续对其加锁(同一个goroutine中,即:同步调用),否则会panic。只有在unlock()之后才能再次Lock()。异步调用Lock(),是正当的锁竞争,当然不会有panic了。适用于读写不确定场景,即读写次数没有明显的区别,并且只允许只有一个读或者写的场景,所以该锁也叫做全局锁。

  • func (m *Mutex) Unlock()用于解锁m,如果在使用Unlock()前未加锁,就会引起一个运行错误。已经锁定的Mutex并不与特定的goroutine相关联,这样可以利用一个goroutine对其加锁,再利用其他goroutine对其解锁。

建议:同一个互斥锁的成对锁定和解锁操作放在同一层次的代码块中。
使用锁的经典模式:

  1. var lck sync.Mutex
  2. func foo() {
  3. lck.Lock()
  4. defer lck.Unlock()
  5. // ...
  6. }

lck.Lock()会阻塞直到获取锁,然后利用defer语句在函数返回时自动释放锁。

下面代码通过3个goroutine来体现sync.Mutex 对资源的访问控制特征:

  1. package main
  2. import (
  3. "fmt"
  4. "sync"
  5. "time"
  6. )
  7. func main() {
  8. wg := sync.WaitGroup{}
  9. var mutex sync.Mutex
  10. fmt.Println("Locking (G0)")
  11. mutex.Lock()
  12. fmt.Println("locked (G0)")
  13. wg.Add(3)
  14. f
2/5