The way to Go 要点知识

1. 基本结构和数据类型

1.1 go程序的基本结构和要素

  1. 一个应用程序可以包含不同的包,而且即使你只使用 main 包也不必把所有的代码都写在一个巨大的文件里:你可以用一些较小的文件,并且在每个文件非注释的第一行都使用 package main 来指明这些文件都属于 main 包。
  2. Go 中的包模型采用了显式依赖关系的机制来达到快速编译的目的,编译器会从后缀名为 .o 的对象文件(需要且只需要这个文件)中提取传递依赖类型的信息。
  3. 如果 A.go 依赖 B.go,而 B.go 又依赖 C.go
    编译 C.go, B.go, 然后是 A.go.
    为了编译 A.go, 编译器读取的是 B.o 而不是 C.o.
  4. .如果包名不是以 ./ 开头,如 "fmt" 或者 "container/list",则 Go 会在全局文件进行查找;如果包名以 ./ 开头,则 Go 会在相对目录中查找;如果包名以 / 开头(在 Windows 下也可以这样使用),则会在系统的绝对路径中查找。
  5. 给导入的包起别名
package main

import fm "fmt" // alias3

func main() {
   fm.Println("hello, world")
}
  1. 包的分级声明和初始化
    你可以在使用 import 导入包之后定义或声明 0 个或多个常量 (const)、变量 (var) 和类型 (type),这些对象的作用域都是全局的(**在本包范围内**),所以可以被本包中所有的函数调用.

1.2 变量

1.当一个变量被声明之后,系统自动赋予它该类型的零值:int 为 0,float32(64) 为 0.0,bool 为 false,string 为空字符串,指针为 nil。记住,所有的内存在 Go 中都是经过初始化的。

1.3 基本类型和运算符

1.两个类型相同的值可以使用相等 == 或者不等 != 运算符来进行比较并获得一个布尔型的值

var aVar = 10
aVar == 5 -> false
aVar == 10 -> true

2 .位清除 &^:将指定位置上的值设置为 0
x=15 y=4
x &^ y

x :     1111
y:    	 0100
x &^ y: 1011

y中为1的位 对应于x的位清0,若y中其他位为0 对应x中其他位不变。

1.4 运算符与优先级

二元运算符的运算方向均是从左至右
优先级 	运算符
 7 		^ !
 6 		* / % << >> & &^
 5 		+ - | ^
 4 		== != < <= >= >
 3 		<-
 2 		&&
 1 		||

2. 控制结构

2.1 fmt 包 最简单的打印函数也有 2 个返回值:

count, err := fmt.Println(x) // number of bytes printed, nil or 0, error

当打印到控制台时,可以将该函数返回的错误忽略;但当输出到文件流、网络流等具有不确定因素的输出对象时,应该始终检查是否有错误发生

2.2 使用goto 执行循环打印

i := 0
START:
	fmt.Printf("The counter is at %d\n", i)
	i++
	if i < 4 {
		goto START
	}

在这里插入图片描述

2.3 标签与goto

  1. forswitchselect 语句都可以配合标签 (label) 形式的标识符使用。
  2. 如果您必须使用 goto,应当只使用正序的标签(标签位于 goto 语句之后),但注意标签和 goto 语句之间不能出现定义新变量的语句,否则会导致编译失败
// compile error goto2.go:8: goto TARGET jumps over declaration of b at goto2.go:8
package main

import "fmt"

func main() {
		a := 1
		goto TARGET // compile error
		b := 9
	TARGET:  
		b += a
		fmt.Printf("a is %v *** b is %v", a, b)
}

3.函数

3.1 介绍

1.函数可以将其他函数调用作为它的参数,只要这个被调用函数的返回值个数、返回值类型和返回值的顺序与调用函数所需求的实参是一致的,例如:

假设 f1 需要 3 个参数 f1(a, b, c int),同时 f2 返回 3 个参数 f2(a, b int) (int, int, int),就可以这样调用 f1:f1(f2(a, b))

3.2 函数参数与返回值

1.事实上,任何一个有返回值(单个或多个)的函数都必须以 returnpanic(参考 第 13 章)结尾
2.在函数调用时,像切片 (slice)、字典 (map)、接口 (interface)、通道 (channel) 这样的引用类型都是默认使用引用传递(即使没有显式的指出指针)
3.如果一个函数需要返回四到五个值,我们可以传递一个切片给函数(如果返回值具有相同类型)或者是传递一个结构体(如果返回值具有不同的类型)。因为传递一个指针允许直接修改变量的值,消耗也更少

3.2.1 命名的返回值

返回值有名称与无名称

package main

import "fmt"

var num int = 10
var numx2, numx3 int

func main() {
    numx2, numx3 = getX2AndX3(num)
    PrintValues()
    numx2, numx3 = getX2AndX3_2(num)
    PrintValues()
}

func PrintValues() {
    fmt.Printf("num = %d, 2x num = %d, 3x num = %d\n", num, numx2, numx3)
}
//无名称
func getX2AndX3(input int) (int, int) {
    return 2 * input, 3 * input
}
//有名称
func getX2AndX3_2(input int) (x2 int, x3 int) {
    x2 = 2 * input
    x3 = 3 * input
    // return x2, x3
    return
}

3.3 传递变长参数

1.如果函数的最后一个参数是采用 ...type 的形式,那么这个函数就可以处理一个变长的参数,这个长度可以为 0,这样的函数称为变参函数

func myFunc(a, b, arg ...int) {}

eg.

func Greeting(prefix string, who ...string)
Greeting("hello:", "Joe", "Anna", "Eileen")

Greeting() 函数中,变量 who 的值为 []string{"Joe", "Anna", "Eileen"}
2.如果参数被存储在一个 slice 类型的变量 slice 中,则可以通过 slice... 的形式来传递参数,调用变参函数

package main

import "fmt"

func main() {
	x := min(1, 3, 2, 0)
	fmt.Printf("The minimum is: %d\n", x)
	slice := []int{7,9,3,5,1}
	// min函数调用:slice... 
	x = min(slice...)
	fmt.Printf("The minimum in the slice is: %d", x)
}

func min(s ...int) int {
	if len(s)==0 {
		return 0
	}
	min := s[0]
	for _, v := range s {
		if v < min {
			min = v
		}
	}
	return min
}

3.一个接受变长参数的函数可以将这个参数作为其它函数的参数进行传递

func F1(s ...string) {
	F2(s...)
	F3(s)
}

func F2(s ...string) { }
func F3(s []string) { }
  1. 但是如果变长参数的类型并不是都相同的呢?
    使用 结构体空接口
    4.1 结构体
    函数 F1() 可以使用正常的参数 a 和 b,以及一个没有任何初始化的 Options 结构: F1(a, b, Options {})。如果需要对选项进行初始化,则可以使用 F1(a, b, Options {par1:val1, par2:val2})
    4.2 空接口
    如果一个变长参数的类型没有被指定,则可以使用默认的空接口 interface{},这样就可以接受任何类型的参数(详见第 11.9 节 )。该方案不仅可以用于长度未知的参数,还可以用于任何不确定类型的参数。一般而言我们会使用一个 for-range 循环以及 switch 结构对每个参数的类型进行判断。
func typecheck(..,..,values … interface{}) {
	for _, value := range values {
		switch v := value.(type) {
			case int:case float64:case string:case bool:default:}
	}
}

3.4 defer

多个defer的执行顺序为“后进先出”;
returndefer 之前
defer、return、返回值三者的执行逻辑应该是:

(1) return最先执行,return负责将结果写入返回值中;

(2) 接着defer开始执行一些收尾工作;

(3) 最后函数携带当前返回值退出

package main

import (
	"io"
	"log"
)

func func1(s string) (n int, err error) {
	defer func() {
		log.Printf("func1(%q) = %d, %v", s, n, err)
	}()
	return 7, io.EOF
}

func main() {
	func1("Go")
}

3.5 内置函数

在这里插入图片描述

3.6 递归函数 计算 斐波那契数列

package main

import "fmt"

func main() {
	result := 0
	for i := 0; i <= 10; i++ {
		result = fibonacci(i)
		fmt.Printf("fibonacci(%d) is: %d\n", i, result)
	}
}

func fibonacci(n int) (res int) {
	if n <= 1 {
		res = 1
	} else {
		res = fibonacci(n-1) + fibonacci(n-2)
	}
	return
}

3.7 将函数作为参数

函数可以作为其它函数的参数进行传递,然后在其它函数内调用执行,一般称之为回调

package main

import (
	"fmt"
)

func main() {
	callback(1, Add)
}

func Add(a, b int) {
	fmt.Printf("The sum of %d and %d is: %d\n", a, b, a+b)
}

func callback(y int, f func(int, int)) {
	f(y, 2) // this becomes Add(1, 2)
}
//The sum of 1 and 2 is: 3

3.8 闭包

我们不希望给函数起名字的时候,可以使用匿名函数,例如:func(x, y int) int { return x + y }
这样的一个函数不能够独立存在(编译器会返回错误:non-declaration statement outside function body),但可以被赋值于某个变量,即保存函数的地址到变量中fplus := func(x, y int) int { return x + y },然后通过变量名对函数进行调用:fplus(3,4)
当然,您也可以直接对匿名函数进行调用func(x, y int) int { return x + y } (3, 4)

匿名函数同样被称之为闭包(函数式语言的术语):它们被允许调用定义在其它环境下的变量。闭包可使得某个函数捕捉到一些外部状态,例如:函数被创建时的状态。另一种表示方式为:一个闭包继承了函数所声明时的作用域。这种状态(作用域内的变量)都被共享到闭包的环境中,因此这些变量可以在闭包中被操作,直到被销毁,详见第 6.9 节 中的示例。闭包经常被用作包装函数:它们会预先定义好 1 个或多个参数以用于包装,详见下一节中的示例。另一个不错的应用就是使用闭包来完成更加简洁的错误检查(详见第 16.10.2 节)。

3.9 应用闭包:将函数作为返回值

//函数返回类型为 func(b int)int
func Add2() func(b int) int {
	return func(b int) int {
		return b + 2
	}
}
func Adder(a int) func(b int) int {
	return func(b int) int {
		return a + b
	}
}
func main() {
	p2 := Add2()
	fmt.Printf("Call Add2 for 3 gives: %v\n", p2(3)) //输出5
	adder := Adder(1)
	fmt.Printf("The result is: %v\n", adder(3))
}
输出结果
Call Add2 for 3 gives: 5
The result is: 4

package main

import "fmt"

func main() {
	var f = Adder()
	fmt.Print(f(1), " - ")
	fmt.Print(f(20), " - ")
	fmt.Print(f(300))
}

func Adder() func(int) int {
	var x int
	return func(delta int) int {
		x += delta
		return x
	}
}
输出结果:
1 - 21 - 321

三次调用函数 f 的过程中函数 Adder() 中变量 delta 的值分别为:1、20 和 300。

我们可以看到,在多次调用中,变量 x 的值是被保留的,即 0 + 1 = 1,然后 1 + 20 = 21,最后 21 + 300 = 321:闭包函数保存并积累其中的变量的值,不管外部函数退出与否,它都能够继续操作外部函数中的局部变量

一个返回值为另一个函数函数可以被称之为工厂函数,这在您需要创建一系列相似的函数的时候非常有用:书写一个工厂函数而不是针对每种情况都书写一个函数。下面的函数演示了如何动态返回追加后缀的函数:

func MakeAddSuffix(suffix string) func(string) string {
	return func(name string) string {
		if !strings.HasSuffix(name, suffix) {
			return name + suffix
		}
		return name
	}
}

现在,我们可以生成如下函数:

addBmp := MakeAddSuffix(".bmp")
addJpeg := MakeAddSuffix(".jpeg")

然后调用它们:

addBmp("file") // returns: file.bmp
addJpeg("file") // returns: file.jpeg

3.10 闭包调试

//skip是要提升的堆栈帧数,0-当前函数,1-上一层函数,.... 2 一般为调用A函数的位置,where()在A函数里
where := func() {
	_, file, line, _ := runtime.Caller(2)
	log.Printf("%s:%d", file, line)
}
where()
// some code
where()
// some more code
where()

3.11 计算函数执行时间

start := time.Now()
longCalculation()
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)

7.数组与切片

7.1声明和初始化

7.1.3多维数组 二维数组为例

// multidim_array.go 
package main

import "fmt"

const (
	WIDTH  = 1920
	HEIGHT = 1080
	// WIDTH =	5
	// HEIGHT = 4
)

type pixel int

var screen [WIDTH][HEIGHT]pixel

func main() {
	for y := 0; y < HEIGHT; y++ {
		for x := 0; x < WIDTH; x++ {
			screen[x][y] = 0
		}
	}
	fmt.Println(screen)

	for row := range screen {
		for column := range screen[0] {
			screen[row][column] = 1
		}
	}

	fmt.Println(screen)
}

/* Output for WIDTH =	5 and 	HEIGHT = 4:
[[0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0] [0 0 0 0]]
[[1 1 1 1] [1 1 1 1] [1 1 1 1] [1 1 1 1] [1 1 1 1]]
*/

7.2 切片

1.对于每一个切片(包括 string),以下状态总是成立

s == s[:i] + s[i:] // i是一个整数且: 0 <= i <= len(s)
len(s) <= cap(s)

2 .len cap

package main
import "fmt"
从切片s的索引位置low到high处所获得的切片,len=high-low,cap=max-low
func main() {
	var arr1 [6]int
	var slice1 []int = arr1[2:5] // item at index 5 not included!

	// load the array with integers: 0,1,2,3,4,5
	for i := 0; i < len(arr1); i++ {
		arr1[i] = i
	}

	// print the slice
	for i := 0; i < len(slice1); i++ {
		fmt.Printf("Slice at %d is %d\n", i, slice1[i])
	}

	fmt.Printf("The length of arr1 is %d\n", len(arr1))
	fmt.Printf("The length of slice1 is %d\n", len(slice1))
	fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))

	// grow the slice
	slice1 = slice1[0:4]
	for i := 0; i < len(slice1); i++ {
		fmt.Printf("Slice at %d is %d\n", i, slice1[i])
	}
	fmt.Printf("The length of slice1 is %d\n", len(slice1))
	fmt.Printf("The capacity of slice1 is %d\n", cap(slice1))

	// grow the slice beyond capacity
	//slice1 = slice1[0:7 ] // panic: runtime error: slice bound out of range
}

输出:

Slice at 0 is 2  
Slice at 1 is 3  
Slice at 2 is 4  
The length of arr1 is 6  
The length of slice1 is 3    high-low=5-2=3
The capacity of slice1 is 4   没有指明默认max=6 max-low=6-2=4
Slice at 0 is 2  
Slice at 1 is 3  
Slice at 2 is 4  
Slice at 3 is 5    超出slice1范围 继承 arr1中的5
The length of slice1 is 4    high-low=4-0=4
The capacity of slice1 is 4   cap(slice1)=4    max(slice1)-low(slice1)=4-0=4

7.2.2 将切片传递给函数

计算数组元素和

func sum(a []int) int {
	s := 0
	for i := 0; i < len(a); i++ {
		s += a[i]
	}
	return s
}

func main() {
	var arr = [5]int{0, 1, 2, 3, 4}
	sum(arr[:])
}

1.所以下面两种方法可以生成相同的切片:

make([]int, 50, 100)
new([100]int)[0:50]

7.2.4 new 和make 区别

new(T) 为每个新的类型 T 分配一片内存,初始化为 0 并且返回类型为 *T 的内存地址:这种方法 返回一个指向类型为 T值为 0 的地址的指针,它适用于值类型如数组和结构体(参见第 10 章);它相当于 &T{}。
make(T) 返回一个类型为 T 的初始值,它只适用于 3 种内建的引用类型:切片mapchannel(参见第 8 章和第 13 章)。
换言之,new() 函数分配内存make() 函数初始化

7.3 For-range 结构

1.如果你只需要索引,你可以忽略第二个变量,例如:

for ix := range seasons {
	fmt.Printf("%d", ix)
}
// Output: 0 1 2 3

7.4 切片重组 (reslice)

改变切片长度的过程称之为切片重组 reslicing

  1. 将切片扩展 1 位可以这么做:
//切片可以反复扩展直到占据整个相关数组。
sl = sl[0:len(sl)+1]
  1. 切片扩展例子
package main
import "fmt"

