所有语言都有许多共同点,每次学习都需要去学习一遍这些共同点,但是又很容易忘记。那 如果将学习这些语言的时候将共同的地方概况起来,然后将不同点区分出来是不是更容易学 习呢?

该文主要是为了辅助 how-learn-a-program-language.xmind 而生,主要是包含了与之相关 的所有测试代码和简要说明。

如果采用使用单个文件方式来测试可能比较繁琐,比如当你需要测试的时候你可能需要创建 a.c, a.js, a.py, a.go, a.sh 想想都会崩溃吧!!!

因此 org file 就很好的解决问题。

费话不多说,开始学习吧~~~~~~~~

学习一门语言,基本会遵循这样的步骤:

基本类型系统 -> 变量 -> 函数 -> 控制流程 -> 复杂类型系统 -> 内置库(包) -> 异步/ 延迟编程 -> 错误处理 -> 测试 -> 搞项目

基本都是这样的一个大概的流程,尤其前五个是一门语言最最基础的东西。

Go 官方博客: https://go.dev/blog/all Go 官方文档: https://golang.org/doc/effective_go Go 官方标准: https://golang.org/ref/spec

Go Demos: https://github.com/gcclll/golang-demos.git

变量

Golang 的变量声明和一般的声明方式有点大不一样,订好体现在它的类型是放在变量名的 后面(var n int = 100),像 C 是在前(int n = 10),可能就是为了和 C 区别一下吧?

变量声明

Golang 变量声明

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import "fmt"

func main() {
	var bVal bool = true
	var iCount int32 = 100
	var sName string = "Tom"
	var fPi float32 = 3.14159
	val := false
	count := 200
	name := "Lily"
	pi := 3.1415
	fmt.Println(
		bVal, iCount, sName, fPi,
		val, count, name, pi
	)
}
true 100 Tom 3.14159

JavaScript 变量声明

1
2
3
4
5
var bVal = false, iCount = 100,
    sName = "Tom", fPi = 3.14159
let val = false, count = 100
const pi = 3.14159, name = "Lily"
console.log(bVal, iCount, sName, fPi)
false 100 Tom 3.14159
undefined

变量分类

作用域分类(全局、局部变量)

Golang
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import "fmt"

var gCount int32 = 100 // 全局变量
func iCanVisitGCount() (int16) {
	var iCount int16 = 1000
	fmt.Printf("我能访问全局变量 gCount: %d\n", gCount)
	fmt.Printf("我能访问局部变量 iCount: %d\n", iCount)
	if true {
		var bVal bool = false
		fmt.Printf("我能访问 if 块中的局部变量 bVal: %t\n", bVal)
	}
	// fmt.Printf("我不能访问 if 块中的局部变量 bVal: %t\n", bVal) // #1
	return iCount
}
func main() {
	fmt.Printf("我也能访问全局变量 gCount: %d\n", gCount)
	iCanVisitGCount()
	// if err {
	// 	fmt.Println("iCanVisitGCount 执行异常: ", err)
	// }
	// fmt.Printf("我不能访问局部变量 iCount: %d\n", iCount) // #2
}

注释 #1, #2 执行结果

我也能访问全局变量 gCount: 100
我能访问全局变量 gCount: 100
我能访问局部变量 iCount: 1000
我能访问 if 块中的局部变量 bVal: false

放开 #1, #2 执行结果:

/var/folders/1n/xw58p9v90tn42m87q527fvgr0000gn/T/babel-lcR0GY/go-src-T6z2DN.go:14:67: undefined: bVal
/var/folders/1n/xw58p9v90tn42m87q527fvgr0000gn/T/babel-lcR0GY/go-src-T6z2DN.go:23:56: undefined: iCount
JavaScript

JavaScript 自从有了 es6 之后就有了块作用域的概念,比如:if 中使用 let 和 var 是 完全不一样的结果,var 在 if 声明的变量会被提升到函数顶部,等同于是函数级的局部变 量,在函数内都可以访问,而 let/const 则只能在 if 块作用域中访问

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
var gVal = 100 // 全局变量
function test() {
  var bVal = false
  if (true) {
    var iCount = 300
    let n = 1
    console.log('in if, iCount = ' + iCount)
    console.log('in if, n = ' + n)
  }
  console.log('out if, iCount = ' + iCount)
  console.log('out if, n = ' + typeof n)
  console.log('in test function, bVal = ' + bVal)
  console.log('in test function, gVal = ' + gVal)
}
test()
console.log('out test function, bVal = ' + typeof bVal)
console.log('out test function, gVal = ' + gVal)
in if, iCount = 300
in if, n = 1
out if, iCount = 300
out if, n = undefined
in test function, bVal = false
in test function, gVal = 100
out test function, bVal = undefined
out test function, gVal = 100
undefined

值类型(值,引用)

值类型:变量本身存储的就是该变量的具体值。

引用类型:变量本身存储的是一个指针(或地址),该指针指向了内存中一块区域,该区域中 存放的是具体的值。

最能体现值和引用类型区别的地方就是作为函数的参数了。

Golang
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import "fmt"
type Dog struct {
	Name string
}

func test(dog Dog, pDog *Dog) {
	fmt.Println(dog, pDog, *pDog)
	dog.Name = "value dog"
	pDog.Name = "pointer dog"
}

func main() {
	dog1 := Dog{Name: "dog1"}
	dog2 := Dog{Name: "dog2"}

	var a int = 100
	var pt *int // 未初始化时,指针是 nil
	fmt.Println("pt = ", pt)
	// * 星号是取值,这里是往 pt 指向的内存中在放一个 100 的整型值
	pt = &a
	fmt.Println("pt = ", pt)

	test(dog1, &dog2)
	fmt.Println(dog1, dog2)
}
pt =  <nil>
pt =  0xc00001a0b8
{dog1} &{dog2} {dog2}
{dog1} {pointer dog}

观查上面的结果会发现 dog1.Name 还是 "dog1", 而 dog2.Name 却发生了改变。

这就是值传递和引用传递的区别:

值传递:只是将原始数据的拷贝传递给了函数。

引用传递:是将原始数据在内存中的地址传递给了函数,因此修改 pDog 的值等于是修改了 这个指针指向的内存位置的值,所以让原始的 dog2 也发生了变化。

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