func main() {
	slice1 := make([]int, 0, 10)
	// load the slice, cap(slice1) is 10:
	for i := 0; i < cap(slice1); i++ {
		slice1 = slice1[0:i+1]
		slice1[i] = i
		fmt.Printf("The length of slice is %d\n", len(slice1))
	}

	// print the slice:
	for i := 0; i < len(slice1); i++ {
		fmt.Printf("Slice at %d is %d\n", i, slice1[i])
	}
}

输出

The length of slice is 1
The length of slice is 2
The length of slice is 3
The length of slice is 4
The length of slice is 5
The length of slice is 6
The length of slice is 7
The length of slice is 8
The length of slice is 9
The length of slice is 10
Slice at 0 is 0
Slice at 1 is 1
Slice at 2 is 2
Slice at 3 is 3
Slice at 4 is 4
Slice at 5 is 5
Slice at 6 is 6
Slice at 7 is 7
Slice at 8 is 8
Slice at 9 is 9

7.5 切片的复制与追加

append() 在大多数情况下很好用,但是如果你想完全掌控整个追加过程,你可以实现一个这样的 AppendByte() 方法:

func AppendByte(slice []byte, data ...byte) []byte {
	m := len(slice)
	n := m + len(data)
	if n > cap(slice) { // if necessary, reallocate
		// allocate double what's needed, for future growth.
		newSlice := make([]byte, (n+1)*2)
		copy(newSlice, slice)
		slice = newSlice //因为还想要使用slice切片
	}
	slice = slice[0:n] //切片扩容
	copy(slice[m:n], data) //将data切片赋值给slice里的[m:n]元素
	return slice
}

练习 7.9

给定一个切片 s []int 和一个 int 类型的因子 factor,扩展 s 使其长度为 len(s) * factor

package main

import "fmt"

var s []int

func main() {
	s = []int{1, 2, 3}
	fmt.Println("The length of s before enlarging is:", len(s))
	fmt.Println(s)
	s = enlarge(s, 5)
	fmt.Println("The length of s after enlarging is:", len(s))
	fmt.Println(s)
}

func enlarge(s []int, factor int) []int {
	ns := make([]int, len(s)*factor)
	// fmt.Println("The length of ns  is:", len(ns))
	copy(ns, s)
	//fmt.Println(ns)
	s = ns
	//fmt.Println(s)
	//fmt.Println("The length of s after enlarging is:", len(s))
	return s
}

练习 7.10

写一个函数 InsertStringSlice() 将切片插入到另一个切片的指定位置

package main

import (
	"fmt"
)

func main() {
	s := []string{"M", "N", "O", "P", "Q", "R"}
	in := []string{"A", "B", "C"}
	res := InsertStringSlice(s, in, 0) // at the front
	fmt.Println(res)                   // [A B C M N O P Q R]
	res = InsertStringSlice(s, in, 3)  // [M N O A B C P Q R]
	fmt.Println(res)
}

func InsertStringSlice(slice, insertion []string, index int) []string {
	result := make([]string, len(slice)+len(insertion))
	at := copy(result, slice[:index])
	at += copy(result[at:], insertion)
	copy(result[at:], slice[index:])
	return result
}

练习 7.11

写一个函数 RemoveStringSlice() 将从 startend 索引的元素从切片中移除

// remove_slice.go
package main

import (
	"fmt"
)

func main() {
	s := []string{"M", "N", "O", "P", "Q", "R"}
	res := RemoveStringSlice(s, 2, 4)
	fmt.Println(res) // [M N Q R]
}

func RemoveStringSlice(slice []string, start, end int) []string {
	result := make([]string, len(slice)-(end-start))
	at := copy(result, slice[:start])
	copy(result[at:], slice[end:])
	return result
}

练习 7.12

用顺序函数过滤容器:s 是前 10 个整型的切片。构造一个函数 Filter,第一个参数是 s,第二个参数是一个 fn func(int) bool返回满足函数 fn 的元素切片。通过 fn 测试方法测试当整型值是偶数时的情况。

package main

import (
	"fmt"
)

func main() {
	s := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
	s = Filter(s, even)
	fmt.Println(s)
}

// Filter returns a new slice holding only
// the elements of s that satisfy f()
func Filter(s []int, fn func(int) bool) []int {
	var p []int // == nil
	for _, i := range s {
		if fn(i) {
			p = append(p, i)
		}
	}
	return p
}

func even(n int) bool {
	if n%2 == 0 {
		return true
	}
	return false
}
/* [0 2 4 6 8] */

7.6 字符串、数组和切片的应用

7.6.1 从字符串生成字节切片

1.假设 s 是一个字符串(本质上是一个字节数组),那么就可以直接通过 c := []byte(s) 来获取一个字节的切片 c 。另外,您还可以通过 copy() 函数来达到相同的目的:copy(dst []byte, src string)
2. 您还可以将一个字符串追加到某一个字节切片的尾部

var b []byte
var s string
b = append(b, s...)

7.6.4 修改字符串中的某个字符

Go 语言中的字符串是不可变的,也就是说 str[index] 这样的表达式是不可以被放在等号左侧的.
例如,将字符串 "hello" 转换为 "cello"

s := "hello"
c := []byte(s) //字符串转字节数组
c[0] = 'c' //修改数组中的元素值
s2 := string(c) // s2 == "cello" //字节数组转字符串

7.6.5 字节数组对比函数

下面的 Compare() 函数会返回两个字节数组字典顺序的整数对比结果,即 0 if a == b, -1 if a < b, 1 if a > b

func Compare(a, b[]byte) int {
    for i:=0; i < len(a) && i < len(b); i++ {
        switch {
        case a[i] > b[i]:
            return 1
        case a[i] < b[i]:
            return -1
        }
    }
    // 数组的长度可能不同
    switch {
    case len(a) < len(b):
        return -1
    case len(a) > len(b):
        return 1
    }
    return 0 // 数组相等
}

7.6.6 搜索及排序切片和数组

标准库提供了 sort 包来实现常见的搜索排序操作。您可以使用 sort 包中的函数 func Ints(a []int) 来实现对 int 类型的切片排序。例如 sort.Ints(arri),其中变量 arri是需要被升序排序的数组或切片。为了检查某个数组是否已经被排序,可以通过函数 IntsAreSorted(a []int) bool 来检查,如果返回 true 则表示已经被排序

类似的,可以使用函数 func Float64s(a []float64) 来排序 float64 的元素,或使用函数 func Strings(a []string) 排序字符串元素。

想要在数组或切片搜索一个元素,该数组或切片必须先被排序(因为标准库的搜索算法使用的是二分法)。然后,您就可以使用函数 func SearchInts(a []int, n int) int 进行搜索,并返回对应结果的索引值

7.6.7 append() 函数常见操作

1.将切片 b 的元素追加到切片 a 之后:a = append(a, b...)

2.复制切片 a 的元素到新的切片 b 上:

b = make([]T, len(a))
copy(b, a)

3.删除位于索引 i 的元素:a = append(a[:i], a[i+1:]...)

4.切除切片 a 中从索引 ij 位置的元素:a = append(a[:i], a[j:]...)

5.为切片 a 扩展 j 个元素长度:a = append(a, make([]T, j)...)

6.在索引 i 的位置插入元素 x:a = append(a[:i], append([]T{x}, a[i:]...)...)

7.在索引 i 的位置插入长度为 j 的新切片:a = append(a[:i], append(make([]T, j), a[i:]...)...)

8.在索引 i 的位置插入切片 b 的所有元素:a = append(a[:i], append(b, a[i:]...)...)

9.取出位于切片 a 最末尾的元素 x:x, a = a[len(a)-1], a[:len(a)-1]

10.将元素 x 追加到切片 a:a = append(a, x)

因此,您可以使用切片和 append() 操作来表示任意可变长度的序列。
如果您需要更加完整的方案,可以学习一下 Eleanor McHugh 编写的几个包:sliceschainlists

练习 7.12

编写一个函数,要求其接受两个参数,原始字符串 str 和分割索引 i,然后返回两个分割后的字符串

package main

import "fmt"

func main() {
	str := "Google"
	for i := 0; i <= len(str); i++ {
		a, b := Split(str, i)
		fmt.Printf("The string %s split at position %d is: %s / %s\n", str, i, a, b)
	}

}

func Split(s string, pos int) (string, string) {
	return s[0:pos], s[pos:]
}

练习7.13

假设有字符串 str,那么 str[len(str)/2:] + str[:len(str)/2] 的结果是什么?

package main

import "fmt"

func main() {
	str := "Google"
	str2 := Split2(str)
	fmt.Printf("The string %s transformed is: %s\n", str, str2)
}

func Split2(s string) string {
	mid := len(s) / 2
	return s[mid:] + s[:mid]
}

// Output: The string Google transformed is: gleGoo

练习 7.14

编写一个程序,要求能够反转字符串,即将 “Google” 转换成 “elgooG”(提示:使用 []byte 类型的切片)。

package main

import "fmt"

func reverse(s string) string {
	runes := []rune(s)
	n, h := len(runes), len(runes)/2
	for i := 0; i < h; i++ {
		runes[i], runes[n-1-i] = runes[n-1-i], runes[i]
	}
	return string(runes)
}

func main() {
	// reverse a string:
	str := "Google"
	sl := []byte(str)
	var rev [100]byte
	j := 0
	for i := len(sl) - 1; i >= 0; i-- {
		rev[j] = sl[i]
		j++
	}
	str_rev := string(rev[:])
	fmt.Printf("The reversed string is -%s-\n", str_rev)
	// variant: "in place" using swapping
	str2 := "Google"
	sl2 := []byte(str2)
	for i, j := 0, len(sl2)-1; i < j; i, j = i+1, j-1 {
		sl2[i], sl2[j] = sl2[j], sl2[i]
	}
	fmt.Printf("The reversed string is -%s-\n", string(sl2))
	// variant: using [] int for runes (necessary for Unicode-strings!):
	s := "My Test String!"
	fmt.Println(s, " --> ", reverse(s))
}

/* Output:
The reversed string is -elgooG-
The reversed string is -elgooG-
My Test String!  -->  !gnirtS tseT yM
*/

练习7.15

编写一个程序,要求能够遍历一个字符数组,并将当前字符和前一个字符不相同的字符拷贝至另一个数组

// Q29_uniq.go
package main

import (
	"fmt"
)

var arr []byte = []byte{'a', 'b', 'a', 'a', 'a', 'c', 'd', 'e', 'f', 'g'}

func main() {
	arru := make([]byte, len(arr)) // this will contain the unique items
	ixu := 0                       // index in arru
	tmp := byte(0)
	for _, val := range arr {
		if val != tmp {
			arru[ixu] = val
			fmt.Printf("%c ", arru[ixu])
			ixu++
		}
		tmp = val
	}
	// fmt.Println(arru)
}

练习 7.16

编写一个程序,使用冒泡排序的方法排序一个包含整数的切片

// Q14_Bubblesort.go
package main

import (
	"fmt"
)

func main() {
	sla := []int{2, 6, 4, -10, 8, 89, 12, 68, -45, 37}
	fmt.Println("before sort: ", sla)
	// sla is passed via call by value, but since sla is a reference type
	// the underlying slice is array is changed (sorted)
	bubbleSort(sla)
	fmt.Println("after sort:  ", sla)
}

func bubbleSort(sl []int) { //每次将最大的元素移到最后
	for pass := 1; pass < len(sl); pass++ { //循环多少次  总共3个数 就要循环2次
		for i := 0; i < len(sl)-pass; i++ { //每一次 要便利多少个元素
			if sl[i] > sl[i+1] {
				sl[i], sl[i+1] = sl[i+1], sl[i]
			}
		}
	}
}

练习7.17

在函数式编程语言中,一个 map-function 是指能够接受一个函数原型和一个列表,并使用列表中的值依次执行函数原型,公式为:map ( F(), (e1,e2, . . . ,en) ) = ( F(e1), F(e2), ... F(en) )。

编写一个函数 mapFunc 要求接受以下 2 个参数:

一个将整数乘以 10 的函数
一个整数列表
最后返回保存运行结果的整数列表

//2.函数功能 将放入的数组中的每个元素乘以10,最终以切片输出
func main() {
	list := []int{1, 2, 3, 4, 5, 6, 7}
	mf := func(num int) int {
		return num * 10
	}
	fmt.Println(mapFunc(mf, list))
}
func mapFunc(mf func(int) int, list []int) []int {
	result := make([]int, len(list))
	for i, value := range list {
		value = mf(value)
		//这里不用append 原因是[0 0 0 0 0 0 0 10 20 30 40 50 60 70] append函数是追加 相当于在创建
		//长度为7的result切片后,又增添了7个元素
		result[i] = value 
	}
	return result
}

八、map

8.1.1 概念

1.未初始化的 map 的值是 nil
2.key 可以是任意可以用 == 或者 != 操作符比较的类型,比如 string、int、float32(64)。所以数组、切片和结构体不能作为 key (译者注:含有数组切片的结构体不能作为 key,只包含内建类型的 struct 是可以作为 key 的),但是指针和接口类型可以。如果要用结构体作为 key 可以提供 Key() 和 Hash() 方法,这样可以通过结构体的域计算出唯一的数字或者字符串的 key。
3. map 也可以用函数作为自己的,这样就可以用来做分支结构(详见第 5 章):key 用来选择要执行的函数
4. mapAssigned 也是 mapLit 的引用,对 mapAssigned修改也会影响mapLit 的值
5. 不要使用 new()永远用 make() 来构造 map
6. 为了说明值可以是任意类型的,这里给出了一个使用 func() int 作为值的 map

package main
import "fmt"

func main() {
	mf := map[int]func() int{
		1: func() int { return 10 },
		2: func() int { return 20 },
		5: func() int { return 50 },
	}
	fmt.Println(mf)
}
map[1:0x10903be0 5:0x10903ba0 2:0x10903bc0] 整型都被映射到函数地址

8.1.3 用切片作为 map 的值

mp1 := make(map[int][]int)
mp2 := make(map[int]*[]int)

8.2 测试键值对是否存在及删除元素

  1. 为了解决key1 不存在还是它对应的 value 就是空值,我们可以这么用:
    val1, isPresent = map1[key1]
package main
import "fmt"

func main() {
	var value int
	var isPresent bool

	map1 := make(map[string]int)
	map1["New Delhi"] = 55
	map1["Beijing"] = 20
	map1["Washington"] = 25
	value, isPresent = map1["Beijing"]
	if isPresent {
		fmt.Printf("The value of \"Beijing\" in map1 is: %d\n", value)
	} else {
		fmt.Printf("map1 does not contain Beijing")
	}

	value, isPresent = map1["Paris"]
	fmt.Printf("Is \"Paris\" in map1 ?: %t\n", isPresent)
	fmt.Printf("Value is: %d\n", value)

	// delete an item:
	delete(map1, "Washington")
	value, isPresent = map1["Washington"]
	if isPresent {
		fmt.Printf("The value of \"Washington\" in map1 is: %d\n", value)
	} else {
		fmt.Println("map1 does not contain Washington")
	}
}
输出结果:
The value of "Beijing" in map1 is: 20
Is "Paris" in map1 ?: false
Value is: 0  (空值)
map1 does not contain Washington

8.4 map 类型的切片

1.假设我们想获取一个 map 类型的切片,我们必须使用两次 make() 函数第一次分配切片第二次分配切片中每个 map 元素

// 3.创造map类型切片
func main() {
	items := make([]map[int]int, 5)
	for i := range items {
		items[i] = make(map[int]int, 1)
		items[i][1] = i
		items[i][2] = i * 10
	}
	fmt.Println(items)
}
输出结果:[map[1:0 2:0] map[1:1 2:10] map[1:2 2:20] map[1:3 2:30] map[1:4 2:40]]

8.5 map 的排序

如果你想为 map 排序,需要将 key(或者 value)拷贝到一个切片,再对切片排序(使用 sort 包,详见第 7.6.6 节),然后可以使用切片的 for-range 方法打印出所有的 key 和 value

// the telephone alphabet:
package main
import (
	"fmt"
	"sort"
)

var (
	barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23,
							"delta": 87, "echo": 56, "foxtrot": 12,
							"golf": 34, "hotel": 16, "indio": 87,
							"juliet": 65, "kili": 43, "lima": 98}
)