function test(dog, pDog, dogVal) {
  console.log(dog, pDog)
  dog.Name = 'value dog'
  pDog.Name = 'pointer dog'
  dogVal = 'dog value'
}

var dog1 = { Name: 'dog1' }
var dog2 = { Name: 'dog2' }
var dog3 = 'dog3'
test(dog1, dog2, dog3)
console.log(dog1, dog2, dog3)
{ Name: 'dog1' } { Name: 'dog2' }
{ Name: 'value dog' } { Name: 'pointer dog' } dog3
undefined

对于 JavaScript 而言是没有所谓指针的概念的,但对于对象类型都属于引用传递,普通类 型都是值的传递,如: dog1, dog2 是对象所以为引用传递值会被改变, dog3 是字符串为 普通类型是值传递所以函数内的操作对外部是没有任何影响的。

函数/方法

函数

JavaScript

function name(a, b) {}

Golang

func name(a, b, int) int {}

func name(a string, b int) (string, int) {], 返回两个值

func name(a, b int) (x, y int), 指定返回的变量,此时可不需要显式 return x, y, 如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import "fmt"

func test(a, b int) (x, y int) {
	// 这里不能用 := 因为 x, y 在函数声明中已经声明了
	// 即 (x, y int) 此时等于是声明了两 int 变量
	x = a + b
	y = a - b


// 必须要加上这个 return
	return
}

func main() {
	x, y := test(10, 2)
	fmt.Println(x, y)
}
12 8

方法

JavaScript

原型上的函数可视为方法,如:

1
2
3
4
function Test() {}
Test.prototype.test = () => console.log('test...')
const t = new Test()
t.test()
test...
undefined

Golang

Go 中的方法使用在模块中的结构体中,且和函数的定义非常相似,保需要在函数名前面加 上(t *Test)就会变成该结构体的方法,如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import "fmt"

type Test struct {
	Name string
}

func (t *Test) test() {
	t.Name = "test1"
}

func (t Test) test2() {
	t.Name = "test2"
}

func main() {
	t := Test{Name: "test..."}
	t.test()
	fmt.Println("test1:", t.Name)
	t.test2()
	fmt.Println("test2:", t.Name)
}
test1: test1
test2: test1

方法可能传递 Test 或者 *Test 指针类型,目的取决于是否需要修改原始数据的内容, Test 是值传递, *Test 是引用传递,如上面的测试两个都是输出的 "test1", 因为 t.test2() 修改的是 t 的拷贝而不是它本身所指向的内存空间。

为普通类型定义方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import (
	"fmt"
	"math"
)

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if (f < 0) {
		return float64(-f)
	}
	return float64(f)
}

func (f MyFloat) Tbs() float64 {
	return f.Abs()
}

func main() {
	f := MyFloat(-math.Sqrt2)
	fmt.Println(f.Abs())
	fmt.Println(f.Tbs())
}
1.4142135623730951
1.4142135623730951

WARNING

  1. 只能为同一个包中的类型声明一个方法(方法和类型必须在同一个包中)

  2. 不能为其它包里声明的类型声明方法(不能跨包)

基础类型

JavaScript 为弱类型语言,声明时不需要指定变量类型。

Golang 为强类型语言,声明时需要指定变量类型。

JavaScript

JavaScript 基础类型有: string, number, boolean

类型转换

1
2
3
console.log('string/boolean -> number: ', +"100", Number("100"), +false, +true, Number(false), Number(true))
console.log('string/number -> boolean: ', !!"0", Boolean("0"), !!"", Boolean(""), !!0, !!1, Boolean(0), Boolean(1))
console.log('boolean/number -> string: ', typeof ('' + 0), typeof String(0),typeof ('' + false), typeof String(false))
string/boolean -> number:  100 100 0 1 0 1
string/number -> boolean:  true true false false false true false true
boolean/number -> string:  string string string string
undefined

Golang

Golang 基础类型从大的分类来说有: string, bool, int, float, complex

其中 int 整型有: int, int8, int16, int32, int64

float 浮点型有: float32, float63

complex 复数型有: complex64, complex128, 且复数 = 一个对应的 int + 一个虚数

另外基础类型还有 rune, byte

runeint32 的一个别名类型,用来表示一个 UNICODE 字符的编码值。

byteuInt8 的一个别名类型。

测试各个类型的大小:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import (
	"fmt"
	"unsafe"
	"math/cmplx"
)

func main() {
	fmt.Println("> 输出字节数: ")
	var s string = "string"
	fmt.Println("string, len(string)         :",
		unsafe.Sizeof(s), len(s))

	var i int = -100
	var i8 int8 = 100
	var i16 int16 = 100
	var i32 int32 = 100
	// 64 位系统 int 类型输出是 8 = 8*8byte = 64bits
	fmt.Println("int, int8, int16, int32     :",
		unsafe.Sizeof(i), unsafe.Sizeof(i8), unsafe.Sizeof(i16), unsafe.Sizeof(i32))

    var ui uint = 100
	var ui8 uint8 = 100
	var ui16 uint16 = 100
	var ui32 uint32 = 100
	// 64 位系统 int 类型输出是 8 = 8*8byte = 64bits
	fmt.Println("uint, uint8, uint16, uint32 :",
		unsafe.Sizeof(ui), unsafe.Sizeof(ui8), unsafe.Sizeof(ui16), unsafe.Sizeof(ui32))


	var f32 float32 = 100.0
	var f64 float64 = 100.0
	fmt.Println("float32, float64            :",
		unsafe.Sizeof(f32), unsafe.Sizeof(f64))

	var r rune = 32
	fmt.Println("rune                        :", unsafe.Sizeof(r))

	var b byte = 0
	fmt.Println("byte                        :", unsafe.Sizeof(b))

	var c64 complex64 = 0
	var c128 complex128 = cmplx.Sqrt(-5 + 12i)
	fmt.Println("complex64, complex128       :", unsafe.Sizeof(c64), unsafe.Sizeof(c128), c64, c128)
}
> 输出字节数:
string, len(string)         : 16 6
int, int8, int16, int32     : 8 1 2 4
uint, uint8, uint16, uint32 : 8 1 2 4
float32, float64            : 4 8
rune                        : 4
byte                        : 1
complex64, complex128       : 8 16 (0+0i) (2+3i)

类型转换

1
2
3
4
5
6
7
8
9
import (
	"fmt"
	"unsafe"
)
func main() {
	var f32 float32 = 100.01
	var f64 float64 = 1002324324.0111111111
	fmt.Println(int(f32), int64(f64))
}
100 1002324324

控制流程

大多数语言中的控制流程的语法基本都差不多,可能各个语言为了区别开有一些特殊的地方, 比如 Golang 中对于条件就不需要 ()

for

JavaScript

1
2
3
for (let i = 0; i < 5; i++) {
  console.log(i)
}
0
1
2
3
4
undefined

省略初始化和递增语句,只保留条件语句(其实只不过是将两者提取到 for 之外去了,本质 并没变):

1
2
3
4
let i = 0
for (; i < 5; ) {
  console.log(i++)
}
0
1
2
3
4
undefined

Golang

1
2
3
4
5
6
import ("fmt")
func main() {
	for i := 0; i < 5; i++ {
		fmt.Println(i)
	}
}
i =  0
i =  1
i =  2
i =  3
i =  4

省略初始化和递增语句,只保留条件语句(其实只不过是将两者提取到 for 之外去了,本质 并没变):

1
2
3
4
5
6
7
8
import ("fmt")
func main() {
	i := 0
	for ; i < 5 ; {
		fmt.Println(i)
		i++
	}
}
0
1
2
3
4

range 在 for 循环中的使用:

1
2
3
4
5
6
7
8
9
import "fmt"

var pow = []int{1,2,4,8,16,32,64,128}

func main() {
	for i, v := range pow {
		fmt.Printf("2**%d = %d\n", i, v)
	}
}
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
2**4 = 16
2**5 = 32
2**6 = 64
2**7 = 128

还可以通过 _ 下划线来忽略掉 key 或者 value, 比如:

for _, val := range pow {...} 当只用到值的时候可以这样。

for key, _ := range pow {...} 当只使用 key 的时候。

如果只是使用索引,可以直拉使用一个就行 :

for key := range pow {...}

根据实际情况而定。

if, else if, else

JavaScript

1
2
3
4
5
6
7
8
const val = 100
if (val > 0) {
  console.log('val > 0')
} else if (val < 0 ) {
  console.log('val < 0')
} else {
  console.log('val === 0')
}
val > 0
undefined

Golang

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
import ("fmt")
func main() {
	val := 100
	if val > 0 {
		fmt.Println("val > 0")
	} else if val < 0 {
		fmt.Println("val < 0")
	} else {
		fmt.Println("val = 0")
	}
}
val > 0

还可以在 if 条件中加入其它更多的语句:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import (
	"fmt"
	"math"
)

func pow(x, n, lim float64) float64 {
	if v := math.Pow(x, n); v < lim {
		return v
	}
	return lim
}

func main() {
	fmt.Println(pow(3,2,10), pow(3,3,20))
}
9 20

switch…case

switch…case 在 golang 中不需要 break 来结束一个 case,它默认每个 case 都自结束 的。

JavaScript

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
let val = 0, timer
function printVal() {
  switch (val) {
    case 0:
    case 1:
      console.log(`val = ${val} in 0 ~ 1`)
      break
    case 2:
    case 3:
    case 4:
    case 5:
      console.log(`val = ${val} in 2 ~ 4`)
      break
    case 10:
      console.log('val = 10, end...')
      clearInterval(timer)
      break
    default:
      console.log(`val = ${val} in 6 ~ 9`)
      break
  }
  val++
}

timer = setInterval(printVal, 100)
val = 0 in 0 ~ 1
val = 1 in 0 ~ 1
val = 2 in 2 ~ 4
val = 3 in 2 ~ 4
val = 4 in 2 ~ 4
val = 5 in 2 ~ 4
val = 6 in 6 ~ 9
val = 7 in 6 ~ 9
val = 8 in 6 ~ 9
val = 9 in 6 ~ 9
val = 10, end...

Golang

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import (
	"fmt"
)

func test(val int) {
	// 可以包含简单语句
	switch i := val * val; i {
		case 0: // none
		case 1: fmt.Println("val =", val)
		case 4: fmt.Println("val =", val)
		case 9: fmt.Println("val =", val)
		default: fmt.Println("default:", val)
	}
}
func main() {
	test(0)
	test(1)
	test(2)
	test(3)
}
val = 1
val = 2
val = 3

可以看到 case 0 并没有像 JavaScript 那样走到了 case 1 执行,而是自己结束了,因此 没有任何输出。

while

JavaScript

1
2
3
4
5
6
7
8
9

let i = 0
while (i++ < 5) {
  console.log('while: ', i)
}

do {
  console.log('do...while', i++)
} while (i > 5 && i < 10)
while:  1
while:  2
while:  3
while:  4
while:  5
do...while 6
do...while 7
do...while 8
do...while 9
undefined

Golang

Golang 中没有 while 但是可以用 for 语句来代替。

1
2
3
4
5
6
7
8
9
import "fmt"

func main() {
	i := 0
	for i < 5 {
		fmt.Println(i)
		i++
	}
}
0
1
2
3
4

如果要无限循环可以连条件语句都省略掉: for {...}

异步或延迟

JavaScript

JavaScript 中延迟执行有:

setTimeout: 多少时间后执行回调函数

setInterval: 每间隔多少时间执行一次回调函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
let i = 0
setTimeout(() => {
  console.log('我在 setTimeout 中,1 秒后被执行, i = ', i, '当前时钟秒数:', new Date().getSeconds())
}, 1000)

let timer = setInterval(() => {
  console.log('我在 setInterval 中,每隔一秒会执行一次, i = ', i++, '当前时钟秒数:', new Date().getSeconds())
  if (i > 3) {
    clearInterval(timer)
  }
}, 1000)
我在 setTimeout 中,1 秒后被执行, i =  0 当前时钟秒数: 17
我在 setInterval 中,每隔一秒会执行一次, i =  0 当前时钟秒数: 17
我在 setInterval 中,每隔一秒会执行一次, i =  1 当前时钟秒数: 18
我在 setInterval 中,每隔一秒会执行一次, i =  2 当前时钟秒数: 19
我在 setInterval 中,每隔一秒会执行一次, i =  3 当前时钟秒数: 20