func main() {
	fmt.Println("unsorted:")
	for k, v := range barVal {
		fmt.Printf("Key: %v, Value: %v / ", k, v)
	}
	keys := make([]string, len(barVal))
	i := 0
	for k, _ := range barVal {
		keys[i] = k
		i++
	}
	sort.Strings(keys)
	fmt.Println()
	fmt.Println("sorted:")
	for _, k := range keys {
		fmt.Printf("Key: %v, Value: %v / ", k, barVal[k])
	}
}
输出结果:
unsorted:
Key: bravo, Value: 56 / Key: echo, Value: 56 / Key: indio, Value: 87 / Key: juliet, Value: 65 / Key: alpha, Value: 34 / Key: charlie, Value: 23 / Key: delta, Value: 87 / Key: foxtrot, Value: 12 / Key: golf, Value: 34 / Key: hotel, Value: 16 / Key: kili, Value: 43 / Key: lima, Value: 98 /
sorted:
Key: alpha, Value: 34 / Key: bravo, Value: 56 / Key: charlie, Value: 23 / Key: delta, Value: 87 / Key: echo, Value: 56 / Key: foxtrot, Value: 12 / Key: golf, Value: 34 / Key: hotel, Value: 16 / Key: indio, Value: 87 / Key: juliet, Value: 65 / Key: kili, Value: 43 / Key: lima, Value: 98 /
但是如果你想要一个排序的列表,那么最好使用结构体切片,这样会更有效
type name struct {
	key string
	value int
}

8.6 将 map 的键值对调

这里对调是指调换 keyvalue

package main
import (
	"fmt"
)

var (
	barVal = map[string]int{"alpha": 34, "bravo": 56, "charlie": 23,
							"delta": 87, "echo": 56, "foxtrot": 12,
							"golf": 34, "hotel": 16, "indio": 87,
							"juliet": 65, "kili": 43, "lima": 98}
)

func main() {
	invMap := make(map[int]string, len(barVal))
	for k, v := range barVal {
		invMap[v] = k
	}
	fmt.Println("inverted:")
	for k, v := range invMap {
		fmt.Printf("Key: %v, Value: %v / ", k, v)
	}
}

如果value值不确定,一种解决方法就是仔细检查唯一性并且使用多值 map,比如使用 map[int][]string 类型

9 包

通过使用 unsafe 包中的方法来测试你电脑上一个整型变量占用多少个字节

func main() {
	var i int = 10
	size := unsafe.Sizeof(i)
	fmt.Println("The size of an int is: ", size)
}

9.3 锁和 sync 包

sync.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()
}

sync 包中还有一个 RWMutex 锁:它能通过 RLock() 来允许同一时间多个线程对变量进行读操作,但是只能一个线程进行写操作。如果使用 Lock() 将和普通的 Mutex 作用相同。包中还有一个方便的 Once 类型变量的方法 once.Do(call),这个方法确保被调用函数只能被调用一次

9.4 精密计算和 big 包

大的整型数字是通过 big.NewInt(n) 来构造的,其中 n 为 int64 类型整数。而大有理数是通过 big.NewRat(n, d) 方法构造。n(分子)和 d(分母)都是 int64 型整数。因为 Go 语言不支持运算符重载,所以所有大数字类型都有像是 Add() 和 Mul() 这样的方法。它们作用于作为 receiver 的整数和有理数,大多数情况下它们修改 receiver 并以 receiver 作为返回结果

// 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
*/

9.6 为自定义包使用 godoc

  1. 生成你项目的所有函数索引通过网页形式
  2. godoc -http=:8080
  3. url http://localhost:8080

10.0 结构 (struct) 与方法 (method)

10.2 使用工厂方法创建结构体实例

10.2.1 结构体工厂

// file结构体
type File struct {
    fd      int     // 文件描述符
    name    string  // 文件名
}
//下面是这个结构体类型对应的工厂方法,它返回一个指向结构体实例的指针:
func NewFile(fd int, name string) *File {
    if fd < 0 {
        return nil
    }
    return &File{fd, name}
}

调用方式f := NewFile(10, "./test.txt")
2. 如何强制使用工厂方法

type matrix struct {
    ...
}

func NewMatrix(params) *matrix {
    m := new(matrix) // 初始化 m
    return m
}

这样子 使得new(matrix)变得私有

package main
import "matrix"
...
wrong := new(matrix.matrix)     // 编译失败(matrix 是私有的)
right := matrix.NewMatrix(...)  // 实例化 matrix 的唯一方式
  1. 结构体用new map用make

10.4 带标签的结构体

在一个变量上调用 reflect.TypeOf() 可以获取变量的正确类型,如果变量是一个结构体类型,就可以通过 Field 来索引结构体的字段,然后就可以使用 Tag 属性

package main

import (
	"fmt"
	"reflect"
)

type TagType struct { // tags
	field1 bool   "An important answer"
	field2 string "The name of the thing"
	field3 int    "How much there are"
}

func main() {
	tt := TagType{true, "Barak Obama", 1}
	for i := 0; i < 3; i++ {
		refTag(tt, i)
	}
}

func refTag(tt TagType, ix int) {
	ttType := reflect.TypeOf(tt)
	ixField := ttType.Field(ix)
	fmt.Printf("%v\n", ixField.Tag)
}
输出结果:
An important answer
The name of the thing
How much there are

10.5 匿名字段和内嵌结构体

10.5.1 定义

结构体可以包含一个或多个 匿名(或内嵌)字段,即这些字段没有显式的名字只有字段的类型是必须的,此时类型就是字段的名字。匿名字段本身可以是一个结构体类型,即 结构体可以包含内嵌结构体。

package main

import "fmt"

type innerS struct {
	in1 int
	in2 int
}

type outerS struct {
	b    int
	c    float32
	int  // anonymous field
	innerS //anonymous field
}

func main() {
	outer := new(outerS)
	outer.b = 6
	outer.c = 7.5
	outer.int = 60
	outer.in1 = 5
	outer.in2 = 10

	fmt.Printf("outer.b is: %d\n", outer.b)
	fmt.Printf("outer.c is: %f\n", outer.c)
	fmt.Printf("outer.int is: %d\n", outer.int)
	fmt.Printf("outer.in1 is: %d\n", outer.in1)
	fmt.Printf("outer.in2 is: %d\n", outer.in2)

	// 使用结构体字面量
	outer2 := outerS{6, 7.5, 60, innerS{5, 10}}
	fmt.Println("outer2 is:", outer2)
}

10.5.3 命名冲突

type A struct {a int}
type B struct {a, b int}

type C struct {A; B}
var c C
解决办法使用 c.A.a 或 c.B.a 

type D struct {B; b float32}
var d D

使用 d.b 是没问题的:它是 float32,而不是 B 的 b。如果想要内层的 b 可以通过 d.B.b 得到

10.6 方法

1.Go 方法是作用在接收者 (receiver) 上的一个函数,接收者是某种类型的变量。因此方法是一种特殊类型的函数
2.一个类型加上它的方法等价于面向对象中的一个。一个重要的区别是:在 Go 中,类型的代码和绑定在它上面的方法的代码可以不放置在一起,它们可以存在在不同的源文件,唯一的要求是:它们必须是同一个包的
3. 如果方法不需要使用 recv 的值,可以用 _ 替换它,比如:

func (_ receiver_type) methodName(parameter_list) (return_value_list) { ... }
  1. 下面是非结构体类型上方法的例子:
package main

import "fmt"

type IntVector []int

func (v IntVector) Sum() (s int) {
	for _, x := range v {
		s += x
	}
	return
}

func main() {
	fmt.Println(IntVector{1, 2, 3}.Sum()) // 输出是6
}
  1. 不能为简单类型定义方法,解决方法可以定义一个别名
1. 会报错 //cannot define new methods on non-local type int
func (a int) Add (b int){   //方法
  fmt.Println(a+b)
}
2. 解决方法
type myInt int

func (a myInt) Add (b myInt){   //方法
  fmt.Println(a+b)
}
func main() {
        var aa,bb myInt = 3,4
        aa.Add(bb)
  }

10.6.2 函数和方法的区别

1.receiver_type 叫做 (接收者)基本类型,这个类型必须在和方法同样的包中被声明

10.6.5 内嵌类型的方法和继承

  1. 它展示了内嵌结构体上的方法可以直接在外层类型的实例上调用
package main

import (
	"fmt"
	"math"
)

// 8. 内嵌类型的方法继承
type Point struct {
	x, y float64
}

func (r *Point) Abs() float64 {
	return math.Sqrt(r.x*r.x + r.y*r.y)
}

type NamePoint struct {
	Point
	name string
}

func (r *NamePoint) Abs() float64 {
	return r.y
}
func main() {
	namePoint := new(NamePoint)
	namePoint.x = 3
	namePoint.y = 4
	fmt.Println(namePoint.Abs()) //覆写方法Abs() 输出 4 == r.y
	fmt.Println(namePoint.Point.Abs()) //输出 5 == math.Sqrt(r.x*r.x + r.y*r.y)
}
输出结果
4
5

练习10.8 继承

type Engine interface {
	Start()
	Stop()
}
type CC struct {
}

func (receiver *CC) Start() {
}
func (receiver *CC) Stop() {
}

type Car struct {
	wheelCount int
	Engine
}

// define a behavior for Car
func (car Car) numberOfWheels() int {
	return car.wheelCount
}

type Mercedes struct {
	Car //anonymous field Car
}

// a behavior only available for the Mercedes
func (m *Mercedes) sayHiToMerkel() {
	fmt.Println("Hi Angela!")
}

func (c *Car) Start() {
	fmt.Println("Car is started")
}

func (c *Car) Stop() {
	fmt.Println("Car is stopped")
}

func (c *Car) GoToWorkIn() {
	// get in car
	c.Start()
	// drive to work
	c.Stop()
	// get out of car
}

func main() {
	c := CC{}
	//m := Mercedes{Car{4, nil}}
	//能用 c代替nil的原因是 CC{}实例也实现了start and stop 方法,因此可以替换nil (在engine位置)
	m := Mercedes{Car{4, &c}}
	fmt.Println("A Mercedes has this many wheels: ", m.numberOfWheels())
	m.GoToWorkIn()
	m.sayHiToMerkel()
}

#10.6.6 如何在类型中嵌入功能

主要有两种方法来实现在类型中嵌入功能:

A:聚合(或组合):包含一个所需功能类型的具名字段

B:内嵌内嵌(匿名地)所需功能类型,像前一节 10.6.5 所演示的那样。

A:

package main

import (
	"fmt"
)

type Log struct {
	msg string
}

type Customer struct {
	Name string
	log  *Log
}

func main() {
	c := new(Customer)
	c.Name = "Barak Obama"
	c.log = new(Log)
	c.log.msg = "1 - Yes we can!"
	// shorter
	c = &Customer{"Barak Obama", &Log{"1 - Yes we can!"}}
	// fmt.Println(c) &{Barak Obama 1 - Yes we can!}
	c.Log().Add("2 - After me the world will be a better place!")
	//fmt.Println(c.log)
	fmt.Println(c.Log())

}

func (l *Log) Add(s string) {
	l.msg += "\n" + s
}

func (l *Log) String() string {
	return l.msg
}

func (c *Customer) Log() *Log {
	return c.log
}

B:

package main

import (
	"fmt"
)

type Log struct {
	msg string
}

type Customer struct {
	Name string
	Log
}

func main() {
	c := &Customer{"Barak Obama", Log{"1 - Yes we can!"}}
	c.Add("2 - After me the world will be a better place!")
	fmt.Println(c)

}

func (l *Log) Add(s string) {
	l.msg += "\n" + s
}

func (l *Log) String() string {
	return l.msg
}

func (c *Customer) String() string {
	return c.Name + "\nLog:" + fmt.Sprintln(c.Log.String())
}

10.6.7 多重继承

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

作为一个例子,假设有一个类型 CameraPhone,通过它可以 Call(),也可以 TakeAPicture(),但是第一个方法属于类型 Phone,第二个方法属于类型 Camera

package main

import (
	"fmt"
)

type Camera struct{}

func (c *Camera) TakeAPicture() string {
	return "Click"
}

type Phone struct{}

func (p *Phone) Call() string {
	return "Ring Ring"
}

type CameraPhone struct {
	Camera
	Phone
}

func main() {
	cp := new(CameraPhone)
	fmt.Println("Our new CameraPhone exhibits multiple behaviors...")
	fmt.Println("It exhibits behavior of a Camera: ", cp.TakeAPicture())
	fmt.Println("It works like a Phone too: ", cp.Call())
}
  1. 比如:我们想定义自己的 Integer 类型,并添加一些类似转换成字符串的方法,在 Go 中可以如下定义:
type Integer int
func (i *Integer) String() string {
    return strconv.Itoa(int(*i))
}

备注 goop包(需要更多面向对象的能力)

10.7 类型的 String() 方法和格式化描述符

当定义了一个有很多方法的类型时,十之八九你会使用 String() 方法来定制类型的字符串形式的输出,换句话说:一种可阅读性和打印性的输出。如果类型定义了 String() 方法,它会被用在 fmt.Printf() 中生成默认的输出:等同于使用格式化描述符 %v 产生的输出。还有 fmt.Print()fmt.Println() 也会自动使用 String() 方法

package main

import (
	"fmt"
	"strconv"
)

type TwoInts struct {
	a int
	b int
}
type Person struct {
	firstName string
	lastName  string
}

func main() {
	two1 := new(TwoInts)
	two1.a = 12
	two1.b = 10
	fmt.Printf("two1 is: %v\n", two1)
	fmt.Println("two1 is:", two1)
	fmt.Printf("two1 is: %T\n", two1)
	fmt.Printf("two1 is: %#v\n", two1)
	person := new(Person)
	person = &Person{"LOVE", "STORY"}
	fmt.Println(person)
}
func (r *Person) String() string {
	return fmt.Sprintf("(陕西省西安市的%s的%s)", r.firstName, r.lastName)
}
func (tn *TwoInts) String() string {
	return "(" + strconv.Itoa(tn.a) + "/" + strconv.Itoa(tn.b) + ")"
}
输出结果:
two1 is: (12/10)
two1 is: (12/10)
two1 is: *main.TwoInts
two1 is: &main.TwoInts{a:12, b:10}
(陕西省西安市的LOVE的STORY)
格式化描述符 %T 会给出类型的完全规格,%#v 会给出实例的完整输出

备注 自定义 string

不要在 String() 方法里面调用涉及 String() 方法的方法,它会导致意料之外的错误,比如下面的例子,它导致了一个无限递归调用TT.String() 调用 fmt.Sprintf而 fmt.Sprintf 又会反过来调用 TT.String()),很快就会导致内存溢出:

type TT float64

func (t TT) String() string {
    return fmt.Sprintf("%v", t)
}
t.String()

练习10.15

为 int 定义别名类型 TZ,定义一些常量表示时区,比如 UTC,定义一个 map,它将时区的缩写映射为它的全称,比如:UTC -> "Universal Greenwich time"。为类型 TZ 定义 String() 方法,它输出时区的全称

// Output:
// Eastern Standard time
// Universal Greenwich time
// Central Standard time
package main

import "fmt"

type TZ int

const (
	HOUR TZ = 60 * 60
	UTC  TZ = 0 * HOUR
	EST  TZ = -5 * HOUR
	CST  TZ = -6 * HOUR
)

var timeZones = map[TZ]string{
	UTC: "Universal Greenwich time",
	EST: "Eastern Standard time",
	CST: "Central Standard time"}

func (tz TZ) String() string { // Method on TZ (not ptr)
	if zone, ok := timeZones[tz]; ok {
		return zone
	}
	return ""
}

func main() {
	fmt.Println(EST) // Print* knows about method String() of type TZ
	fmt.Println(0 * HOUR)
	fmt.Println(-6 * HOUR)
}

/* Output:
Eastern Standard time
Universal Greenwich time
Central Standard time
*/

10.8 垃圾回收和 SetFinalizer

1.比如当内存资源不足时调用 runtime.GC(),它会在此函数执行的点上立即释放一大片内存,此时程序可能会有短时的性能下降(因为 GC 进程在执行)

11 接口与反射

11.1 接口是什么

package main

import "fmt"

type stockPosition struct {
	ticker     string
	sharePrice float32
	count      float32
}

/* method to determine the value of a stock position */
func (s stockPosition) getValue() float32 {
	return s.sharePrice * s.count
}

type car struct {
	make  string
	model string
	price float32
}

/* method to determine the value of a car */
func (c car) getValue() float32 {
	return c.price
}

/* contract that defines different things that have value */
type valuable interface {
	getValue() float32
}

func showValue(asset valuable) {
	fmt.Printf("Value of the asset is %f\n", asset.getValue())
}

func main() {
	var o valuable = stockPosition{"GOOG", 577.20, 4}
	showValue(o)
	o = car{"BMW", "M3", 66500}
	showValue(o)
}

练习11.1 simple_interface

// simple_interface.go
package main