异步执行主要体现在 ajax 请求中,在 es6 之后新增了 Promise 以及后面的 aways…await 语法糖,让写异步代码更加流畅。

Promise: 一个 promise 实例会有三种状态(PENDING, FULFILLED, REJECTED)

且状态的变化只能是从 PENDINGFULFILLEDREJECTED 转变,且一旦转变之后就不可 逆。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
const p = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve(100)
  }, 2000)
})

console.log('p  ->', p)
var p1 = p.then(val => console.log(val))
console.log('p1 ->', p1)
var p2 = Promise.resolve(100)
console.log('p2 ->', p2)
var p3 = Promise.reject()
console.log('p3 ->', p3)
p  -> Promise { <pending> }
p1 -> Promise { <pending> }
p2 -> Promise { 100 }
p3 -> Promise { <rejected> undefined }
undefined100

Golang

defer

Go 中可以用 defer 来将代码延迟执行,它会在所在的函数返回之后被执行,所以常用来做 一些函数的清理工作。

Defer 相关文章: https://go.dev/blog/defer-panic-and-recover

1
2
3
4
5
6
import "fmt"

func main() {
	defer fmt.Println("world")
	fmt.Println("hello")
}
hello
world

defer 多条语句,它会将所有的函数调用放到一个调用栈中,当函数返回后,会将栈中的函 数按照 LIFO(后进先出) 顺序执行。

1
2
3
4
5
6
7
8
9
import "fmt"

func main() {
	fmt.Println("counting...")
	for i := 0; i < 5; i++ {
		defer fmt.Println(i)
	}
	fmt.Println("done")
 }
counting...
done
4
3
2
1
0

善后工作,比如:将一个文件的内容拷贝到另一个文件中,比如要将

1
cat /Users/simon/github/tmp/test/a.js
function test() {
  console.log('hello world')
}

拷贝到

1
cat /Users/simon/github/tmp/test/a12.js
function a12() {
  console.log('hello world')
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import (
	"fmt"
	"os"
	"io"
)
func CopyFile(dstName, srcName string) (written int64, err error) {
	src, err := os.Open(srcName)
	if err != nil {
		return
	}

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

	written, err = io.Copy(dst, src)
	dst.Close()
	src.Close()
	return
}

func main() {
	written, err := CopyFile("/Users/simon/github/tmp/test/a12.js", "/Users/simon/github/tmp/test/a.js")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("write content: ", written)
}
write content:  49

执行上面的代码之后再查看下 a12.js 内容:

1
cat /Users/simon/github/tmp/test/a12.js
function test() {
  console.log('hello world')
}

会发现拷贝成功了。

那如果 dst 文件 a12.js 就不存在的时候会导致异常在第一个 return 处就结束了 CopyFile 这样会导致 a.js 并没有被关闭 (src.Close()),这是不正常的操作的,也是不可取的,但 是错误又是不可避免的。

所以这里就可以用到 defer 的特性来完善 CopyFile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import (
	"fmt"
	"os"
	"io"
)
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)
}

func main() {
	written, err := CopyFile("/Users/simon/github/tmxxxp/test/a123.js", "/Users/simon/github/tmp/test/a.js")
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println("write content: ", written)
}
open /Users/simon/github/tmxxxp/test/a123.js: no such file or directory
write content:  0

经过上面的改造之后,虽然 tmxxxp 目录不存在,会导致第一个在创建 dstName 的时候报 错了,但是由于有 defer src.Close() 的存在, srcName 对应被打开的文件依然会在 CopyFile 退出之后被关闭。

defer 语句的行为是非常直观且是可预测的,它们的行为遵循以下几个原则:

  1. defer 后面的函数的参数会在 defer 语句执行的时候立即执行

    也就是说,函数参数的值就是当前 defer 语句所在位置的实时的值,比如:

    1
    2
    3
    4
    5
    6
    7
    
    import "fmt"
    func main() {
    i := 0
    defer fmt.Println(i) // i = 0
    i++
    return
    }
    0
    

    看到没,结果是 0, 而并不是 i++ 执行之后的 1 ,拿 JavaScript 来说,相当于使用 闭包的时候值被传递进了闭包而得到正确的实时值(JS 中典型的闭包问题)

  2. defer 有多条语句的时候会依据 FILO(先进后出)的顺序去执行,调用栈中的语句

    1
    2
    3
    4
    5
    6
    
    import "fmt"
    func main() {
    for i := 0; i < 3; i++ {
    defer fmt.Println(i)
    }
    }
    2
    1
    0
    
  3. defer 后面的语句或函数可以访问甚至修改命名返回值变量

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    import "fmt"
    func test() (i int) {
    defer func() { i++ }()
    return 1
    }
    
    func main() {
    i := test()
    fmt.Println(i)
    }
    2
    

    为什么结果是 2 ?

    首先可以尝试将 return 1 改成 return 会得到结果 1,这是因为 int 类型默认值是 0, 所以当 return 时其实是 return i 也就是 return 0

    当使用 return 1 时候也就是将 i 的值修改成了 1 再返回出去,所以在执行 defer 语 句之前 i 的值就是 1.

    然而 test() 中有个 defer func() { i++ }() 这个语句会让 i+1 最后 test() 返回值 变成了 2.

    从这个例子可以得出

    INFO

    defer 语句虽然是在函数返回之后执行,但是对于命名返回值的变量依旧有修改和访问 且会影响其结果的能力。

panic(恐慌)

Go 中内置的一个函数,可以用来停止控制流程的代码执行,并且产生恐慌。

当一个函数内调用了 panic 时, 这个函数的正常代码会停止执行,但是不会影响 defer 语句,也就是即使一个函数内调用了 panic,其中的 defer 语句依旧会被执行。

先看个简单的例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import "fmt"
func test(i int) {

	if (i > 0) {
		fmt.Println("in if", i)
		panic(100)
	}
	defer fmt.Println("Defer in test", i)
	fmt.Println("out if", i)
}
func main() {
	defer func() {
		r := recover()
		if r != nil {
			fmt.Println("Recovered in f", r)
		}
	}()
	test(1)
	// test(-1)
}
in if 1
Recovered in f 100

如上面的例子,如果将 main 中的 test(1) 移到 defer func() … 前面,则会和没有 defer 一样报错:

panic: 100

goroutine 1 [running]:
main.test(0x1)
	/var/folders/1n/xw58p9v90tn42m87q527fvgr0000gn/T/babel-lcR0GY/go-src-12TVSu.go:8 +0x249
main.main()
	/var/folders/1n/xw58p9v90tn42m87q527fvgr0000gn/T/babel-lcR0GY/go-src-12TVSu.go:14 +0x3b
exit status 2

但是只要放在 defer func() … 后面就可以正常执行得到下面的结果:

in if 1
Recovered in f 100

这实际并不是 defer 语句的问题而是 defer 中调用了 recover() 的缘故,假如不用 defer 直接调用 recover() 呢?

经过测试会发现结果和 test(1) 放在 defer func() … 一样会报同样的错误,这可能是 因为放在 defer 中会在函数返回之后拦截了这个函数因 panic 导致的错误。

TIP

是不是也就意味着,如果要使用 panic 而不会因为错误而中断程序执行,需要用到 defer + recover() 来捕获 panic 的状态信息(recover() 执行得到的结果,也是调用 panic 时传递给它的参数值)做进一步的处理。

有了上面的大概了解后,再来看下 go 官博中的示例进行分析

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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.

代码分析流程图:

/img/go/go-panic-01.svg

数据结构

这部分描述的是非基础类型,而是一些对象类型的数据结构。

JavaScript

Array

创建数组:

字面量: var a = [1,2,3]

构造函数: var a = new Array(3) 这会创建三个 HOLE 元素, map 等函数去遍历的时候 是遍历不到这些元素的。

1
2
3
4
5
6
7
8
var a = [1,2,3]
var b = new Array(3)
b.forEach((i, v) => console.log('forEach', i, v))
var b1 = b.map((i, v) => {
  console.log('map', i, v);
  return v
})
console.log('>', b1, b.length, Array.from(b));
> [ <3 empty items> ] 3 [ undefined, undefined, undefined ]
undefined

可以看到上面什么什么输出都没有。

可以用 Array.from(new Array(3)) 让创建的数组元素可以被遍历出来,只不过值是 undefined

Golang

Pointers(指针类型)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import "fmt"
func main() {
	i, j := 42, 2781

	p := &i // 指向 i 的一个指针

	fmt.Println(*p) // *p 是取 p 指针指向的内存中的数据值
	,*p = 21 // 给 p 指向的内存赋值,这里其实就是改变 i 的值
	fmt.Println(i)

	p = &j
	,*p = *p / 37
	fmt.Println(j)
}
42
21
75

Struct(结构体类型)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import "fmt"

type Animal struct {
	Name string
	Age int
}

func main() {
	cat := Animal{Name: "猫", Age: 100}
	dog := Animal{"狗", 101}

	fmt.Println("1.", cat, dog)

	// 结构体指针
	pCat := &cat
	var pDog *Animal = &dog

	pCat.Age = 200
	pDog.Age = 201

	fmt.Println("2.", cat, dog)
	fmt.Println(pCat, pDog, *pCat, *pDog)
}
1. {猫 100} {狗 101}
2. {猫 200} {狗 201}
&{猫 200} &{狗 201} {猫 200} {狗 201}

结构体字面量:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import "fmt"

type Vertex struct {
	X, Y int
}

var (
	v1 = Vertex{1, 2}
	v2 = Vertex{X: 1} // Y:0 int 类型默认值
	v3 = Vertex{} // X:0, Y:0, int 类型默认值
	p = &Vertex{1, 2} // Vertex 结构体指针
)

func main() {
	fmt.Println(v1, p, v2, v3)
}
{1 2} &{1 2} {1 0} {0 0}

Array(数组)

Go 中的数组和 JavaScript 不一样,它声明的时候必须指定长度,且一旦确定了长度之后 就不能再变化。

即: 不能添加也不能删除元素。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import "fmt"

func main() {
	var a [2]string
	a[0] = "hello"
	a[1] = "world"
	fmt.Println(a[0], a[1])
	fmt.Println(a)

	primes := [6]int{2,3,5,7,11,13}
	fmt.Println(primes)
}
hello world
[hello world]
[2 3 5 7 11 13]

Slice(切片)

切片和数组类似,但是它支持元素的添加和删除。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import "fmt"

func main() {
	names := [4]string{
		"张三",
		"李四",
		"王五",
		"赵六",
	}

	fmt.Println(names)

	a := names[0:2]
	b := names[1:3]
	fmt.Println(a, b)

	b[0] = "XXX"
	fmt.Println(a, b)
	fmt.Println(names)
}

[张三 李四 王五 赵六]
[张三 李四] [李四 王五]
[张三 XXX] [XXX 王五]
[张三 XXX 王五 赵六]

要理解切片,需要理解“切片只是对数组的一种描述”。含义是,一个切片创建之后,它只不 是由 (一个指针 ptr + len int + cap int) 组成的一个结构, ptr 指向的是真实的数组, len 是这个切片所代表的区间在源数组中数组元素的个数, cap 则是这个切片指向数组的 起始位置开始计算到数组末位这中间元素的个数。

一个切片结构:

/img/go/tests/slice-struct.png

切片初始状态结构:

/img/go/tests/slice-1.png

切片字面量
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import "fmt"
func main() {
	q := []int{2,3,5,7,11,13}
	fmt.Println(q)

	r := []bool{true, false, true, false}
	fmt.Println(r)

	s := []struct{
		i int
		b bool
	}{
		{2, true},
		{3, false},
		{5, true},
		{7, true},
		{11, false},
		{13, true},
	}
	fmt.Println(s)
}
[2 3 5 7 11 13]
[true false true false]
[{2 true} {3 false} {5 true} {7 true} {11 false} {13 true}]
切片索引(arr[start:end])

start 默认是 0, end 默认是数组或切片的长度(cap(s)),切片时结果