import (
	"fmt"
)

type Simpler interface {
	Get() int
	Put(int)
}

type Simple struct {
	i int
}

func (p *Simple) Get() int {
	return p.i
}

func (p *Simple) Put(u int) {
	p.i = u
}

func fI(it Simpler) int {
	it.Put(5)
	return it.Get()
}

func main() {
	var s Simple
	fmt.Println(fI(&s)) // &s is required because Get() is defined with a receiver type pointer
}

// Output: 5

11.2 接口嵌套

type ReadWrite interface {
    Read(b Buffer) bool
    Write(b Buffer) bool
}

type Lock interface {
    Lock()
    Unlock()
}

type File interface {
    ReadWrite
    Lock
    Close()
}

11.3 类型断言:如何检测和转换接口变量的类型

如果转换合法,v 是 varI 转换到类型 T 的值ok 会是 true;否则 v 是类型 T 的零值,ok 是 false,也没有运行时错误发生。

if v, ok := varI.(T); ok {  // checked type assertion
    Process(v)
    return
}
// varI is not of type T
package main

import (
	"fmt"
	"math"
)

type Square struct {
	side float32
}

type Circle struct {
	radius float32
}

type Shaper interface {
	Area() float32
}

func main() {
	var areaIntf Shaper
	sq1 := new(Square)
	sq1.side = 5

	areaIntf = sq1
	// Is Square the type of areaIntf?
	if t, ok := areaIntf.(*Square); ok {
		fmt.Printf("The type of areaIntf is: %T\n", t)
	}
	if u, ok := areaIntf.(*Circle); ok {
		fmt.Printf("The type of areaIntf is: %T\n", u)
	} else {
		fmt.Println("areaIntf does not contain a variable of type Circle")
	}
}

func (sq *Square) Area() float32 {
	return sq.side * sq.side
}

func (ci *Circle) Area() float32 {
	return ci.radius * ci.radius * math.Pi
}

11.4 类型判断:type-switch

变量 t 得到了 areaIntf 的值和类型

switch t := areaIntf.(type) {
case *Square:
	fmt.Printf("Type Square %T with value %v\n", t, t)
case *Circle:
	fmt.Printf("Type Circle %T with value %v\n", t, t)
case nil:
	fmt.Printf("nil value: nothing to check?\n")
default:
	fmt.Printf("Unexpected type %T\n", t)
}

2.下面的代码片段展示了一个类型分类函数,它有一个可变长度参数,可以是任意类型的数组,它会根据数组元素的实际类型执行不同的动作

func classifier(items ...interface{}) {
	for i, x := range items {
		switch x.(type) {
		case bool:
			fmt.Printf("Param #%d is a bool\n", i)
		case float64:
			fmt.Printf("Param #%d is a float64\n", i)
		case int, int64:
			fmt.Printf("Param #%d is a int\n", i)
		case nil:
			fmt.Printf("Param #%d is a nil\n", i)
		case string:
			fmt.Printf("Param #%d is a string\n", i)
		default:
			fmt.Printf("Param #%d is unknown\n", i)
		}
	}
}

练习 11.4 simple_interface2.go:

package main

import "fmt"

type Simpler interface {
	Get() int
	Set(int)
}

type Simple struct {
	i int
}

func (p *Simple) Get() int {
	return p.i
}

func (p *Simple) Set(u int) {
	p.i = u
}

type RSimple struct {
	i int
	j int
}

func (r *RSimple) Get() int {
	return r.j
}
func (r *RSimple) Set(n int) {
	r.j = n
}
func fI(it Simpler) int {
	switch it.(type) {
	case *RSimple:
		it.Set(50)
		return it.Get()
	case *Simple:
		it.Set(100)
		return it.Get()
	default:
		return 99
	}
	return 0
}
func main() {
	var s Simple
	fmt.Println(fI(&s))
	var R RSimple
	fmt.Println(fI(&R))
	var a Simpler
	fmt.Println(fI(a))
}
输出代码:
100
50
99

11.5 测试一个值是否实现了某个接口

假定 v 是一个值,然后我们想测试它是否实现了 Stringer 接口,可以这样做

type Stringer interface {
    String() string
}

if sv, ok := v.(Stringer); ok {
    fmt.Printf("v implements String(): %s\n", sv.String()) // note: sv, not v
}

实例:有Simpler接口 此接口有 Get 和 Set方法 有一个结构体 Simple实现了此接口,自定义类型Mystring没实现此接口,现在对进行测试,看他是否实现了此接口

1.类型断言只适用于接口,因此我们拿空接口来接收值
2.注意方法的接收者是否有 * 号,如果有 * 号 那么要将该值的指针传入
type Simpler interface {
	Get() int
	Set(int)
}
type Simple struct {
	i int
}

func (p *Simple) Get() int {
	return p.i
}
func (p *Simple) Set(u int) {
	p.i = u
}
type Mystring string
func main() {
	var r Mystring = "123"
	var s Simple = Simple{i: 1}
	var i interface{}
	i = &r
	if sv, ok := i.(Simpler); ok {
		fmt.Println(ok, "-", sv.Get()) //note sv not a
	}
	fmt.Println("上面没有 下面有")
	var j interface{}
	j = &s //指针
	if sv, ok := j.(Simpler); ok { //括号里为接口名称
		fmt.Println(ok, "-", sv.Get()) //note sv not a
	}
}
输出结果:
上面没有 下面有
true - 1

11.6 使用方法集与接口

package main

import (
	"fmt"
)

type List []int

func (l List) Len() int {
	return len(l)
}

func (l *List) Append(val int) {
	*l = append(*l, val)
}

type Appender interface {
	Append(int)
}

func CountInto(a Appender, start, end int) {
	for i := start; i <= end; i++ {
		a.Append(i)
	}
}

type Lener interface {
	Len() int
}

func LongEnough(l Lener) bool {
	return l.Len()*10 > 42
}

func main() {
	// A bare value
	var lst List
	// compiler error:
	// cannot use lst (type List) as type Appender in argument to CountInto:
	//       List does not implement Appender (Append method has pointer receiver)
	// CountInto(lst, 1, 10)
	if LongEnough(lst) { // VALID: Identical receiver type
		fmt.Printf("- lst is long enough\n")
	}

	// A pointer value
	plst := new(List)
	CountInto(plst, 1, 10) // VALID: Identical receiver type
	if LongEnough(plst) {
		// VALID: a *List can be dereferenced for the receiver
		fmt.Printf("- plst is long enough\n")
	}
}

lst 上调用 CountInto 时会导致一个编译器错误,因为 CountInto 需要一个 Appender,而它的方法 Append 只定义在指针上。 在 lst 上调用 LongEnough 是可以的,因为 Len 定义在值上。

plst 上调用 CountInto 是可以的,因为 CountInto 需要一个 Appender,并且它的方法 Append 定义在指针上。 在 plst 上调用 LongEnough 也是可以的,因为指针会被自动解引用

Go 语言规范定义了接口方法集的调用规则

1.类型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集
2.类型 T 的可调用方法集包含接受者为 T 的所有方法
3.类型 T 的可调用方法集包含接受者为 *T 的方法

11.7 第一个例子:使用 Sorter 接口排序

一个很好的例子是来自标准库的 sort 包,要对一组数字或字符串排序,只需要实现三个方法:反映元素个数的 Len() 方法、比较第 i 和 j 个元素的 Less(i, j) 方法以及交换第 i 和 j 个元素的 Swap(i, j) 方法。

type IntArray []int

func (p IntArray) Len() int           { return len(p) }
func (p IntArray) Less(i, j int) bool { return p[i] < p[j] }
func (p IntArray) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

type StringArray []string

func (p StringArray) Len() int           { return len(p) }
func (p StringArray) Less(i, j int) bool { return p[i] < p[j] }
func (p StringArray) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

type day struct {
	num       int
	shortName string
	longName  string
}
type dayArray struct {
	data []*day
}

func (p *dayArray) Len() int           { return len(p.data) }
func (p *dayArray) Less(i, j int) bool { return p.data[i].num < p.data[j].num }
func (p *dayArray) Swap(i, j int)      { p.data[i], p.data[j] = p.data[j], p.data[i] }
func main() {
	data := []int{74, 59, 238, -784, 9845, 959, 905, 0, 0, 42, 7586, -5467984, 7586}
	sort.Sort(IntArray(data))
	if sort.IsSorted(IntArray(data)) {
		fmt.Println("排序过了 ")
	}
	fmt.Println(data)
	dataString := []string{"monday", "friday", "tuesday", "wednesday", "sunday", "thursday", "", "saturday"}
	sort.Sort(StringArray(dataString))
	if sort.IsSorted(StringArray(dataString)) {
		fmt.Println("字符串排序过了")
	}
	fmt.Println(dataString, len(dataString))
	Sunday := day{0, "SUN", "Sunday"}
	Monday := day{1, "MON", "Monday"}
	Tuesday := day{2, "TUE", "Tuesday"}
	Wednesday := day{3, "WED", "Wednesday"}
	Thursday := day{4, "THU", "Thursday"}
	Friday := day{5, "FRI", "Friday"}
	Saturday := day{6, "SAT", "Saturday"}
	datadays := []*day{&Tuesday, &Monday, &Friday, &Thursday, &Wednesday, &Saturday, &Sunday}
	sort.Sort(&dayArray{datadays})
	for _, v := range datadays {
		fmt.Println(*v)
	}
}

11.9 空接口

每个 interface {} 变量在内存中占据两个字长:一个用来存储它包含的类型,另一个用来存储它包含的数据或者指向数据的指针。

  • 空接口联合 type-switch
package main

import "fmt"

type specialString string

var whatIsThis specialString = "hello"

func TypeSwitch() {
	testFunc := func(any interface{}) {
		switch v := any.(type) {
		case bool:
			fmt.Printf("any %v is a bool type", v)
		case int:
			fmt.Printf("any %v is an int type", v)
		case float32:
			fmt.Printf("any %v is a float32 type", v)
		case string:
			fmt.Printf("any %v is a string type", v)
		case specialString:
			fmt.Printf("any %v is a special String!", v)
		default:
			fmt.Println("unknown type!")
		}
	}
	testFunc(whatIsThis)
}

func main() {
	TypeSwitch()
}

11.10反射包

11.10.1 方法和类型的反射

// blog: Laws of Reflection
package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x float64 = 3.4
	//返回类型
	fmt.Println("type:", reflect.TypeOf(x))
	//reflect.ValueOf(x) 返回 <float64 Value>
	v := reflect.ValueOf(x)
	//输出值
	fmt.Println("value:", v)
	// Value 有一个 Type() 方法返回 reflect.Value 的 Type 类型
	fmt.Println("type:", v.Type())
	//另一个是 Type 和 Value 都有 Kind() 方法返回一个常量来表示类型:Uint、Float64、Slice 等等
	//Kind总返回底层类型
	fmt.Println("kind:", v.Kind())
	//reflect.ValueOf(x).Float() 返回这个 float64 类型的实际值
	fmt.Println("value:", v.Float())
	//变量 v 的 Interface() 方法可以得到还原(接口)值,所以可以这样打印 v 的值:fmt.Println(v.Interface())
	fmt.Println(v.Interface())
	fmt.Printf("value is %5.2e\n", v.Interface())
	y := v.Interface().(float64)
	fmt.Println(y)
}
type: float64
value: 3.4
type: float64
kind: float64
value: 3.4
3.4
value is 3.40e+00
3.4

11.10.2 通过反射修改(设置)值

package main

import (
	"fmt"
	"reflect"
)

func main() {
	var x float64 = 3.4
	v := reflect.ValueOf(x)
	// setting a value:
	// v.SetFloat(3.1415) // Error: will panic: reflect.Value.SetFloat using unaddressable value
	//可以使用 CanSet() 方法测试是否可设置 note:是否可设置是value的一个属性
	fmt.Println("settability of v:", v.CanSet())
	//传地址
	v = reflect.ValueOf(&x) // Note: take the address of x.
	fmt.Println("type of v:", v.Type())
	fmt.Println("settability of v:", v.CanSet())
	//这间接地使用指针:v = v.Elem()
	v = v.Elem()
	fmt.Println("The Elem of v is: ", v)
	fmt.Println("settability of v:", v.CanSet())
	v.SetFloat(3.1415) // this works!
	fmt.Println(v.Interface())
	fmt.Println(v)
}
settability of v: false
type of v: *float64
settability of v: false
The Elem of v is:  <float64 Value>
settability of v: true
3.1415
<float64 Value>

11.10.3 反射结构

package main

import (
	"fmt"
	"reflect"
)

type NotknownType struct {
	s1, s2, s3 string
}

func (n NotknownType) String() string {
	return n.s1 + " - " + n.s2 + " - " + n.s3
}

// variable to investigate:
var secret interface{} = NotknownType{"Ada", "Go", "Oberon"}

func main() {
	value := reflect.ValueOf(secret) // <main.NotknownType Value>
	typ := reflect.TypeOf(secret)    // main.NotknownType
	// alternative:
	// typ := value.Type()  // main.NotknownType
	fmt.Println(typ)
	knd := value.Kind() // struct
	fmt.Println(knd)

	// iterate through the fields of the struct:
	//NumField() 方法返回结构内的字段数量
	for i := 0; i < value.NumField(); i++ {
	//通过一个 for 循环用索引取得每个字段的值 Field(i)
		fmt.Printf("Field %d: %v\n", i, value.Field(i))
		// error: panic: reflect.Value.SetString using value obtained using unexported field
		// value.Field(i).SetString("C#")
	}

	// call the first method, which is String():
	//这个函数调用是在 Go 语言中使用反射(reflection)时使用的。它会调用值的方法,
	//其中参数 0 表示该方法没有参数。在这里,nil 表示没有传递任何参数。
	//value.Method(0) 返回一个 Method 类型的值,表示该值对应的类型的第一个方法
	//Call(nil) 是对该方法的调用,nil 参数表示该方法没有参数。
	//Call 方法接收的参数是一个 []reflect.Value 类型的切片
	results := value.Method(0).Call(nil)
	fmt.Println(results) // [Ada - Go - Oberon]
}
package main

import (
	"fmt"
	"reflect"
)

type T struct {
	A int
	B string
}

func main() {
	t := T{23, "skidoo"}
	s := reflect.ValueOf(&t).Elem()
	typeOfT := s.Type()
	for i := 0; i < s.NumField(); i++ {
		f := s.Field(i)
		fmt.Printf("%d: %s %s = %v\n", i,
			typeOfT.Field(i).Name, f.Type(), f.Interface())
	}
	s.Field(0).SetInt(77)
	s.Field(1).SetString("Sunset Strip")
	fmt.Println("t is now", t)
}
0: A int = 23
1: B string = skidoo
t is now {77 Sunset Strip}

11.4 结构体、集合、高阶函数

type Any interface{}
type Car struct {
	Model        string
	Manufacturer string
	BuildYear    int
	// ...
}
type Cars []*Car

// Process all cars with the given function f: 用给定函数f处理所有汽车:
func (cs Cars) Process(f func(car *Car)) {
	//遍历切片Cars,找到每一辆car,用给定的参数函数处理
	for _, c := range cs {
		f(c)
	}
}

// Find all cars matching a given criteria. 找到所有符合给定条件的汽车。
//实现一个查找函数来获取子集合,并在 Process() 中传入一个闭包执行(这样就可以访问局部切片 cars)
func (cs Cars) FindAll(f func(car *Car) bool) Cars {
	cars := make([]*Car, 0)
	cs.Process(func(c *Car) {
		if f(c) { //如果满足传入的函数的条件,那么就将该辆车加入车辆集
			cars = append(cars, c)
		}
	})
	return cars
}

// Process cars and create new data.//处理汽车,并创建新数据
func (cs Cars) Map(f func(car *Car) Any) []Any {
	result := make([]Any, len(cs))
	ix := 0
	cs.Process(func(c *Car) {
		result[ix] = f(c)
		ix++
	})
	return result
}

/*
我们也可以根据参数返回不同的函数。也许我们想根据不同的厂商添加汽车到不同的集合,但是这(这种映射关系)可能会是会改变的。所以我们可以定义一个函数来产生特定的添加函数和 map 集:
*/
func MakeSortedAppender(manufacturers []string) (func(car *Car), map[string]Cars) {
	// Prepare maps of sorted cars.准备分类汽车的地图
	sortedCars := make(map[string]Cars)

	for _, m := range manufacturers {
		sortedCars[m] = make([]*Car, 0)
	}
	sortedCars["Default"] = make([]*Car, 0)

	// Prepare appender function://准备appender函数
	appender := func(c *Car) {
		if _, ok := sortedCars[c.Manufacturer]; ok {
			sortedCars[c.Manufacturer] = append(sortedCars[c.Manufacturer], c)
		} else {
			sortedCars["Default"] = append(sortedCars["Default"], c)
		}
	}
	return appender, sortedCars
}