不含 end 索引位置的值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import "fmt"
func main() {
	s := []int{2,3,5,7,11,13}

	a := s[1:4]
	fmt.Println(a)

	b := s[:2]
	fmt.Println(b)

	c := s[1:]
	fmt.Println(c)

	d := s[:]
	fmt.Println(d)

	a[0] = 22
	fmt.Println(s)

	b[0] = 33
	fmt.Println(s)

	c[0] = 44
	fmt.Println(s)

	d[0] = 55
	fmt.Println(s)
}
[3 5 7]
[2 3]
[3 5 7 11 13]
[2 3 5 7 11 13]
[2 22 5 7 11 13]
[33 22 5 7 11 13]
[33 44 5 7 11 13]
[55 44 5 7 11 13]
切片长度和容量

长度是指该切片所指区域内元素的个数,而切片的容量是从该切片第一个元素在源数组中的 起始位置开始计算到源数组结束位置这中间能容纳元素的个数。

如(s = s[2:4])下图中深色区域代表的是 2:4 这个区间

/img/go/tests/slice-2.png

len(s) = 2 表示深色的哪两个块个数。

cap(s) = 3 表示从第一个深色块位置开始数,一直到最后一个块(非深色),所以是 3 个。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import "fmt"

func main() {
	s := []int{2,3,5,7,11,13}
	printSlice(s)

	// 空的切片
	s = s[:0]
	printSlice(s)

	// 扩展长度
	s = s[:4]
	printSlice(s)

	// 切掉开头两个元素
	s = s[2:]
	printSlice(s)

	s = s[0:]
	printSlice(s)

	// 空 Slice
	var s1 []int
	printSlice(s1)
	fmt.Println("s1 = nil,", s1 == nil)
}

func printSlice(s []int) {
	fmt.Printf("len=%d, cap=%d, addr=%p, %v\n", len(s), cap(s), &s, s)
}
len=6, cap=6, addr=0xc00000c0a0, [2 3 5 7 11 13]
len=0, cap=6, addr=0xc00000c0e0, []
len=4, cap=6, addr=0xc00000c120, [2 3 5 7]
len=2, cap=4, addr=0xc00000c160, [5 7]
len=2, cap=4, addr=0xc00000c1a0, [5 7]
len=0, cap=0, addr=0xc00000c1e0, []
s1 = nil, true
make([]int, 5[, cap]) 创建切片
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
import "fmt"

func main() {
	// len = 5, cap = 5
	a := make([]int, 5)
	printSlice("a", a)

	// len = 0, cap = 5
	b := make([]int, 0, 5)
	printSlice("b", b)

	c := b[:2]
	printSlice("c", c)

	d := c[2:5]
	printSlice("d", d)
}

func printSlice(s string, x []int) {
	fmt.Printf("%s: len=%d, cap=%d, addr=%p, %v\n", s, len(x), cap(x), &x, x)
}
a: len=5, cap=5, addr=0xc0000a6040, [0 0 0 0 0]
b: len=0, cap=5, addr=0xc0000a6080, []
c: len=2, cap=5, addr=0xc0000a60c0, [0 0]
d: len=3, cap=3, addr=0xc0000a6100, [0 0 0]
多维切片

切片的元素也可以是切片或其它任意结构。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import (
	"fmt"
	"strings"
)

func main() {
	board := [][]string{
		[]string{"_", "_", "_", "_"},
		[]string{"_", "_", "_", "_"},
		[]string{"_", "_", "_", "_"},
		[]string{"_", "_", "_", "_"},
	}

	board[0][0] = "X"
	board[2][2] = "O"
	board[1][2] = "X"
	board[1][0] = "O"
	board[0][2] = "X"

	for i := 0; i < len(board); i++ {
		fmt.Printf("%s\n", strings.Join(board[i], " "))
	}
}
X _ X _
O _ X _
_ _ O _
_ _ _ _
切片拷贝(copy(dst, src []T) int)

切片拷贝常用来对原有的切片进行扩展(不允许访问索引 <0> cap(s) 的元素,这会导 致溢出)。

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

func main() {
	p := []byte{2,3,5}
	p = AppendByte(p, 7, 11, 13)
	fmt.Printf("len = %d, cap = %d, addr = %p, %v", len(p), cap(p), &p, p)
}

func AppendByte(s []byte, data ...byte) []byte {
	m := len(s)
	n := m + len(data)
	// 容量不够了,创建新的切片去容纳
	if n > cap(s) {
		s1 := make([]byte, (n + 1) * 2)
		copy(s1, s)
		s = s1
	}
	// 重新设置 s 的指向(源 s 的长度 + 将要追加的数据的个数)
	s = s[0:n]
	// 将新元素追加到源切片末尾元素后面
	copy(s[m:n], data)
	// 返回新的切片
	return s
}
len = 6, cap = 14, addr = 0xc00000c0a0, [2 3 5 7 11 13]

使用 copy 不免有些繁琐,可以用下一节的 append 来代替, append 可以同时追加多个元 素,并且会根据切片的容量去自动扩展,如果容量不足会自动创建新的数组来容量更多的元 素,而不用像上面一样去和技管理。

但是并且不是说 copy 就不能用,要结合实际的场景去判断,比如:在一个函数中要去读取 一个文件,并返回这个文件中的某些部分,如果直接返回读取的结果可能会造成内存的浪费。

如:

1
2
3
4
5
var digitRegexp = regexp.MustCompile("[0-9]+")
func FindDigits(filename string) []byte {
	b, _ := ioutil.ReadFile(filename)
	return digitRegexp.Find(b)
}

上面的 FindDigits 就有这个问题,读取文件内容到 b,这个变量 b 会一直存在于内存中 不得释放而它指向的是整个文件的内容,如果文件很大这就会造成很大的内存浪费,因为函 数的返回值有引用到它,这明显是不合理的。

可以将其进一步优化,在返回之前将查询到的结果拷贝出来,然后返回结果部分返回,这样 b 会在函数返回之后被释放。

1
2
3
4
5
6
7
8
func CopyDigits(filename string) []byte {
	b, _ := ioutil.ReadFile(filename)
	b = digitRegexp.Find(b)
	c := make([]byte, len(b))
	// 或者用 append(c, b...)
	copy(c, b)
	return c
}

这样被持久的内存空间就变成了范围较小的变量 c 而不是整个文件 b, 在文件很的情况下 可以大大的节约内存。

切片扩展(append(s []T, vs ...T) []T)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import "fmt"

func main() {
	var s []int

	printSlice(s)

	// 在 nil 切片上追加元素
	s = append(s, 0)
	printSlice(s)

	// 继续添加
	s = append(s, 1)
	printSlice(s)

	// 一次加多个
	s = append(s, 2, 3, 4)
	printSlice(s)

	// 用展开符号直接添加一个 slice
	s1 := []int{10, 11, 12}
	s = append(s, s1...)
	printSlice(s)
}

func printSlice(s []int) {
	fmt.Printf("len=%d, cap=%d, addr=%p, %v\n", len(s), cap(s), &s, s)
}
len=0, cap=0, addr=0xc00000c0a0, []
len=1, cap=1, addr=0xc00000c0c0, [0]
len=2, cap=2, addr=0xc00000c100, [0 1]
len=5, cap=6, addr=0xc00000c140, [0 1 2 3 4]
len=8, cap=12, addr=0xc00000c180, [0 1 2 3 4 10 11 12]

Map(映射)

一组 key-value 的键值对的集合。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import "fmt"

type Vertex struct {
	Lat, Long float64
}

// map[keytype]valuetype
var m map[string]Vertex

func main() {
	m = make(map[string]Vertex)
	m["Bell Labs"] = Vertex{
		40.68433, -74.39967,
	}
	fmt.Println(m, m["Bell Labs"])
}
map[Bell Labs:{40.68433 -74.39967}] {40.68433 -74.39967}
字面量表示

Map 的字面量表达式和结构体差不多,只不过它必须要指定 key ,而不能像结构体一样可 以省略。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import "fmt"

type Vertex struct {
	Lat, Long float64
}

// 声明同时进行初始化
var m = map[string]Vertex{
	"Bell Labs": Vertex{
		40.68433, -74.39967,
	},
	"Google": Vertex{
		37.42202, -122.08408,
	},
}

func main() {
	fmt.Println(m)
}
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]

省略初始化时值中的 Vertex:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import "fmt"

type Vertex struct {
	Lat, Long float64
}

// 声明同时进行初始化
var m = map[string]Vertex{
	"Bell Labs": { 40.68433, -74.39967, },
	"Google": { 37.42202, -122.08408, },
}

func main() {
	fmt.Println(m)
}
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]
添加或更新

通过 m[key] = value 来添加或更新已经存在的(key)的值。

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

func main() {
	m := make(map[string]int)

	// 添加
	m["a"] = 42
	fmt.Println("add: ", m["a"])

	// 更新
	m["a"] = 48
	fmt.Println("update: ", m["a"])

	// 取值 + 检查存在与否
	va, oka := m["a"]
	fmt.Println("a present? ", oka, ", value = ", va)

	// 删除
	delete(m, "a")
	fmt.Println("delete: ", m["a"])

	vb, okb := m["b"]
	fmt.Println("b present? ", okb, ", value = ", vb)
}
add:  42
update:  48
a present?  true , value =  48
delete:  0
b present?  false , value =  0

Function(函数)

函数作为另一个函数的参数传递。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import (
	"fmt"
	"math"
)

func compute(fn func(float64, float64) float64) float64 {
	return fn(3, 4)
}

func main() {
	hypot := func(x, y float64) float64 {
		return math.Sqrt(x * x + y*y)
	}
	fmt.Println(hypot(5, 12))
	fmt.Println(compute(hypot))
	fmt.Println(compute(math.Pow))
}
13
5
81

函数闭包(func closure)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import "fmt"

func adder() func(int) int {
	sum := 0
	return func(x int) int {
		sum += x
		return sum
	}
}

func main() {
	pos, neg := adder(), adder()
	for i := 0; i < 10; i++ {
		fmt.Println(pos(i), neg(-2*i))
	}
}
0 0
1 -2
3 -6
6 -12
10 -20
15 -30
21 -42
28 -56
36 -72
45 -90

闭包实例(Fibonacci):

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

func fibonacci() func(int) int {
	var f, s int = 0, 0
	return func(i int) int {
		if i == 0 {
			return 0
		} else if i == 1 || i == 2 {
			f = 1
			s = 1
			return f
		} else {
			f, s = s, f + s
			return s
		}
	}
}

func main() {
	f := fibonacci()
	for i :=0; i < 10; i++ {
		fmt.Println(f(i))
	}
}
0
1
1
2
3
5
8
13
21
34

Interface(接口)

接口:是一组方法声明的集合,只要结构体实现了接口中的方法,就说明这个结构体实现了 这个接口。

注意:实现方法时,区分接受参数指针类型和非指针类型。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
type Abser interface {
	Abs() float64
}
type Vertex struct {
	X, Y float64
}

func (v *Vertex) Abs() float64 {
	return v.X * v.Y
}

var v = Vertex{1.5, 2.5}
var a = v // 结构体变量
var b = &v // 结构体指针

a.Abs() // error
b.Abs() // ok

上面的使用当中 a.Abs() 会报错,因为在实现 Abs() 的时候接受者参数是一个指针,而不 是普通的结构体变量,所以只能用 *Vertex 去调用 Abs() ,如同: b.Abs()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import (
	"fmt"
	"math"
)

type Abser interface {
	Abs() float64
}

func main() {
	var a Abser
	f := MyFloat(-math.Sqrt2)
	v := Vertex{3, 4}

	a = f 	// a MyFloat 实现了 Abser
	fmt.Println(a.Abs())

	a = &v // a *Vertex 实现了 Abser
	fmt.Println(a.Abs())
}

type MyFloat float64

func (f MyFloat) Abs() float64 {
	if (f < 0) {
		return float64(-f)
	}
	return float64(f)
}

type Vertex struct {
	X,Y float64
}