func main() {
	// make some cars:
	ford := &Car{"Fiesta", "Ford", 2008}
	bmw := &Car{"XL 450", "BMW", 2011}
	merc := &Car{"D600", "Mercedes", 2009}
	bmw2 := &Car{"X 800", "BMW", 2008}
	// query:
	allCars := Cars([]*Car{ford, bmw, merc, bmw2})
	//具体查询
	allNewBMWs := allCars.FindAll(func(car *Car) bool {
		return (car.Manufacturer == "BMW") && (car.BuildYear > 2010)
	})
	fmt.Println("AllCars: ", allCars)
	fmt.Println("New BMWs: ", allNewBMWs)
	//
	manufacturers := []string{"Ford", "Aston Martin", "Land Rover", "BMW", "Jaguar"}
	sortedAppender, sortedCars := MakeSortedAppender(manufacturers)
	allCars.Process(sortedAppender)
	fmt.Println("Map sortedCars: ", sortedCars)
	BMWCount := len(sortedCars["BMW"])
	fmt.Println("We have ", BMWCount, " BMWs")
}
AllCars:  [0xf8400038a0 0xf840003bd0 0xf840003ba0 0xf840003b70]
New BMWs:  [0xf840003bd0]
Map sortedCars:  map[Default:[0xf840003ba0] Jaguar:[] Land Rover:[] BMW:[0xf840003bd0 0xf840003b70] Aston Martin:[] Ford:[0xf8400038a0]]
We have  2  BMWs

12.0 读写数据

  • 除了 fmtos 包,我们还需要用到 bufio 包来处理缓冲的输入和输出。

12.1 读取用户输入

// 从控制台读取输入:
package main
import "fmt"

var (
   firstName, lastName, s string
   i int
   f float32
   input = "56.12 / 5212 / Go"
   format = "%f / %d / %s"
)

func main() {
   fmt.Println("Please enter your full name: ")
   //Scanln() 扫描来自标准输入的文本,将空格分隔的值依次存放到后续的参数内,直到碰到换行
   fmt.Scanln(&firstName, &lastName)
   // fmt.Scanf("%s %s", &firstName, &lastName)
   fmt.Printf("Hi %s %s!\n", firstName, lastName) // Hi Chris Naegels
   //Sscan... 和以 Sscan... 开头的函数则是从字符串读取,除此之外,与 Scanf() 相同。
   fmt.Sscanf(input, format, &f, &i, &s)
   fmt.Println("From the string we read: ", f, i, s)
    // 输出结果: From the string we read: 56.12 5212 Go
}
  • 您也可以使用 bufio 包提供的缓冲读取器 (buffered reader) 来读取数据,正如以下例子所示:
package main
import (
    "fmt"
    "bufio"
    "os"
)

var inputReader *bufio.Reader
var input string
var err error

func main() {
	//inputReader := bufio.NewReader(os.Stdin) 这行代码,将会创建一个读取器,并将其与标准输入绑定。
	/*该函数的实参可以是满足 io.Reader 接口的任意对象(任意包含有适当的 Read() 方法的对象),
	函数返回一个新的带缓冲的 io.Reader 对象,它将从指定读取器(例如 os.Stdin)读取内容。
	*/
    inputReader = bufio.NewReader(os.Stdin)
    fmt.Println("Please enter some input: ")
    //ReadString(delim byte),该方法从输入中读取内容,直到碰到 delim 指定的字符,然后将读取到的内容连同 delim 字符一起放到缓冲区。
    input, err = inputReader.ReadString('\n')
    if err == nil {
        fmt.Printf("The input was: %s\n", input)
    }
}
  • 从键盘读取输入,使用switch
package main
import (
    "fmt"
    "os"
    "bufio"
)

func main() {
    inputReader := bufio.NewReader(os.Stdin)
    fmt.Println("Please enter your name:")
    input, err := inputReader.ReadString('\n')

    if err != nil {
        fmt.Println("There were errors reading, exiting program.")
        return
    }

    fmt.Printf("Your name is %s", input)
    // For Unix: test with delimiter "\n", for Windows: test with "\r\n" 该字符串表示回车和换行
    switch input {
    case "Philip\r\n":  fmt.Println("Welcome Philip!")
    case "Chris\r\n":   fmt.Println("Welcome Chris!")
    case "Ivo\r\n":     fmt.Println("Welcome Ivo!")
    default: fmt.Printf("You are not welcome here! Goodbye!")
    }

    // version 2:   
    switch input {
    case "Philip\r\n":  fallthrough
    case "Ivo\r\n":     fallthrough
    case "Chris\r\n":   fmt.Printf("Welcome %s\n", input)
    default: fmt.Printf("You are not welcome here! Goodbye!\n")
    }

    // version 3:
    switch input {
    case "Philip\r\n", "Ivo\r\n":   fmt.Printf("Welcome %s\n", input)
    default: fmt.Printf("You are not welcome here! Goodbye!\n")
    }
}

12.2 文件读写

12.2.1 读文件

在 Go 语言中,文件使用指向 os.File 类型的指针来表示的,也叫做文件句柄。我们在前面章节使用到过标准输入 os.Stdin 和标准输出 os.Stdout,他们的类型都是 *os.File

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
)

func main() {
	//变量 inputFile 是 *os.File 类型的。该类型是一个结构,表示一个打开文件的描述符(文件句柄)
    inputFile, inputError := os.Open("./input.dat")
    if inputError != nil {
        fmt.Printf("An error occurred on opening the inputfile\n" +
            "Does the file exist?\n" +
            "Have you got access to it?\n")
        return // exit the function on error
    }
    //使用 defer inputFile.Close() 语句确保在程序退出前关闭该文件
    defer inputFile.Close()
	//通过使用 bufio 包提供的读取器(写入器也类似)
    inputReader := bufio.NewReader(inputFile)
    for {
    //接着,我们在一个无限循环中使用 ReadString('\n') 或 ReadBytes('\n') 将文件的内容逐行(行结束符 '\n')读取出来。另外,我们也可以使用 ReadLine() 方法来实现相同的功能
        inputString, readerError := inputReader.ReadString('\n')
        fmt.Printf("The input was: %s", inputString)
        if readerError == io.EOF {//EOF 文件末尾
            return
        }      
    }
}
  • 将整个文件读到一个字符串
package main
import (
    "fmt"
    "io/ioutil"
    "os"
)

func main() {
    inputFile := "./output.txt" //读取当前目录下的文件
    outputFile := "products_copy.txt"//生成至当前目录下
    //ioutil.ReadFile() 方法,该方法第一个返回值的类型是 []byte,里面存放读取到的内容,第二个返回值是错误
    buf, err := ioutil.ReadFile(inputFile)
    if err != nil {
        fmt.Fprintf(os.Stderr, "File Error: %s\n", err)
        // panic(err.Error())
    }
    fmt.Printf("%s\n", string(buf))
    //函数 WriteFile() 可以将 []byte 的值写入文件
    err = ioutil.WriteFile(outputFile, buf, 0644) // oct, not hex
    if err != nil {
        panic(err.Error())
    }
}
  1. 带缓冲的读取
    在很多情况下,文件的内容是不按行划分的,或者干脆就是一个二进制文件。在这种情况下,ReadString() 就无法使用了,我们可以使用 bufio.Reader 的 Read(),它只接收一个参数:
buf := make([]byte, 1024)
...
//n表示读取字节数
//buf 是一个字节数组或字符数组,用于存储从输入流中读取的数据
n, err := inputReader.Read(buf)
if (n == 0) { break}
  1. 按列读取文件中的数据
如果数据是按列排列并用空格分隔的,你可以使用 fmt 包提供的以 FScan... 开头的一系列函数来读取他们
func main() {
//文件内容
//	8227  44645
//	123   456
	file, err := os.Open("./end_txt.txt")
	if err != nil {
		panic(err)
	}
	defer file.Close()

	var col1, col2 []string
	for {
		var v1, v2 string
		_, err := fmt.Fscanln(file, &v1, &v2)
		// scans until newline
		if err != nil {
			break
		}
		col1 = append(col1, v1)
		col2 = append(col2, v2)
	}
	fmt.Println(col1)
	fmt.Println(col2)

}
[8227 123]
[44645 456]

12.2.2 compress 包:读取压缩文件

  • compress 包提供了读取压缩文件的功能,支持的压缩文件格式为:bzip2、flate、gzip、lzw 和 zlib
func main() {
	fName := "./end_txt.gz"
	var r *bufio.Reader
	fi, err := os.Open(fName)
	if err != nil {
		fmt.Fprintf(os.Stderr, "%v, Can't open %s: error: %s\n", os.Args[0], fName,
			err)
		os.Exit(1)
	}
	defer fi.Close()
	fz, err := gzip.NewReader(fi)
	if err != nil {
		//如果报错,表示该文件不为gzip后缀文件,以普通文件方式读取
		fmt.Println("nomarl")
		r = bufio.NewReader(fi)
	} else {
		//没错误,以gzip后缀文件方式读取
		fmt.Println("abnormality")
		r = bufio.NewReader(fz)
	}

	for {
		line, err := r.ReadString('\n')
		if err != nil {
			fmt.Println("Done reading file")
			os.Exit(0)
		}
		fmt.Println(line)
	}
}

12.2.3 写入文件

func main() {
	// var outputWriter *bufio.Writer
	// var outputFile *os.File
	// var outputError os.Error
	// var outputString string
	//OpenFile 函数有三个参数:文件名、一个或多个标志(使用逻辑运算符 | 连接),使用的文件权限
	/*
		os.O_RDONLY:只读
		os.O_WRONLY:只写
		os.O_CREATE:创建:如果指定文件不存在,就创建该文件。
		os.O_TRUNC:截断:如果指定文件已存在,就将该文件的长度截为 0 。
	*/
	//在读文件的时候,文件的权限是被忽略的,所以在使用 OpenFile() 时传入的第三个参数可以用 0 。
	//而在写文件时,不管是 Unix 还是 Windows,都需要使用 0666。
	outputFile, outputError := os.OpenFile("./output.txt", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
	if outputError != nil {
		fmt.Printf("An error occurred with file opening or creation\n")
		return
	}
	defer outputFile.Close()
	//创建一个写入器(缓冲区)对象
	outputWriter := bufio.NewWriter(outputFile)
	outputString := "hello world!\n"

	for i := 0; i < 10; i++ {
		//将字符串写入缓冲区
		outputWriter.WriteString(outputString)
	}
	//调用 Flush() 方法可以强制将缓存中的数据写入磁盘,避免数据丢失的可能性
	//缓冲区的内容紧接着被完全写入文件
	outputWriter.Flush()
	
	//fmt 包里的 F... 开头的 Print() 函数可以直接写入任何 io.Writer,包括文件
	//如果内容简单 可以调用fmt.Fprintf()直接写入文件
	fmt.Fprintf(outputFile, "Some test data.\n")
}

  • 不使用缓冲区写入文件

func main() {
	os.Stdout.WriteString("hello, world\n")
	f, _ := os.OpenFile("./output.txt", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
	defer f.Close()
	//我们不使用缓冲区,直接将内容写入文件
	f.WriteString("hello, world in a file\n")
}

12.3文件拷贝


func main() {
	CopyFile("target.txt", "./output.txt")
	fmt.Println("Copy done!")
}

func CopyFile(dstName, srcName string) (written int64, err error) {
	//返回只读指针
	src, err := os.Open(srcName)
	if err != nil {
		return
	}
	defer src.Close()

	dst, err := os.Create(dstName)
	if err != nil {
		return
	}
	defer dst.Close()

	return io.Copy(dst, src)
}

12.4 从命令行读取参数

12.4.1 os包

  • os 包中有一个 string 类型的切片变量 os.Args,用来处理一些基本的命令行参数,它在程序启动后读取命令行输入的参数
func main() {
	who := "Alice "
	//参数包含命令行参数,从程序名称开始
	if len(os.Args) > 1 {
	//os.Args[0] 放的是程序本身的名字
		who += strings.Join(os.Args[1:], " ")
	}
	fmt.Println("Good Morning", who)
}
Good Morning Alice

如果在命令行中输入

go run main.go  John Bill Marc Luke

结果显示,证明它在自动读取命令行参数,并存入变量os.Args

Good Morning Alice John Bill Marc Luke

12.4.2 flag 包

  • flag 包有一个扩展功能用来解析命令行选项。但是通常被用来替换基本常量,例如,在某些情况下我们希望在命令行给常量一些不一样的值。
package main

import (
	"flag" // command line option parser
	"os"
)

var NewLine = flag.Bool("n", false, "print newline") // echo -n flag, of type *bool

const (
	Space   = " "
	Newline = "\n"
)

func main() {
	//flag.PrintDefaults() 打印 flag 的使用帮助信息
	flag.PrintDefaults()
	//flag.Parse() 扫描参数列表(或者常量列表)并设置 flag, flag.Arg(i) 表示第 i 个参数。
	//Parse() 之后 flag.Arg(i) 全部可用,flag.Arg(0) 就是第一个真实的 flag
	flag.Parse() // Scans the arg list and sets up flags
	/*flag.Narg() 返回参数的数量。解析后 flag 或常量就可用了。 
	flag.Bool() 定义了一个默认值是 false 的 flag:当在命令行出现了第一个参数(这里是 'n'),
	flag 被设置成 true(NewLine 是 *bool 类型)。
	flag 被解引用到 *NewLine,所以当值是 true 时将添加一个 Newline("\n")
	*/
	var s string = ""
	for i := 0; i < flag.NArg(); i++ {
		if i > 0 {
			s += " "
			if *NewLine { // -n is parsed, flag becomes true
				s += Newline
			}
		}
		s += flag.Arg(i)
	}
	os.Stdout.WriteString(s)
}

12.5 用buffer读取文件

  • 我们结合使用了缓冲读取文件和命令行 flag 解析这两项技术
package main

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"os"
)

func cat(r *bufio.Reader) {
	for {
		buf, err := r.ReadBytes('\n')
		fmt.Fprintf(os.Stdout, "%s", buf)
		if err == io.EOF {
			break
		}
	}
	return
}

func main() {
	flag.Parse()
	if flag.NArg() == 0 {
		cat(bufio.NewReader(os.Stdin))
	}
	for i := 0; i < flag.NArg(); i++ {
		f, err := os.Open(flag.Arg(i))
		if err != nil {
			fmt.Fprintf(os.Stderr, "%s:error reading from %s: %s\n", os.Args[0], flag.Arg(i), err.Error())
			continue
		}
		cat(bufio.NewReader(f))
		f.Close()
	}
}
如果为 go run main.go ./target.txt 它就可以读取文件
如果运行代码后不加参数 则如果不加参数,那么你输入什么屏幕就打印什么

12.6 用切片读写文件

func cat(f *os.File) {
	const NBUF = 512
	var buf [NBUF]byte
	for {
		//Read从文件中读取最多nr个字节。
		switch nr, err := f.Read(buf[:]); {
		case nr < 0:
			fmt.Fprintf(os.Stderr, "cat: error reading: %s\n", err.Error())
			os.Exit(1)
		case nr == 0: // EOF
			return
		case nr > 0:
			//将 nr 个字节写入标准输出设备
			if nw, ew := os.Stdout.Write(buf[0:nr]); nw != nr { //如果读写个数不一致,报错
				fmt.Fprintf(os.Stderr, "cat: error writing: %s\n", ew.Error())
			}
		}
	}
}
使用方法,go run main.go 后 有参数就当文件处理,没有参数,执行后有什么输出什么
package main

import (
	"flag"
	"fmt"
	"os"
)

func cat(f *os.File) {
	const NBUF = 512
	var buf [NBUF]byte
	for {
		switch nr, err := f.Read(buf[:]); true {
		case nr < 0:
			fmt.Fprintf(os.Stderr, "cat: error reading: %s\n", err.Error())
			os.Exit(1)
		case nr == 0: // EOF
			return
		case nr > 0:
			if nw, ew := os.Stdout.Write(buf[0:nr]); nw != nr {
				fmt.Fprintf(os.Stderr, "cat: error writing: %s\n", ew.Error())
			}
		}
	}
}

func main() {
	flag.Parse() // Scans the arg list and sets up flags
	if flag.NArg() == 0 {
		cat(os.Stdin)
	}
	for i := 0; i < flag.NArg(); i++ {
		f, err := os.Open(flag.Arg(i))
		if f == nil {
			fmt.Fprintf(os.Stderr, "cat: can't open %s: error %s\n", flag.Arg(i), err)
			os.Exit(1)
		}
		cat(f)
		f.Close()
	}
}

12.9 Json数据格式

数据结构 --> 指定格式 = 序列化 或 编码(传输之前) 结构体->json字符串 叫做 序列化
指定格式 --> 数据结构 = 反序列化 或 解码(传输之后)

序列化是在内存中把数据转换成指定格式(数据 -> 字符串),反之亦然(字符串 -> 数据)
编码也是一样的,只是输出一个数据流(实现了 io.Writer 接口);
解码是从一个数据流(实现了 io.Reader)输出到一个数据结构。
// json.go
package main

import (
	"encoding/json"
	"fmt"
	"log"
	"os"
)

type Address struct {
	Type    string
	City    string
	Country string
}

type VCard struct {
	FirstName string
	LastName  string
	Addresses []*Address
	Remark    string
}

func main() {
	pa := &Address{"private", "Aartselaar", "Belgium"}
	wa := &Address{"work", "Boom", "Belgium"}
	vc := VCard{"Jan", "Kersschot", []*Address{pa, wa}, "none"}
	// fmt.Printf("%v: \n", vc) // {Jan Kersschot [0x126d2b80 0x126d2be0] none}:
	// JSON format:
	//序列化
	js, _ := json.Marshal(vc)
	fmt.Printf("JSON format: %s", js)
	// using an encoder:
	file, _ := os.OpenFile("vcard.json", os.O_CREATE|os.O_WRONLY, 0666)
	defer file.Close()
	//NewEncoder返回一个写入 file 的新编码器
	/*
	json.NewEncoder() 的函数签名是 func NewEncoder(w io.Writer) *Encoder,
	返回的 Encoder 类型的指针可调用方法 Encode(v interface{}),
	将数据对象 v 的 json 编码写入 io.Writer w 中。
	*/
	enc := json.NewEncoder(file)
	//然后使用 enc.Encode(vc) 方法将结构体 vc 编码为 JSON 格式并保存到文件中。
	err := enc.Encode(vc)
	if err != nil {
		log.Println("Error in encoding json")
	}
}

JSON 与 Go 类型对应如下:

bool 对应 JSON 的 boolean
float64 对应 JSON 的 number
string 对应 JSON 的 string
nil 对应 JSON 的 null

不是所有的数据都可以编码为 JSON 类型,只有验证通过的数据结构才能被编码:

JSON 对象只支持字符串类型的 key;要编码一个 Go map 类型,map 必须是 map[string]TT 是 json 包中支持的任何类型
Channel,复杂类型和函数类型不能被编码
不支持循环数据结构;它将引起序列化进入一个无限循环
指针可以被编码,实际上是对指针指向的值进行编码(或者指针是 nil)

反序列化
json.Unmarshal() 的函数签名是 func Unmarshal(data []byte, v interface{}) error 把 JSON 解码为数据结构。
vc 编码后的数据为 js ,对其解码时,我们首先创建结构 VCard 用来保存解码的数据:var v VCard 并调用 json.Unmarshal(js, &v),解析 []byte 中的 JSON 数据并将结果存入指针 &v 指向的值

解码任意数据
json 包使用 map[string]interface{}[]interface{} 储存任意的 JSON 对象和数组;其可以被反序列化为任何的 JSON blob 存储到接口值中。

b := []byte(`{"Name": "Wednesday", "Age": 6, "Parents": ["Gomez", "Morticia"]}`)

不用理解这个数据的结构,我们可以直接使用 Unmarshal() 把这个数据编码并保存在接口值中:

var f interface{}
err := json.Unmarshal(b, &f)

f 指向的值是一个 mapkey 是一个字符串,value 是自身存储作为空接口类型的值:

map[string]interface{} {
	"Name": "Wednesday",
	"Age":  6,
	"Parents": []interface{} {
		"Gomez",
		"Morticia",
	},
}

要访问这个数据,我们可以使用类型断言

m := f.(map[string]interface{})

我们可以通过 for range 语法和 type switch 来访问其实际类型:

for k, v := range m {
	switch vv := v.(type) {
	case string:
		fmt.Println(k, "is string", vv)
	case int:
		fmt.Println(k, "is int", vv)

	case []interface{}:
		fmt.Println(k, "is an array:")
		for i, u := range vv {
			fmt.Println(i, u)
		}
	default:
		fmt.Println(k, "is of a type I don’t know how to handle")
	}
}

通过这种方式,你可以处理未知的 JSON 数据,同时可以确保类型安全。
编码和解码流
json 包提供 DecoderEncoder 类型来支持常用 JSON 数据流读写。NewDecoder() 和 NewEncoder() 函数分别封装了 io.Reader 和 io.Writer 接口。

func NewDecoder(r io.Reader) *Decoder
func NewEncoder(w io.Writer) *Encoder

要想把 JSON 直接写入文件,可以使用 json.NewEncoder 初始化文件(或者任何实现 io.Writer 的类型),并调用 Encode();反过来与其对应的是使用 json.NewDecoder 和 Decode() 函数

func NewDecoder(r io.Reader) *Decoder
func (dec *Decoder) Decode(v interface{}) error

来看下接口是如何对实现进行抽象的:数据结构可以是任何类型,只要其实现了某种接口,目标或源数据要能够被编码就必须实现 io.Writerio.Reader 接口。由于 Go 语言中到处都实现了 Reader 和 Writer,因此 Encoder 和 Decoder 可被应用的场景非常广泛,例如读取或写入 HTTP 连接、websockets 或文件

12.10 XML 数据格式

12.11 用gob传输数据

  • 编码
// gob2.go
package main

import (
	"encoding/gob"
	"log"
	"os"
)

type Address struct {
	Type             string
	City             string
	Country          string
}

type VCard struct {
	FirstName	string
	LastName	string
	Addresses	[]*Address
	Remark		string
}

var content	string

func main() {
	pa := &Address{"private", "Aartselaar","Belgium"}
	wa := &Address{"work", "Boom", "Belgium"}
	vc := VCard{"Jan", "Kersschot", []*Address{pa,wa}, "none"}
	// fmt.Printf("%v: \n", vc) // {Jan Kersschot [0x126d2b80 0x126d2be0] none}:
	// using an encoder:
	file, _ := os.OpenFile("vcard.gob", os.O_CREATE|os.O_WRONLY, 0666)
	defer file.Close()
	enc := gob.NewEncoder(file)
	err := enc.Encode(vc)
	if err != nil {
		log.Println("Error in encoding gob")
	}
}
  • 解码
var s VCard
	open, err := os.Open("./vcard.gob")
	if err != nil {
		fmt.Println(err)
	}
	decoder := gob.NewDecoder(open)
	err = decoder.Decode(&s)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(s.LastName, s.FirstName, *s.Addresses[0], *s.Addresses[1], s.Remark)
Kersschot Jan {private Aartselaar Belgium} {work Boom Belgium} none

12.12 Go中的密码学

通过网络传输的数据必须加密,以防止被 hacker(黑客)读取或篡改,并且保证发出的数据和收到的数据检验和一致。 鉴于 Go 母公司的业务,我们毫不惊讶地看到 Go 的标准库为该领域提供了超过 30 个的包:

hash 包:实现了 adler32、crc32、crc64 和 fnv 校验;
crypto 包:实现了其它的 hash 算法,比如 md4、md5、sha1 等。以及完整地实现了 aes、blowfish、rc4、rsa、xtea 等加密算法。

//sha1计算并输出了一些校验值
// hash_sha1.go
package main

import (
	"fmt"
	"crypto/sha1"
	"io"
	"log"
)

func main() {
	hasher := sha1.New()
	io.WriteString(hasher, "test")
	b := []byte{}
	fmt.Printf("Result: %x\n", hasher.Sum(b))
	fmt.Printf("Result: %d\n", hasher.Sum(b))
	//
	hasher.Reset()
	data := []byte("We shall overcome!")
	n, err := hasher.Write(data)
	if n!=len(data) || err!=nil {
		log.Printf("Hash write error: %v / %v", n, err)
	}
	checksum := hasher.Sum(b)
	fmt.Printf("Result: %x\n", checksum)
}

13.0 错误处理与测试

13.1 错误处理

    1. 错误类型断言
//  err != nil
if e, ok := err.(*os.PathError); ok {
	// remedy situation
}
switch err := err.(type) {
	case ParseError:
		PrintParseError(err)
	case PathError:
		PrintPathError(err)
	...
	default:
		fmt.Printf("Not a special error, just %s\n", err)
}

13.1.2 用 fmt 创建错误对象

if f < 0 {
	return 0, fmt.Errorf("math: square root of negative number %g", f)
}

13.2 运行时的异常和panic

一个检查程序是否被已知用户启动的具体例子:

var user = os.Getenv("USER")

func check() {
	if user == "" {
		panic("Unknown user: no value for $USER")
	}
}

13.3 从 panic 中恢复 (recover)

下面例子中的 protect() 函数调用函数参数 g 来保护调用者防止从 g 中抛出的运行时 panic,并展示 panic 中的信息:

func protect(g func()) {
	defer func() {
		log.Println("done")
		// Println executes normally even if there is a panic
		if err := recover(); err != nil {
			log.Printf("run time panic: %v", err)
		}
	}()
	log.Println("start")
	g() //   possible runtime-error
}
  • panic-recover
// panic_recover.go
package main

import (
	"fmt"
)

func badCall() {
	panic("bad end")
}

func test() {
	defer func() {
		if e := recover(); e != nil {
			fmt.Printf("Panicing %s\r\n", e)
		}
	}()
	badCall()
	fmt.Printf("After bad call\r\n") // <-- would not reach
}

func main() {
	fmt.Printf("Calling test\r\n")
	test()
	fmt.Printf("Test completed\r\n")
}
Calling test
Panicing bad end
Test completed

13.4 自定义包中的错误处理和 panicking

这是所有自定义包实现者应该遵守的最佳实践:
1)在包内部,总是应该从 panic 中 recover:不允许显式的超出包范围的 panic()
2)向包的调用者返回错误值(而不是 panic)。

注意

当定义了一个有很多方法的类型时,十之八九你会使用 String() 方法来定制类型的字符串形式的输出,
换句话说:一种可阅读性和打印性的输出。如果类型定义了 String() 方法,
它会被用在 fmt.Printf() 中生成默认的输出:等同于使用格式化描述符 %v 产生的输出。
还有 fmt.Print() 和 fmt.Println() 也会自动使用 String() 方法。
下文中定义了ParseError结构体,生成了一个string方法,虽然没有显示的调用string方法,
但panic(&ParseError{idx, field, err})也会默认输出
详情请看 the way to go 10.7 类型的 String() 方法和格式化描述符
// parse.go
package parse

import (
	"fmt"
	"strings"
	"strconv"
)

// A ParseError indicates an error in converting a word into an integer.
type ParseError struct {
    Index int      // The index into the space-separated list of words.
    Word  string   // The word that generated the parse error.
    Err error // The raw error that precipitated this error, if any.
}

// String returns a human-readable error message.
func (e *ParseError) String() string {
    return fmt.Sprintf("pkg parse: error parsing %q as int", e.Word)
}

// Parse parses the space-separated words in in put as integers.
func Parse(input string) (numbers []int, err error) {
    defer func() {
        if r := recover(); r != nil {
            var ok bool
            err, ok = r.(error)
            if !ok {
                err = fmt.Errorf("pkg: %v", r)
            }
        }
    }()

    fields := strings.Fields(input)
    numbers = fields2numbers(fields)
    return
}

func fields2numbers(fields []string) (numbers []int) {
    if len(fields) == 0 {
        panic("no words to parse")
    }
    for idx, field := range fields {
        num, err := strconv.Atoi(field)
        if err != nil {
            panic(&ParseError{idx, field, err})
        }
        numbers = append(numbers, num)
    }
    return
}
// panic_package.go
package main

import (
	"fmt"
	"./parse/parse"
)

func main() {
    var examples = []string{
            "1 2 3 4 5",
            "100 50 25 12.5 6.25",
            "2 + 2 = 4",
            "1st class",
            "",
    }

    for _, ex := range examples {
        fmt.Printf("Parsing %q:\n  ", ex)
        nums, err := parse.Parse(ex)
        if err != nil {
            fmt.Println(err) // here String() method from ParseError is used
            continue
        }
        fmt.Println(nums)
    }
}
Parsing "1 2 3 4 5":
  [1 2 3 4 5]
Parsing "100 50 25 12.5 6.25":
  pkg: pkg parse: error parsing "12.5" as int
Parsing "2 + 2 = 4":
  pkg: pkg parse: error parsing "+" as int
Parsing "1st class":
  pkg: pkg parse: error parsing "1st" as int
Parsing "":
  pkg: no words to parse

13.5 一种闭包处理错误的模式

// panic_defer.go
package main

import "fmt"

func main() {
	f()
	fmt.Println("Returned normally from f.")
}

func f() {
	defer func() {
		if r := recover(); r != nil {
			fmt.Println("Recovered in f", r)
		}
	}()
	fmt.Println("Calling g.")
	g(0)
	fmt.Println("Returned normally from g.")
}

func g(i int) {
	if i > 3 {
		fmt.Println("Panicking!")
		panic(fmt.Sprintf("%v", i))
	}
	defer fmt.Println("Defer in g", i)
	fmt.Println("Printing in g", i)
	g(i + 1)
}
Calling g.
Printing in g 0
Printing in g 1
Printing in g 2
Printing in g 3
Panicking!
Defer in g 3
Defer in g 2
Defer in g 1
Defer in g 0
Recovered in f 4
Returned normally from f.

13.6 启动外部命令和程序

14.2 协程间的信道

var ch1 chan string
ch1 = make(chan string)
-------------------------
ch1 := make(chan string)
函数通道 funcChan := make(chan func())

14.2.2 通信操作符 <-

这个操作符直观的标示了数据的传输:信息按照箭头的方向流动。

流向通道(发送)

ch <- int1 表示:用通道 ch 发送变量 int1(双目运算符,中缀 = 发送)

从通道流出(接收),三种方式:

int2 = <- ch 表示:变量 int2 从通道 ch(一元运算的前缀操作符,前缀 = 接收)接收数据(获取新值);假设 int2 已经声明过了,如果没有的话可以写成:int2 := <- ch。

<- ch 可以单独调用获取通道的(下一个)值,当前值会被丢弃,但是可以用来验证,所以以下代码是合法的:

if <- ch != 1000{
	...
}

实例

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan string)

	go sendData(ch)
	go getData(ch)

	time.Sleep(1e9)
}

func sendData(ch chan string) {
	ch <- "Washington"
	ch <- "Tripoli"
	ch <- "London"
	ch <- "Beijing"
	ch <- "Tokyo"
}

func getData(ch chan string) {
	var input string
	// time.Sleep(2e9)
	for {
		input = <-ch
		fmt.Printf("%s ", input)
	}
}
Washington Tripoli London Beijing tokyo

如果改变getData()函数

func getData(ch chan string) {
	var input string
	// time.Sleep(2e9)
	if <-ch != "" {
		input = <-ch
		fmt.Printf("%s ", input)
	}
}
输出结果  Tripoli 
因为  <-ch  获取的是通道的下一个值 所以跳过 Washington 输出 Tripoli 

14.2.3 通道阻塞

默认情况下,通信是同步且无缓冲的:在有接受者接收数据之前,发送不会结束。可以想象一个无缓冲的通道在没有空间来保存数据的时候:必须要一个接收者准备好接收通道的数据然后发送者可以直接把数据发送给接收者。所以通道的发送/接收操作在对方准备好之前是阻塞的:

1)对于同一个通道,发送操作(协程或者函数中的),在接收者准备好之前是阻塞的:如果 ch 中的数据无人接收,就无法再给通道传入其他数据:新的输入无法在通道非空的情况下传入。所以发送操作会等待 ch 再次变为可用状态:就是通道值被接收时(可以传入变量)。

2)对于同一个通道,接收操作是阻塞的(协程或函数中的),直到发送者可用:如果通道中没有数据,接收者就阻塞了。

尽管这看上去是非常严格的约束,实际在大部分情况下工作的很不错。

package main

import "fmt"

func main() {
	ch1 := make(chan int)
	go pump(ch1)       // pump hangs
	fmt.Println(<-ch1) // prints only 0
}

func pump(ch chan int) {
	for i := 0; ; i++ {
		ch <- i
	}
}
0

14.2.5 同步通道-使用带缓冲的通道

扩容

buf := 100
ch1 := make(chan string, buf)

内置的 cap() 函数可以返回缓冲区的容量。

在其他协程运行时让 main 程序无限阻塞的通常做法是在 main() 函数的最后放置一个 select {}

14.2.7 信号量模式

func compute(ch chan int){
	ch <- someComputation() // when it completes, signal on the channel.
}

func main(){
	ch := make(chan int) 	// allocate a channel.
	go compute(ch)		// start something in a goroutines
	doSomethingElseForAWhile()
	result := <- ch
}
ch := make(chan int)
go func(){
	// doSomething
	ch <- 1 // Send a signal; value does not matter
}()
doSomethingElseForAWhile()
<- ch	// Wait for goroutine to finish; discard sent value.

14.2.8 实现并行的 for 循环

for i, v := range data {
	go func (i int, v float64) {
		doSomething(i, v)
		...
	} (i, v)
}

14.2.9 用带缓冲通道实现一个信号量

信号量是实现互斥锁(排外锁)常见的同步机制,限制对资源的访问,解决读写问题,

type Empty interface {}
type semaphore chan Empty

// acquire n resources
func (s semaphore) P(n int) {
	e := new(Empty)
	for i := 0; i < n; i++ {
		s <- e
	}
}

// release n resources
func (s semaphore) V(n int) {
	for i:= 0; i < n; i++{
		<- s
	}
}

实现互斥例子

/* mutexes */
func (s semaphore) Lock() {
	s.P(1)
}

func (s semaphore) Unlock(){
	s.V(1)
}

/* signal-wait */
func (s semaphore) Wait(n int) {
	s.P(n)
}

func (s semaphore) Signal() {
	s.V(1)
}
例1 用这种习惯用法写一个程序,开启一个协程来计算 2 个整数的和并等待计算结果并打印出来。
// gosum.go
package main

import (
	"fmt"
)

func sum(x, y int, c chan int) {
	c <- x + y
}

func main() {
	c := make(chan int)
	go sum(12, 13, c)
	fmt.Println(<-c) // 25
}
例 2
用这种习惯用法写一个程序,有两个协程,
第一个提供数字 01020...90 并将他们放入通道,第二个协程从通道中读取并打印。
main() 等待两个协程完成后再结束。
// goroutines2.go
package main

import "fmt"

// integer producer:
func numGen(start, count int, out chan<- int) { //out代表接受管道,chan的右边,代表进入
	for i := 0; i < count; i++ {
		out <- start
		start = start + count
	}
	close(out)//关闭 通道
}

// integer consumer:
func numEchoRange(in <-chan int, done chan<- bool) {
	for num := range in {
		fmt.Printf("%d\n", num)
	}
	done <- true
}

func main() {
	numChan := make(chan int)
	done := make(chan bool)
	go numGen(0, 10, numChan)
	go numEchoRange(numChan, done)

	<-done
}

/* Output:
0
10
20
30
40
50
60
70
80
90
*/

习惯用法:通道工厂模式

编程中常见的另外一种模式如下:不将通道作为参数传递给协程,而用函数来生成一个通道并返回(工厂角色);函数内有个匿名函数被协程调用。

package main

import (
	"fmt"
	"time"
)

func main() {
	stream := pump()
	go suck(stream)
	time.Sleep(1e9)
}

func pump() chan int {
	ch := make(chan int)
	go func() {
		for i := 0; ; i++ {
			ch <- i
		}
	}()
	return ch
}

func suck(ch chan int) {
	for {
		fmt.Println(<-ch)
	}
}

14.2.10 给通道使用 for 循环

for 循环的 range 语句可以用在通道 ch 上,便可以从通道中获取值,像这样:

for v := range ch {
	fmt.Printf("The value is %v\n", v)
}

实例

package main

import (
	"fmt"
	"time"
)

func main() {
	suck(pump())
	time.Sleep(1e9)
}

func pump() chan int {
	ch := make(chan int)
	go func() {
		for i := 0; ; i++ {
			ch <- i
		}
	}()
	return ch
}

func suck(ch chan int) {
	go func() {
		for v := range ch {
			fmt.Println(v)
		}
	}()
}
习惯用法:管道和选择器模式

更具体的例子还有协程处理它从通道接收的数据并发送给输出通道:

sendChan := make(chan int)
receiveChan := make(chan string)
go processChannel(sendChan, receiveChan)

func processChannel(in <-chan int, out chan<- string) {
	for inValue := range in {
		result := ... /// processing inValue
		out <- result
	}
}

协程 filter(in, out chan int, prime int) 拷贝整数到输出通道,丢弃掉可以被 prime 整除的数字。然后每个 prime 又开启了一个新的协程,生成器和选择器并发请求。

// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.package main
package main

import "fmt"

// Send the sequence 2, 3, 4, ... to channel 'ch'.
func generate(ch chan int) {
	for i := 2; ; i++ {
		ch <- i // Send 'i' to channel 'ch'.
	}
}

// Copy the values from channel 'in' to channel 'out',
// removing those divisible by 'prime'.
func filter(in, out chan int, prime int) {
	for {
		i := <-in // Receive value of new variable 'i' from 'in'.
		if i%prime != 0 {
			out <- i // Send 'i' to channel 'out'.
		}
	}
}

// The prime sieve: Daisy-chain filter processes together.
func main() {
	ch := make(chan int) // Create a new channel.
	go generate(ch)      // Start generate() as a goroutine.
	for {
		prime := <-ch
		fmt.Print(prime, " ")
		ch1 := make(chan int)
		go filter(ch, ch1, prime)
		ch = ch1
	}
}
2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97 101
103 107 109 113 127 131 137 139 149 151 157 163 167 173 179 181 191 193 197 199 211 223
227 229 233 239 241 251 257 263 269 271 277 281 283 293 307 311 313 317 331 337 347 349
353 359 367 373 379 383 389 397 401 409 419 421 431 433 439 443 449 457 461 463 467 479
487 491 499 503 509 521 523 541 547 557 563 569 571 577 587 593 599 601 607 613 617 619
631 641 643 647 653 659 661 673 677 683 691 701 709 719 727 733 739 743 751 757 761 769
773 787 797 809 811 821 823 827 829 839 853 857 859 863 877 881 883 887 907 911 919 929
937 941 947 953 967 971 977 983 991 997 1009 1013...
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
)

// Send the sequence 2, 3, 4, ... to returned channel
func generate() chan int {
	ch := make(chan int)
	go func() {
		for i := 2; ; i++ {
			ch <- i
		}
	}()
	return ch
}

// Filter out input values divisible by 'prime', send rest to returned channel
func filter(in chan int, prime int) chan int {
	out := make(chan int)
	go func() {
		for {
			if i := <-in; i%prime != 0 {
				out <- i
			}
		}
	}()
	return out
}

func sieve() chan int {
	out := make(chan int)
	go func() {
		ch := generate()
		for {
			prime := <-ch
			ch = filter(ch, prime)
			out <- prime
		}
	}()
	return out
}

func main() {
	primes := sieve()
	for {
		fmt.Println(<-primes)
	}
}

14.3 协程的同步:关闭通道-测试阻塞的通道

第一个可以通过函数 close(ch) 来完成:这个将通道标记为无法通过发送操作 <- 接受更多的值;给已经关闭的通道发送或者再次关闭都会导致运行时的 panic()。在创建一个通道后使用 defer 语句是个不错的办法(类似这种情况):

ch := make(chan float64)
defer close(ch)

第二个问题可以使用逗号 ok 模式用来检测通道是否被关闭。

if v, ok := <-ch; ok {//ok is true if v received value
  process(v)
}

或者在 for 循环中接收的时候,当关闭的时候使用 break

而检测通道当前是否阻塞,需要使用 select(参见第 14.4 节)。

select {
case v, ok := <-ch:
  if ok {
    process(v)
  } else {
    fmt.Println("The channel is closed")
  }
default:
  fmt.Println("The channel is blocked")
}

改变了代码

package main

import "fmt"

func main() {
	ch := make(chan string)
	go sendData(ch)
	getData(ch)
}

func sendData(ch chan string) {
	ch <- "Washington"
	ch <- "Tripoli"
	ch <- "London"
	ch <- "Beijing"
	ch <- "Tokio"
	close(ch)
}

func getData(ch chan string) {
	for {
		input, open := <-ch
		if !open {
			break
		}
		fmt.Printf("%s ", input)
	}
}

14.4 使用 select 切换协程

从不同的并发执行的协程中获取值可以通过关键字 select 来完成,它和 switch 控制语句非常相似(章节 5.3)也被称作通信开关;

select {
case u:= <- ch1:
        ...
case v:= <- ch2:
        ...
        ...
default: // no value ready to be received
        ...
}

default 语句是可选的;fallthrough 行为,和普通的 switch 相似,是不允许的。在任何一个 case 中执行 break 或者 return,select 就结束了。
select :选择处理列出的多个通信情况中的一个。
1.如果都阻塞了,会等待直到其中一个可以处理
2.如果多个可以处理,随机选择一个
3.如果没有通道操作可以处理并且写了 default 语句它就会执行:default 永远是可运行的(这就是准备好了,可以执行)。

如果没有 default,select 就会一直阻塞。

package main

import (
	"fmt"
	"time"
)

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)

	go pump1(ch1)
	go pump2(ch2)
	go suck(ch1, ch2)

	time.Sleep(1e9)
}

func pump1(ch chan int) {
	for i := 0; ; i++ {
		ch <- i * 2
	}
}

func pump2(ch chan int) {
	for i := 0; ; i++ {
		ch <- i + 5
	}
}

func suck(ch1, ch2 chan int) {
	for {
		select {
		case v := <-ch1:
			fmt.Printf("Received on channel 1: %d\n", v)
		case v := <-ch2:
			fmt.Printf("Received on channel 2: %d\n", v)
		}
	}
}
Received on channel 2: 5
Received on channel 2: 6
Received on channel 1: 0
Received on channel 2: 7
Received on channel 2: 8
Received on channel 2: 9
Received on channel 2: 10
Received on channel 1: 2
Received on channel 2: 11
...
Received on channel 2: 47404
Received on channel 1: 94346
Received on channel 1: 94348

习惯用法:后台服务模式
服务通常是是用后台协程中的无限循环实现的,在循环中使用 select 获取并处理通道中的数据:

// Backend goroutine.
func backend() {
	for {
		select {
		case cmd := <-ch1:
			// Handle ...
		case cmd := <-ch2:
			...
		case cmd := <-chStop:
			// stop server
		}
	}
}

14.5 通道、超时和计时器(Ticker)

time.Tick() 函数声明为

 Tick(d Duration) <-chan Time
func After(d Duration) <-chan Time
package main

import (
	"fmt"
	"time"
)

func main() {
	tick := time.Tick(1e8)
	boom := time.After(5e8)
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			return
		default:
			fmt.Println("    .")
			time.Sleep(5e7)
		}
	}
}
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
BOOM!

习惯用法:简单超时模式

timeout := make(chan bool, 1)
go func() {
        time.Sleep(1e9) // one second
        timeout <- true
}()
select {
    case <-ch:
        // a read from ch has occured
    case <-timeout:
        // the read from ch has timed out
        break
}
也可以使用 time.After() 函数替换 timeout-channel。可以在 select 中通过
time.After() 发送的超时信号来停止协程的执行。以下代码,在 timeoutNs 纳秒后
执行 select 的 timeout 分支后,执行 client.Call 的协程也随之结束,不会给通道 ch 返回值:
ch := make(chan error, 1)
go func() { ch <- client.Call("Service.Method", args, &reply) } ()
select {
case resp := <-ch
    // use resp and reply
case <-time.After(timeoutNs):
    // call timed out
    break
}
ch := make(chan int, 1)
go func() { for { ch <- 1 } } ()
L:
for {
    select {
    case <-ch:
        // do something
    case <-time.After(timeoutNs):
        // call timed out
        break L
    }
}

第三种形式:假设程序从多个复制的数据库同时读取。只需要一个答案,需要接收首先到达的答案,Query 函数获取数据库的连接切片并请求。并行请求每一个数据库并返回收到的第一个响应:

func Query(conns []Conn, query string) Result {
    ch := make(chan Result, 1)
    for _, conn := range conns {
        go func(c Conn) {
            select {
            case ch <- c.DoQuery(query):
            default:
            }
        }(conn)
    }
    return <- ch
}

次声明,结果通道 ch 必须是带缓冲的:以保证第一个发送进来的数据有地方可以存放,确保放入的首个数据总会成功,所以第一个到达的值会被获取而与执行的顺序无关。正在执行的协程可以总是可以使用 runtime.Goexit() 来停止。

使用锁的情景:
访问共享数据结构中的缓存信息
保存应用程序上下文和状态信息数据

使用通道的情景:
与异步操作的结果进行交互
分发任务
传递数据所有权

14.9 实现 Futures 模式

func InverseProduct(a Matrix, b Matrix) {
    a_inv_future := InverseFuture(a)   // start as a goroutine
    b_inv_future := InverseFuture(b)   // start as a goroutine
    a_inv := <-a_inv_future
    b_inv := <-b_inv_future
    return Product(a_inv, b_inv)
}
func InverseFuture(a Matrix) chan Matrix {
    future := make(chan Matrix)
    go func() {
        future <- Inverse(a)
    }()
    return future
}

14.11 限制同时处理的请求数

使用带缓冲区的通道很容易实现这一点(参见 14.2.5),其缓冲区容量就是同时处理请求的最大数量。程序 max_tasks.go 虽然没有做什么有用的事但是却包含了这个技巧:超过 MAXREQS 的请求将不会被同时处理,因为当信号通道表示缓冲区已满时 handle() 函数会阻塞且不再处理其他请求,直到某个请求从 sem 中被移除。sem 就像一个信号量,这一专业术语用于在程序中表示特定条件的标志变量。

package main

const MAXREQS = 50

var sem = make(chan int, MAXREQS)

type Request struct {
	a, b   int
	replyc chan int
}

func process(r *Request) {
	// do something
}

func handle(r *Request) {
	sem <- 1 // doesn't matter what we put in it
	process(r)
	<-sem // one empty place in the buffer: the next request can start
}

func server(service chan *Request) {
	for {
		request := <-service
		go handle(request)
	}
}

func main() {
	service := make(chan *Request)
	go server(service)
}

14.17 使用通道并发访问对象

为了保护对象被并发访问修改,我们可以使用协程在后台顺序执行匿名函数来替代使用同步互斥锁。在下面的程序中我们有一个类型 Person 中包含一个字段 chF ,这是一个用于存放匿名函数的通道。

这个结构在构造函数 NewPerson() 中初始化的同时会启动一个后台协程 backend()。backend() 方法会在一个无限循环中执行 chF 中放置的所有函数,有效地将它们序列化从而提供了安全的并发访问。更改和读取 salary 的方法会通过将一个匿名函数写入 chF 通道中,然后让 backend() 按顺序执行以达到其目的。需注意的是 Salary() 方法创建的闭包函数是如何将 fChan 通道包含在其中的。

package main

import (
	"fmt"
	"strconv"
)

type Person struct {
	Name   string
	salary float64
	chF    chan func()
}

func NewPerson(name string, salary float64) *Person {
	p := &Person{name, salary, make(chan func())}
	go p.backend()
	return p
}

func (p *Person) backend() {
	for f := range p.chF {
		f()
	}
}

// Set salary.
func (p *Person) SetSalary(sal float64) {
	p.chF <- func() { p.salary = sal }
}

// Retrieve salary.
func (p *Person) Salary() float64 {
	fChan := make(chan float64)
	p.chF <- func() { fChan <- p.salary }
	return <-fChan
}

func (p *Person) String() string {
	return "Person - name is: " + p.Name + " - salary is: " + strconv.FormatFloat(p.Salary(), 'f', 2, 64)
}

func main() {
	bs := NewPerson("Smith Bill", 2500.5)
	fmt.Println(bs)
	bs.SetSalary(4000.25)
	fmt.Println("Salary changed:")
	fmt.Println(bs)
}
Person - name is: Smith Bill - salary is: 2500.50
Salary changed:
Person - name is: Smith Bill - salary is: 4000.25

16.0 常见的陷阱和错误

16.1 误用短声明导致变量覆盖

此类错误也容易在 for 循环中出现,尤其当函数返回一个具名变量时难于察觉,例如以下的代码段:

func shadow() (err error) {
	x, err := check1() // x 是新创建变量,err 是被赋值
	if err != nil {
		return // 正确返回 err
	}
	if y, err := check2(x); err != nil { // y 和 if 语句中 err 被创建
		return // if 语句中的 err 覆盖外面的 err,所以错误的返回 nil !
	} else {
		fmt.Println(y)
	}
	return
}

解释:if 语句中的 err 覆盖外面的 err,所以错误的返回 nil !,这句话意思是return永远返回的是函数定义的err,而if里创建的err是局部变量,如果只有return,则不能将该局部变量err(if)return返回,所以执行到这里,相当于return err(函数定义的err),因为第一步err为nil,所以这步返回的err为nil

16.3 发生错误时使用 defer 关闭一个文件

如果你在一个 for 循环内部处理一系列文件,你需要使用 defer 确保文件在处理完毕后被关闭,例如:

for _, file := range files {
    if f, err = os.Open(file); err != nil {
        return
    }
    // 这是错误的方式,当循环结束时文件没有关闭
    defer f.Close()
    // 对文件进行操作
    f.Process(data)
}

但是在循环内结尾处的 defer 没有执行,所以文件一直没有关闭!垃圾回收机制可能会自动关闭文件,但是这会产生一个错误,更好的做法是:

for _, file := range files {
    if f, err = os.Open(file); err != nil {
        return
    }
    // 对文件进行操作
    f.Process(data)
    // 关闭文件
    f.Close()
 }

defer 仅在函数返回时才会执行,在循环内的结尾或其他一些有限范围的代码内不会执行。

16.4 何时使用 new() 和 make()

- 切片、映射和通道,使用 make()
- 数组、结构体和所有的值类型,使用 new()

16.5 不需要将一个指向切片的指针传递给函数

在第 4.9 小节,我们已经知道,切片实际是一个指向潜在数组的指针。我们常常需要把切片作为一个参数传递给函数是因为:实际就是传递一个指向变量的指针,在函数内可以改变这个变量,而不是传递数据的拷贝

       func findBiggest( listOfNumbers []int ) int {}

而不是:

       func findBiggest( listOfNumbers *[]int ) int {}

16.6 使用指针指向接口类型

查看如下程序:nexter 是一个接口类型,并且定义了一个 next() 方法读取下一字节。函数 nextFew1 将 nexter 接口作为参数并读取接下来的 num 个字节,并返回一个切片:这是正确做法。但是 nextFew2 使用一个指向 nexter 接口类型的指针作为参数传递给函数:当使用 next() 函数时,系统会给出一个编译错误:n.next undefined (type *nexter has no field or method next) (译者注:n.next 未定义(*nexter 类型没有 next 成员或 next 方法))

package main
import (
    "fmt"
)
type nexter interface {
    next() byte
}
func nextFew1(n nexter, num int) []byte {
    var b []byte
    for i:=0; i < num; i++ {
        b[i] = n.next()
    }
    return b
}
func nextFew2(n *nexter, num int) []byte {
    var b []byte
    for i:=0; i < num; i++ {
        b[i] = n.next() // 编译错误:n.next 未定义(*nexter 类型没有 next 成员或 next 方法)
    }
    return b
}
func main() {
    fmt.Println("Hello World!")
}

永远不要使用一个指针指向一个接口类型,因为它已经是一个指针。

16.9 闭包和协程的使用

package main

import (
    "fmt"
    "time"
)

var values = [5]int{10, 11, 12, 13, 14}

func main() {
    // 版本 A:
    for ix := range values { // ix 是索引值
        func() {
            fmt.Print(ix, " ")
        }() // 调用闭包打印每个索引值
    }
    fmt.Println()
    // 版本 B:和 A 版本类似,但是通过调用闭包作为一个协程
    for ix := range values {
        go func() {
            fmt.Print(ix, " ")
        }()
    }
    fmt.Println()
    time.Sleep(5e9)
    // 版本 C:正确的处理方式
    for ix := range values {
        go func(ix interface{}) {
            fmt.Print(ix, " ")
        }(ix)
    }
    fmt.Println()
    time.Sleep(5e9)
    // 版本 D:输出值:
    for ix := range values {
        val := values[ix]
        go func() {
            fmt.Print(val, " ")
        }()
    }
    time.Sleep(1e9)
}
0 1 2 3 4

4 4 4 4 4

1 0 3 4 2

10 11 12 13 14

版本 A 调用闭包 5 次打印每个索引值,版本 B 也做相同的事,但是通过协程调用每个闭包。按理说这将执行得更快,因为闭包是并发执行的。如果我们阻塞足够多的时间,让所有协程执行完毕,版本 B 的输出是:4 4 4 4 4。为什么会这样?在版本 B 的循环中,ix 变量实际是一个单变量,表示每个数组元素的索引值。因为这些闭包都只绑定到一个变量,这是一个比较好的方式,当你运行这段代码时,你将看见每次循环都打印最后一个索引值 4,而不是每个元素的索引值。因为协程可能在循环结束后还没有开始执行,而此时 ix 值是 4。

版本 C 的循环写法才是正确的:调用每个闭包时将 ix 作为参数传递给闭包。ix 在每次循环时都被重新赋值,并将每个协程的 ix 放置在栈中,所以当协程最终被执行时,每个索引值对协程都是可用的。注意这里的输出可能是 0 2 1 3 4 或者 0 3 1 2 4 或者其他类似的序列,这主要取决于每个协程何时开始被执行。

在版本 D 中,我们输出这个数组的值,为什么版本 B 不能而版本 D 可以呢

因为版本 D 中的变量声明是在循环体内部,所以在每次循环时,这些变量相互之间是不共享的,所以这些变量可以单独的被每个闭包使用。

16.10.2 避免错误检测使代码变得混乱:

... err1 := api.Func1()
if err1 != nil {
    fmt.Println("err: " + err.Error())
    return
}
err2 := api.Func2()
if err2 != nil {
...
    return
}    

首先,包括在一个初始化的 if 语句中对函数的调用。但即使代码中到处都是以 if 语句的形式通知错误(通过打印错误信息)。通过这种方式,很难分辨什么是正常的程序逻辑,什么是错误检测或错误通知。还需注意的是,大部分代码都是致力于错误的检测。通常解决此问题的好办法是尽可能以闭包的形式封装你的错误检测,例如下面的代码:

func httpRequestHandler(w http.ResponseWriter, req *http.Request) {
    err := func () error {
        if req.Method != "GET" {
            return errors.New("expected GET")
        }
        if input := parseInput(req); input != "command" {
            return errors.New("malformed command")
        }
        // 可以在此进行其他的错误检测
    } ()

        if err != nil {
            w.WriteHeader(400)
            io.WriteString(w, err)
            return
        }
        doSomething() ...

这种方法可以很容易分辨出错误检测、错误通知和正常的程序逻辑(更详细的方式参考第 13.5 小节)。

17.1 逗号 ok 模式

在学习本书第二部分和第三部分时,我们经常在一个表达式返回 2 个参数时使用这种模式:something, ok,第一个参数是一个值或者 nil第二个参数是 true/false 或者一个错误 error。在一个需要赋值的 if 条件语句中,使用这种模式去检测第二个参数值会让代码显得优雅简洁。这种模式在 Go 语言编码规范中非常重要。下面总结了所有使用这种模式的例子:
(1)在函数返回时检测错误(参考第 5.2 小节):

value, err := pack1.Func1(param1)

if err != nil {
    fmt.Printf("Error %s in pack1.Func1 with parameter %v", err.Error(), param1)
    return err
}

// 函数Func1没有错误:
Process(value)

e.g.: os.Open(file) strconv.Atoi(str)

这段代码中的函数将错误返回给它的调用者,当函数执行成功时,返回的错误是 nil,所以使用这种写法:

func SomeFunc() error {if value, err := pack1.Func1(param1); err != nil {return err
    }return nil
}

这种模式也常用于通过 defer 使程序从 panic 中恢复执行(参考第 17.2(4) 小节)。

要实现简洁的错误检测代码,更好的方式是使用闭包,参考第 16.10.2 小节

(2)检测映射中是否存在一个键值(参考第 8.2 小节):key1 在映射 map1 中是否有值?

if value, isPresent = map1[key1]; isPresent {
        Process(value)
}
// key1 不存在

(3)检测一个接口类型变量 varI 是否包含了类型 T:类型断言(参考第 11.3 小节):

if value, ok := varI.(T); ok {
    Process(value)
}
// 接口类型 varI 没有包含类型 T

(4)检测一个通道 ch 是否关闭(参考第 14.3 小节):

    for input := range ch {
        Process(input)
    }

    for {
        if input, open := <-ch; !open {
            break // 通道是关闭的
        }
        Process(input)
    }

17.2 defer 模式 defer统计

17.4 运算符模式和接口

17.4.1 函数作为运算符

运算符由包级别的函数实现,以操作一个或两个参数,并返回一个新对象。函数针对要操作的对象,在专门的包中实现。例如,假设要在包 matrix 中实现矩阵操作,就会包含 Add() 用于矩阵相加,Mult() 用于矩阵相乘,他们都会返回一个矩阵。这两个函数通过包名来调用,因此可以创造出如下形式的表达式:

m := matrix.Add(m1, matrix.Mult(m2, m3))

如果我们想在这些运算中区分不同类型的矩阵(稀疏或稠密),由于没有函数重载,我们不得不给函数起不同的名称,例如:

func addSparseToDense (a *sparseMatrix, b *denseMatrix) *denseMatrix
func addDenseToDense (a *denseMatrix, b *denseMatrix) *denseMatrix
func addSparseToSparse (a *sparseMatrix, b *sparseMatrix) *sparseMatrix

这可不怎么优雅,我们能选择的最佳方案是将它们隐藏起来,作为包的私有函数,并暴露单一的 Add() 函数作为公共 API。可以在嵌套的 switch 断言中测试类型,以便在任何支持的参数组合上执行操作:

func Add(a Matrix, b Matrix) Matrix {
	switch a.(type) {
	case sparseMatrix:
		switch b.(type) {
		case sparseMatrix:
			return addSparseToSparse(a.(sparseMatrix), b.(sparseMatrix))
		case denseMatrix:
			return addSparseToDense(a.(sparseMatrix), b.(denseMatrix))}
	default:
		// 不支持的参数}
}

17.4.2 方法作为运算符

func (a *sparseMatrix) Add(b Matrix) Matrix {
	switch b.(type) {
	case sparseMatrix:
		return addSparseToSparse(a.(sparseMatrix), b.(sparseMatrix))
	case denseMatrix:
		return addSparseToDense(a.(sparseMatrix), b.(denseMatrix))default:
		// 不支持的参数}
}

17.4.3 使用接口

type Algebraic interface {
	Add(b Algebraic) Algebraic
	Min(b Algebraic) Algebraic
	Mult(b Algebraic) Algebraic
	…
	Elements()
}

然后为我们的 matrix 类型定义 Add(),Min(),Mult(),……等方法。

每种实现上述 Algebraic 接口类型的方法都可以链式调用。每个方法实现都应基于参数类型,使用 switch 类型断言来提供优化过的实现。另外,应该为仅依赖于接口的方法,指定一个默认处理分支:

func (a *denseMatrix) Add(b Algebraic) Algebraic {
	switch b.(type) {
	case sparseMatrix:
		return addDenseToSparse(a, b.(sparseMatrix))default:
		for x in range b.Elements()}
}

18.0 出于性能考虑的实用代码片段

18.1 字符串

(1)如何修改字符串中的一个字符:

str:="hello"
c:=[]byte(str)
c[0]='c'
s2:= string(c) // s2 == "cello"

(2)如何获取字符串的子串:

substr := str[n:m]

(3)如何使用 for 或者 for-range 遍历一个字符串:

// gives only the bytes:
for i:=0; i < len(str); i++ {= str[i]
}
// gives the Unicode characters:
for ix, ch := range str {}

(4)如何获取一个字符串的字节数:len(str)

如何获取一个字符串的字符数:

(最快速)使用 utf8.RuneCountInString(str)

或使用 len([]rune(str))
5)如何连接字符串:
(最快速)使用 bytes.Buffer(参考章节 7.2)

或使用 Strings.Join()(参考章节 4.7)

或使用 +=:

str1 := "Hello " 
str2 := "World!"
str1 += str2 //str1 == "Hello World!"

(6)如何解析命令行参数:使用 os 或者 flag 包(参考例 12.4)

18.2 数组和切片

创建:

arr1 := new([len]type)

slice1 := make([]type, len)

初始化:

arr1 := [...]type{i1, i2, i3, i4, i5}

arrKeyValue := [len]type{i1: val1, i2: val2}

var slice1 []type = arr1[start:end]

(1)如何截断数组或者切片的最后一个元素:

​ line = line[:len(line)-1]

(2)如何使用 for 或者 for-range 遍历一个数组(或者切片):

for i:=0; i < len(arr); i++ {= arr[i]
}
for ix, value := range arr {}

(3)如何在一个二维数组或者切片 arr2Dim 中查找一个指定值 V:

found := false
Found: for row := range arr2Dim {
    for column := range arr2Dim[row] {
        if arr2Dim[row][column] == V{
            found = true
            break Found
        }
    }
}

18.3 映射

创建: map1 := make(map[keytype]valuetype)

初始化: map1 := map[string]int{"one": 1, "two": 2}

(1)如何使用 for 或者 for-range 遍历一个映射:

for key, value := range map1 {}

(2)如何在一个映射中检测键 key1 是否存在:

val1, isPresent = map1[key1]

返回值:键 key1 对应的值或者 0true 或者 false

(3)如何在映射中删除一个键:delete(map1, key1)

18.4 结构体

创建:

type struct1 struct {
    field1 type1
    field2 type2
    …
}
ms := new(struct1)

初始化:

ms := &struct1{10, 15.5, "Chris"}

当结构体的命名以大写字母开头时,该结构体在包外可见。 通常情况下,为每个结构体定义一个构建函数,并推荐使用构建函数初始化结构体(参考例 10.2):

ms := Newstruct1{10, 15.5, "Chris"}
func Newstruct1(n int, f float32, name string) *struct1 {
    return &struct1{n, f, name} 
}

18.5 接口

(1)如何检测一个值 v 是否实现了接口 Stringer:

if v, ok := v.(Stringer); ok {
    fmt.Printf("implements String(): %s\n", v.String())
}

(2)如何使用接口实现一个类型分类函数:

func classifier(items ...interface{}) {
    for i, x := range items {
        switch x.(type) {
        case bool:
            fmt.Printf("param #%d is a bool\n", i)
        case float64:
            fmt.Printf("param #%d is a float64\n", i)
        case int, int64:
            fmt.Printf("param #%d is an int\n", i)
        case nil:
            fmt.Printf("param #%d is nil\n", i)
        case string:
            fmt.Printf("param #%d is a string\n", i)
        default:
            fmt.Printf("param #%d’s type is unknown\n", i)
        }
    }
}

18.6 函数

如何使用内建函数 recover() 终止 panic() 过程(参考章节 13.3):
func protect(g func()) {
    defer func() {
        log.Println("done")
        // Println executes normally even if there is a panic
        if x := recover(); x != nil {
            log.Printf("run time panic: %v", x)
        }
    }()
    log.Println("start")
    g()
}

18.7 文件

(1)如何打开一个文件并读取:

file, err := os.Open("input.dat")
  if err != nil {
    fmt.Printf("An error occurred on opening the inputfile\n" +
      "Does the file exist?\n" +
      "Have you got acces to it?\n")
    return
  }
  defer file.Close()
  iReader := bufio.NewReader(file)
  for {
    str, err := iReader.ReadString('\n')
    if err != nil {
      return // error or EOF
    }
    fmt.Printf("The input was: %s", str)
  }

(2)如何通过切片读写文件:

func cat(f *file.File) {
  const NBUF = 512
  var buf [NBUF]byte
  for {
    switch nr, er := f.Read(buf[:]); true {
    case nr < 0:
      fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n",
        f.String(), er.String())
      os.Exit(1)
    case nr == 0: // EOF
      return
    case nr > 0:
      if nw, ew := file.Stdout.Write(buf[0:nr]); nw != nr {
        fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n",
          f.String(), ew.String())
      }
    }
  }
}

18.8 协程 (goroutine) 与通道 (channel)

18.10 其他

如何在程序出错时终止程序:

if err != nil {
   fmt.Printf("Program stopping with error %v", err)
   os.Exit(1)
}

或者:

if err != nil { 
	panic("ERROR occurred: " + err.Error())
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值