func (v *Vertex) Abs() float64 {
	return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
1.4142135623730951
5
接口类型

接口的实际类型,是根据实现它的结构类型而定,如果实现它的类型是什么它的类型就是什 么,比如下面的 main.F*main.T

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

type I interface {
	M()
}

type T struct {
	S string
}

func (t *T)M() {
	fmt.Println(t.S)
}

type F float64

func (f F)M() {
	fmt.Println(f)
}

func main() {
	var i I

	i = &T{"Hello"}
	describe(i)
	i.M()

	i = F(math.Pi)
	describe(i)
	i.M()
}

func describe(i I) {
	fmt.Printf("(%v, %T)\n", i, i)
}
(&{Hello}, *main.T)
Hello
(3.141592653589793, main.F)
3.141592653589793
空接口

空接口:没有任何方法声明的接口,一般用于未知类型的情况。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import "fmt"

func main() {
	var i interface {}

	i = 42
	describe(i)

	i = "hello"
	describe(i)
}

func describe(i interface{}) {
	fmt.Printf("(%v, %T)\n", i, i)
}
(42, int)
(hello, string)
类型断言

断言格式: t := i.(T) , i 是一个接口,如: var i interface{}, T 是要判断的类型。

如果断言的时候发现接口中如果没有 T 类型,会触发 panic,如果包含这个类型的声明刚 返回这个类型在接口中的值。

1
2
3
4
5
6
7
8
import "fmt"

func main() {
	var i interface{} = "hello"

	s := i.(string)
	fmt.Println(s)
}
hello

断言失败的情况:

1
2
3
4
5
6
7
8
import "fmt"

func main() {
	var i interface{} = "hello"

	s := i.(int)
	fmt.Println(s)
}
panic: interface conversion: interface {} is string, not int

goroutine 1 [running]:

还可以使用 t, ok := i.(T) 两个值来接受断言的结果,如果断言成功返回 T 对应的值, ok = true, 如果断言失败 t 的值则是 T 的默认值, ok = false

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import "fmt"

func main() {
	var i interface{} = "hello"

	s, ok := i.(string)
	fmt.Println(s, ok)

	f, ok := i.(float64)
	fmt.Println(f, ok)

	f = i.(float64) // 这里会触发 panic
	fmt.Println(f)
}
hello true
0 false

所以,如果不想在断言失败的时候触发 panic, 请使用 t, ok := i.(T) 方式。

类型 Switches

使用接口类型判断结合 switch…case 语句可以用来同时检测一个接口可能存在的多种类 型情况。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import "fmt"

func do(i interface{}) {
	switch v := i.(type) {
		case int: fmt.Printf("Twice %v is %v\n", v, v*2)
		case string: fmt.Printf("%q is %v bytes long\n", v, len(v))
		default: fmt.Printf("未知类型")
	}
}

func main() {
	do(20)
	do("hello")
	do(true)
}
Twice 20 is 40
"hello" is 5 bytes long
未知类型

可以通过这种方式来让一个函数接受一个空的接口来接受类型的值,然后在函数体内根据传 入的值的类型做出相应的措施。

Stringer

通过实现这个接口来实现 print 时候的自定义输出。

这个接口在 fmt 包中。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import "fmt"

type Person struct {
	Name string
	Age int
}

func (p Person) String() string {
	return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}

func main() {
	a := Person{"A", 42}
	b := Person{"B", 30}
	fmt.Println(a, b)
}
A (42 years) B (30 years)
Errors
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import (
	"fmt"
	"time"
)

type MyError struct {
	When time.Time
	What string
}

func (e *MyError) Error() string {
	return fmt.Sprintf("at %v, %s", e.When, e.What)
}

func run() error {
	return &MyError{
		time.Now(),
		"it didn't work",
	}
}

func main() {
	if err := run(); err != nil {
		fmt.Println(err)
	}
}
at 2021-09-09 15:10:18.214524 +0800 CST m=+0.000156083, it didn't work
Reader

io 包中的 io.Reader 接口,很多标准库中都实现了这个接口,比如: files, network connections, compressors, ciphers 等等。

func (T) Read(b []byte) (n int, err error)

Read 会使用数据去填充 b 切片,并且返回填充的数据大小和错误信息,比如:数据流读取 结束时候会返回一个 io.EOF 错误。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import (
	"fmt"
	"io"
	"strings"
)

func main() {
	r := strings.NewReader("Hello, Reader!")

	b := make([]byte, 8)
	for {
		n, err := r.Read(b)
		fmt.Printf("n = %v, err = %v, b = %v\n", n, err, b)
		fmt.Printf("b[:n] = %q\n", b[:n])
		if err == io.EOF {
			break
		}
	}

}
n = 8, err = <nil>, b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6, err = <nil>, b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0, err = EOF, b = [101 97 100 101 114 33 32 82]
b[:n] = ""
Image

Package Image

Image 接口:

1
2
3
4
5
6
7
package image

type Image interface {
    ColorModel() color.Model
    Bounds() Rectangle
    At(x, y int) color.Color
}

测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
import (
	"fmt"
	"image"
)

func main() {
	m := image.NewRGBA(image.Rect(0, 0, 100, 100))
	fmt.Println(m.Bounds())
	fmt.Println(m.At(0, 0).RGBA())
}
(0,0)-(100,100)
0 0 0 0

高级特性

Golang

Goroutine(协程)

求素数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
import (
	"fmt"
	"time"
)

var primeCount int = 40
func putInt(intChan chan<- int) {
	for i := 1; i < primeCount; i++ {
		intChan<- i
	}
	fmt.Println("put done.")
	close(intChan)
}

func findPrime(intChan <-chan int, primeChan chan<- int, exitChan chan<- bool) {
	for {
		n, ok := <-intChan
		if !ok {
			break
		}
		fmt.Println(isPrime(n), n)
		if t := isPrime(n); t {
			primeChan<- n
		}
	}

	exitChan<- true
	fmt.Println("find prime")
}

func isPrime(n int) bool {
	for i := 2; i < n; i++ {
		if n % i == 0 {
			return false
		}
	}
	return true
}

var maxChan int = 4
func main() {
	intChan := make(chan int, primeCount)
	primeChan := make(chan int, primeCount)
	exitChan := make(chan bool, maxChan)
	go putInt(intChan)

	for i := 0; i < maxChan; i++ {
		go findPrime(intChan, primeChan, exitChan)
	}

	go func() {
		for i := 0; i < maxChan; i++ {
			<-exitChan
		}
		// close(exitChan)
		fmt.Println("exit")
	}()

	for {
		time.Sleep(time.Second * 1)
		n, ok := <-primeChan
		if !ok {
			fmt.Println("end")
		}
		fmt.Println("prime =", n, "ok =", ok)
	}
}

#+RESULTS: