Go语言学习笔记

Go语言学习笔记

参考(Ctrl c+v) https://www.kancloud.cn/kancloud/the-way-to-go/

当个学习笔记,只记录重点。

初始

平台与架构

Go 语言开发团队开发了适用于以下操作系统的编译器:

  • Linux
  • FreeBSD
  • Mac OS X(也称为 Darwin)

目前有2个版本的编译器:Go 原生编译器 gc 和非原生编译器 gccgo,这两款编译器都是在类 Unix 系统下工作 。其中,gc 版本的编译器已经被移植到 Windows 平台上,并集成在主要发行版中,你也可以通过安装 MinGW 从而在 Windows 平台下使用 gcc 编译器。这两个编译器都是以单通道的形式工作。

你可以获取以下平台上的 Go 1.4 源码和二进制文件:

  • Linux 2.6+:amd64、386 和 arm 架构
  • Mac OS X(Snow Leopard + Lion):amd64 和 386 架构
  • Windows 2000+:amd64 和 386 架构

Go 环境变量

  • $GOROOT 表示 Go 在你的电脑上的安装位置,它的值一般都是 $HOME/go,当然,你也可以安装在别的地方。
  • $GOARCH 表示目标机器的处理器架构,它的值可以是 386、amd64 或 arm。
  • $GOOS 表示目标机器的操作系统,它的值可以是 darwin、freebsd、linux 或 windows。
  • $GOBIN 表示编译器和链接器的安装位置,默认是 $GOROOT/bin,如果你使用的是 Go 1.0.3 及以后的版本,一般情况下你可以将它的值设置为空,Go 将会使用前面提到的默认值。

安装目录清单

  • /bin:包含可执行文件,如:编译器,Go 工具
  • /doc:包含示例程序,代码工具,本地文档等
  • /lib:包含文档模版
  • /misc:包含与支持 Go 编辑器有关的配置文件以及 cgo 的示例
  • /os_arch:包含标准库的包的对象文件(.a
  • /src:包含源代码构建脚本和标准库的包的完整源代码(Go 是一门开源语言)
  • /src/cmd:包含 Go 和 C 的编译器和命令行脚本

Go调试器

  1. 在合适的位置使用打印语句输出相关变量的值(print/printlnfmt.Print/fmt.Println/fmt.Printf)。
  2. fmt.Printf 中使用下面的说明符来打印有关变量的相关信息:
    • %+v 打印包括字段在内的实例的完整信息
    • %#v 打印包括字段和限定类型名称在内的实例的完整信息
    • %T 打印某个类型的完整说明
  3. 使用 panic 语句(第 13.2 节)来获取栈跟踪信息(直到 panic 时所有被调用函数的列表)。
  4. 使用关键字 defer 来跟踪代码执行过程(第 6.4 节)。

构建并运行 Go 程序

在大多数 IDE 中,每次构建程序之前都会自动调用源码格式化工具 gofmt 并保存格式化后的源文件。如果构建成功则不会输出任何信息,而当发生编译时错误时,则会指明源码中具体第几行出现了什么错误,如:a declared and not used。一般情况下,你可以双击 IDE 中的错误信息直接跳转到发生错误的那一行。

如果程序执行一切顺利并成功退出后,将会在控制台输出 Program exited with code 0

从 Go 1 版本开始,使用 Go 自带的更加方便的工具来构建应用程序:

  • go build 编译并安装自身包和依赖包
  • go install 安装自身包和依赖包

语言的核心结构与技术

基本结构和基本数据类型

hello world

1
2
3
4
5
6
7
8
9
package main

import (
"fmt"
)

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

包的概念

包是结构化代码的一种方式:每个程序都由包(通常简称为 pkg)的概念组成,可以使用自身的包或者从其它包中导入内容。

package main表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。

所有的包名都应该使用小写字母

注释

// 单行注释
/* xxxx */ 多行注释

函数

你可以在括号 () 中写入 0 个或多个函数的参数(使用逗号 , 分隔),每个参数的名称后面必须紧跟着该参数的类型。

1
func Sum(a, b int) int { return a + b }

类型

基本类型:intfloatboolstring

结构化的(复合的):structarrayslicemapchannel

结构化的类型没有真正的值,它使用nil作为默认值

类型转换

类型 B 的值 = 类型 B(类型 A 的值)

1
valueOfTypeB = typeB(valueOfTypeA)

常量

1
2
3
4
5
6
const beef, two, c = “meat”, 2, “veg”
const Monday, Tuesday, Wednesday, Thursday, Friday, Saturday = 1, 2, 3, 4, 5, 6
const (
Monday, Tuesday, Wednesday = 1, 2, 3
Thursday, Friday, Saturday = 4, 5, 6
)

变量

声明变量时将变量的 类型 放在变量的 名称之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var identifier type

多行根据变量的值来自动推断其类型
var (
a = 15
b = false
str = "Go says hello to the world!"
numShips = 50
city string
)

并行\同时赋值
a, b, c = 5, 7, "abc"

值交换
a, b = b, a

变量的 命名规则 遵循骆驼命名法,即首个单词小写,每个新单词的首字母大写,例如:numShipsstartDate

一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域。

如果一个变量在函数体外声明,则被认为是全局变量,可以在整个包、外部包(被导出后)使用,不管你声明在哪个源文件里或在哪个源文件里调用该变量。

函数体内声明的变量称之为局部变量,它们的作用域只在函数体内,参数和返回值变量也是局部变量。

打印

函数 fmt.Printfmt.Println 会自动使用格式化标识符 %v 对字符串进行格式化,两者都会在每个参数之间自动增加空格,而后者还会在字符串的最后加上一个换行符。例如:

1
2
3
fmt.Print("Hello:", 23)
将输出:
Hello: 23

init 函数

变量除了可以在全局声明中初始化,也可以在 init 函数中初始化。

不能够被人为调用,而是在每个包完成初始化后自动执行,并且执行优先级比 main 函数高。

一个源文件都可以包含且只包含一个 init 函数。初始化总是以单线程执行,并且按照包的依赖关系顺序执行。

init.go

1
2
3
4
5
6
7
8
9
package trans

import "math"

var Pi float64

func init() {
Pi = 4 * math.Atan(1) // init() function computes Pi
}

user_init.go 中导入了包 trans(在相同的路径中)并且使用到了变量 Pi

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

import (
"fmt"
"./trans"
)

var twoPi = 2 * trans.Pi

func main() {
fmt.Printf("2*Pi = %g\n", twoPi) // 2*Pi = 6.283185307179586
}

基本类型和运算符

布尔类型 bool:布尔型的值只可以是常量 true 或者 false

数字类型:整型 int 和浮点型 float

格式化说明符:格式化字符串里,%d 用于格式化整数(%x%X 用于格式化 16 进制表示的数字),%g 用于格式化浮点型(%f 输出浮点数,%e 输出科学计数表示法),%0d 用于规定输出定长的整数,其中开头的数字 0 是必须的。

数字值转换:进行a32bitInt = int32(a32Float) 的转换时,小数点后的数字将被丢弃。

复数:Go 拥有以下复数类型:

1
2
complex64 (32 位实数和虚数)
complex128 (64 位实数和虚数)

位运算:

一元运算符:按位补足 ^,位左移 <<,位右移 >>

二元运算符:按位与 &,按位或 |,按位异或 ^,位清除 &^

逻辑运算符:== , != , < , <= , > , >=

算术运算符:常见可用于整数和浮点数的二元运算符有 +-*/

运算符与优先级:

有些运算符拥有较高的优先级,二元运算符的运算方向均是从左至右。下表列出了所有运算符以及它们的优先级,由上至下代表优先级由高到低:

1
2
3
4
5
6
7
8
优先级   运算符
7 ^ !
6 * / % << >> & &^
5 + - | ^
4 == != < <= >= >
3 <-
2 &&
1 ||

当然,你可以通过使用括号来临时提升某个表达式的整体运算优先级。

类型别名:

1
type TZ int

字符类型:char.go

1
2
3
4
5
6
7
8
9
10
11
12
13
var ch int = '\u0041'
var ch2 int = '\u03B2'
var ch3 int = '\U00101234'
fmt.Printf("%d - %d - %d\n", ch, ch2, ch3) // integer
fmt.Printf("%c - %c - %c\n", ch, ch2, ch3) // character
fmt.Printf("%X - %X - %X\n", ch, ch2, ch3) // UTF-8 bytes
fmt.Printf("%U - %U - %U", ch, ch2, ch3) // UTF-8 code point

输出:
65 - 946 - 1053236
A - β - r
41 - 3B2 - 101234
U+0041 - U+03B2 - U+101234

字符串

string 类型的零值为长度为零的字符串,即空字符串 ""

一般的比较运算符(==!=<<=>=>)通过在内存中按字节比较来实现字符串的对比。你可以通过函数len() 来获取字符串所占的字节长度,例如:len(str)

字符串拼接符 +:两个字符串 s1s2 可以通过 s := s1 + s2s += "world" 拼接在一起。

  • 解释字符串:

    该类字符串使用双引号括起来,其中的相关的转义字符将被替换,这些转义字符包括:

    • \n:换行符
    • \r:回车符
    • \t:tab 键
    • \u\U:Unicode 字符
    • \\:反斜杠自身
  • 非解释字符串:

    该类字符串使用反引号括起来,支持换行,例如:

    1
    `This is a raw string \n` 中的 `\n\` 会被原样输出。

时间和日期

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
package main
import (
"fmt"
"time"
)

var week time.Duration
func main() {
t := time.Now()
fmt.Println(t) // e.g. Wed Dec 21 09:52:14 +0100 RST 2011
fmt.Printf("%02d.%02d.%4d\n", t.Day(), t.Month(), t.Year())
// 21.12.2011
t = time.Now().UTC()
fmt.Println(t) // Wed Dec 21 08:52:14 +0000 UTC 2011
fmt.Println(time.Now()) // Wed Dec 21 09:52:14 +0100 RST 2011
// calculating times:
week = 60 * 60 * 24 * 7 * 1e9 // must be in nanosec
week_from_now := t.Add(week)
fmt.Println(week_from_now) // Wed Dec 28 08:52:14 +0000 UTC 2011
// formatting times:
fmt.Println(t.Format(time.RFC822)) // 21 Dec 11 0852 UTC
fmt.Println(t.Format(time.ANSIC)) // Wed Dec 21 08:56:34 2011
fmt.Println(t.Format("02 Jan 2006 15:04")) // 21 Dec 2011 08:52
s := t.Format("20060102")
fmt.Println(t, "=>", s)
// Wed Dec 21 08:52:14 +0000 UTC 2011 => 20111221
}

指针

程序在内存中存储它的值,每个内存块(或字)有一个地址,通常用十六进制数表示,如:0x6b08200xf84001d7f0

一个指针变量可以指向任何一个值的内存地址 它指向那个值的内存地址,在 32 位机器上占用 4 个字节,在 64 位机器上占用 8 个字节,并且与它所指向的值的大小无关。

Go 语言的取地址符是 &,放到一个变量前使用就会返回相应变量的内存地址

指针类型前面加上*号(前缀)来获取指针所指向的内容,这里的 * 号是一个类型更改器。使用一个指针引用一个值被称为间接引用。

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

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

例:展示了分配一个新的值给 *p 并且更改这个变量自己的值(这里是一个字符串)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import "fmt"
func main() {
s := "good bye"
var p *string = &s
*p = "ciao"
fmt.Printf("Here is the pointer p: %p\n", p) // prints address
fmt.Printf("Here is the string *p: %s\n", *p) // prints string
fmt.Printf("Here is the string s: %s\n", s) // prints same string
}

输出:
Here is the pointer p: 0x2540820
Here is the string *p: ciao
Here is the string s: ciao

控制结构

if-else 结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
if condition1 {
// do something
} else if condition2 {
// do something else
}else {
// catch-all or default
}

判断一个字符串是否为空
if str == "" { ... }
if len(str) == 0 {...}

判断系统,分别提示
var prompt = "Enter a digit, e.g. 3 "+ "or %s to quit."

func init() {
if runtime.GOOS == "windows" {
prompt = fmt.Sprintf(prompt, "Ctrl+Z, Enter")
} else { //Unix-like
prompt = fmt.Sprintf(prompt, "Ctrl+D")
}
}


switch 结构

1
2
3
4
5
6
7
8
switch {
case condition1:
...
case condition2:
...
default:
...
}

for结构

基于计数器的迭代

1
2
3
4
5
6
7
8
9
package main

import "fmt"

func main() {
for i := 0; i < 5; i++ {
fmt.Printf("This is the %d iteration\n", i)
}
}

for-range 结构

它可以迭代任何一个集合(包括数组和 map),一般形式为:for ix, val := range coll { }

val 始终为集合中对应索引的值拷贝,因此它一般只具有只读性质,对它所做的任何修改都不会影响到集合中原有的值(译者注:如果 val 为指针,则会产生指针的拷贝,依旧可以修改集合中的原值

1
2
3
for pos, char := range str {
...
}

Break 与 continue

break 的作用范围为该语句出现后的最内部的结构,它可以被用于任何形式的 for 循环(计数器、条件判断等)。但在 switch 或 select 语句中,break 语句的作用结果是跳过整个代码块,执行后续的代码。

continue 忽略剩余的循环体而直接进入下一次循环的过程,但不是无条件执行下一次循环,执行之前依旧需要满足循环的判断条件。

标签与 goto

forswitchselect 语句都可以配合标签(label)形式的标识符使用,即某一行第一个以冒号(:)结尾的单词(gofmt 会将后续代码自动移至下一行)。

1
2
3
4
5
6
7
8
9
LABEL1:
for i := 0; i <= 5; i++ {
for j := 0; j <= 5; j++ {
if j == 4 {
continue LABEL1
}
fmt.Printf("i is: %d, and j is: %d\n", i, j)
}
}

函数(function)

函数参数与返回值

按值\引用传递

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func main() {
fmt.Printf("Multiply 2 * 5 * 6 = %d\n", MultiPly3Nums(2, 5, 6))
// var i1 int = MultiPly3Nums(2, 5, 6)
// fmt.Printf("MultiPly 2 * 5 * 6 = %d\n", i1)
}

func MultiPly3Nums(a int, b int, c int) int {
// var product int = a * b * c
// return product
return a * b * c
}

输出显示:

Multiply 2 * 5 * 6 = 60

命名的返回值

getX2AndX3getX2AndX3_2 两个函数演示了如何使用非命名返回值与命名返回值的特性。当需要返回多个非命名返回值时,需要使用 () 把它们括起来,比如 (int, int)

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

import "fmt"

var num int = 10
var numx2, numx3 int

func main() {
numx2, numx3 = getX2AndX3(num)
PrintValues() //输出:num = 10, 2x num = 20, 3x num = 30
numx2, numx3 = getX2AndX3_2(num)
PrintValues() //输出:num = 10, 2x num = 20, 3x num = 30
}

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
}

空白符

空白符用来匹配一些不需要的值,然后丢弃掉。ThreeValues 是拥有三个返回值的不需要任何参数的函数,在下面的例子中,我们将第一个与第三个返回值赋给了 i1f1。第二个返回值赋给了空白符 _,然后自动丢弃掉。

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

import "fmt"

func main() {
var i1 int
var f1 float32
i1, _, f1 = ThreeValues()
fmt.Printf("The int: %d, the float: %f \n", i1, f1) //输出:The int: 5, the float: 7.500000
}

func ThreeValues() (int, int, float32) {
return 5, 6, 7.5
}

改变外部变量

传递指针给函数不但可以节省内存(因为没有复制变量的值),而且赋予了函数直接修改外部变量的能力,所以被修改的变量不再需要使用 return 返回。如下的例子,reply 是一个指向 int 变量的指针,通过这个指针,我们在函数内修改了这个 int 变量的数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
"fmt"
)

// this function changes reply:
func Multiply(a, b int, reply *int) {
*reply = a * b
}

func main() {
n := 0
reply := &n
Multiply(10, 5, reply)
fmt.Println("Multiply:", *reply) // Multiply: 50
}

传递变长参数

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

示例函数和调用:

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

在 Greeting 函数中,变量 who 的值为 []string{"Joe", "Anna", "Eileen"}

如果参数被存储在一个数组 arr 中,则可以通过 arr... 的形式来传递参数调用变参函数。

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

import "fmt"

func main() {
x := Min(1, 3, 2, 0)
fmt.Printf("The minimum is: %d\n", x) // 输出:The minimum is: 0
arr := []int{7,9,3,5,1}
x = Min(arr...)
fmt.Printf("The minimum in the array arr is: %d", x) // 输出:The minimum in the array arr is: 1
}

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

defer 和追踪

https://www.kancloud.cn/kancloud/the-way-to-go/72476

关键字 defer 允许我们推迟到函数返回之前(或任意位置执行 return 语句之后)一刻才执行某个语句或函数(为什么要在返回之后才执行这些语句?因为 return 语句同样可以包含一些操作,而不是单纯地返回某个值)。

关键字 defer 的用法类似于面向对象编程语言 Java 和 C# 的 finally 语句块,它一般用于释放某些已分配的资源。

当有多个 defer 行为被注册时,它们会以逆序执行(类似栈,即后进先出)

可以使用 defer 语句实现代码追踪

内置函数

名称 说明
close 用于管道通信
len、cap len 用于返回某个类型的长度或数量(字符串、数组、切片、map 和管道);cap 是容量的意思,用于返回某个类型的最大容量(只能用于切片和 map)
new、make new 和 make 均是用于分配内存:new 用于值类型和用户定义的类型,如自定义结构,make 用户内置引用类型(切片、map 和管道)。它们的用法就像是函数,但是将类型作为参数:new(type)、make(type)。new(T) 分配类型 T 的零值并返回其地址,也就是指向类型 T 的指针(详见第 10.1 节)。它也可以被用于基本类型:v := new(int)。make(T) 返回类型 T 的初始化之后的值,因此它比 new 进行更多的工作(详见第 7.2.3/4 节、第 8.1.1 节和第 14.2.1 节)new() 是一个函数,不要忘记它的括号
copy、append 用于复制和连接切片
panic、recover 两者均用于错误处理机制
print、println 底层打印函数(详见第 4.2 节),在部署环境中建议使用 fmt 包
complex、real imag 用于创建和操作复数(详见第 4.5.2.2 节

递归函数

当一个函数在其函数体内调用自身,则称之为递归。最经典的例子便是计算斐波那契数列,即每个数均为前两个数之和。数列如下所示:

1
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, …
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
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
}

输出:

fibonacci(0) is: 1
fibonacci(1) is: 1
fibonacci(2) is: 2
fibonacci(3) is: 3
fibonacci(4) is: 5
fibonacci(5) is: 8
fibonacci(6) is: 13
fibonacci(7) is: 21
fibonacci(8) is: 34
fibonacci(9) is: 55
fibonacci(10) is: 89

将函数作为参数

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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

闭包

当我们不希望给函数起名字的时候,可以使用匿名函数,例如: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)

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

https://www.kancloud.cn/kancloud/the-way-to-go/72481

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() {
// make an Add2 function, give it a name p2, and call it:
p2 := Add2()
fmt.Printf("Call Add2 for 3 gives: %v\n", p2(3))
// make a special Adder function, a gets value 3:
TwoAdder := Adder(2)
fmt.Printf("The result is: %v\n", TwoAdder(3))
}

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
}
}

输出:

Call Add2 for 3 gives: 5
The result is: 5

使用闭包调试

能够准确地知道哪个文件中的具体哪个函数正在执行,对于调试是十分有帮助的。您可以使用 runtimelog 包中的特殊函数来实现这样的功能。包runtime 中的函数 Caller() 提供了相应的信息,因此可以在需要的时候实现一个 where() 闭包函数来打印函数执行的位置:

1
2
3
4
5
6
7
8
9
where := func() {
_, file, line, _ := runtime.Caller(1)
log.Printf("%s:%d", file, line)
}
where()
// some code
where()
// some more code
where()

您也可以设置 log 包中的 flag 参数来实现:

1
2
log.SetFlags(log.Llongfile)
log.Print("")

或使用一个更加简短版本的 where 函数:

1
2
3
4
5
6
7
8
var where = log.Print
func func1() {
where()
... some code
where()
... some code
where()
}

计算函数执行时间

在计算开始之前设置一个起始时候,再由计算结束时的结束时间,最后取出它们的差值,就是这个计算所消耗的时间。想要实现这样的做法,可以使用 time 包中的 Now()Sub 函数:

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

通过内存缓存来提升性能

进行大量的计算时,提升性能最直接有效的一种方式就是避免重复计算。通过在内存中缓存和重复利用相同计算的结果,称之为内存缓存。最明显的例子就是生成斐波那契数列的程序

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
package main

import (
"fmt"
"time"
)

const LIM = 41

var fibs [LIM]uint64

func main() {
var result uint64 = 0
start := time.Now()
for i := 0; i < LIM; i++ {
result = fibonacci(i)
fmt.Printf("fibonacci(%d) is: %d\n", i, result)
}
end := time.Now()
delta := end.Sub(start)
fmt.Printf("longCalculation took this amount of time: %s\n", delta)
}
func fibonacci(n int) (res uint64) {
// memoization: check if fibonacci(n) is already known in array:
if fibs[n] != 0 {
res = fibs[n]
return
}
if n <= 1 {
res = 1
} else {
res = fibonacci(n-1) + fibonacci(n-2)
}
fibs[n] = res
return
}

数组与切片

声明和初始化

切片

For-range 结构

切片重组(reslice)

切片的复制与追加

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

Map

声明、初始化和 make

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

for-range 的配套用法

map 类型的切片

map 的排序

将 map 的键值对调

包(package)

标准库概述

regexp 包

锁和 sync 包

精密计算和 big 包

自定义包和可见性

为自定义包使用 godoc

使用 go install 安装自定义包

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

通过 Git 打包和安装

Go 的外部包和项目

结构(struct)与方法(method)

结构体定义

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

使用自定义包中的结构体

带标签的结构体

匿名字段和内嵌结构体

方法

垃圾回收和 SetFinalizer

接口(interface)与反射(reflection)

接口是什么

接口嵌套接口

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

类型判断:type-switch

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

使用方法集与接口

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

第二个例子:读和写

空接口

反射包

Go 高级编程

读写数据

读取用户的输入

文件读写

文件拷贝

从命令行读取参数

用buffer读取文件

用切片读写文件

用 defer 关闭文件

使用接口的实际例子:fmt.Fprintf

Json 数据格式

XML 数据格式

用 Gob 传输数据

Go 中的密码学

错误处理与测试

错误处理

运行时异常和 panic

从 panic 中恢复(Recover)

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

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

启动外部命令和程序

Go 中的单元测试和基准测试

测试的具体例子

用(测试数据)表驱动测试

性能调试:分析并优化

协程(goroutine)与通道(channel)

并发、并行和协程

使用通道进行协程间通信

协程同步:关闭通道-对阻塞的通道进行测试

使用 select 切换协程

通道,超时和计时器(Ticker)

协程和恢复(recover)

网络、模版与网页应用

tcp服务器

一个简单的web服务器

访问并读取页面数据

写一个简单的网页应用

实际应用

常见的陷阱与错误

误用短声明导致变量覆盖

误用字符串

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

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

使用指针指向接口类型

使用值类型时误用指针

误用协程和通道

闭包和协程的使用

糟糕的错误处理

模式

关于逗号ok模式

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

字符串

数组和切片

映射

结构体

接口

函数

文件

协程(goroutine)与通道(channel)

网络和网页应用

其他

出于性能考虑的最佳实践和建议

作者

Se7en

发布于

2021-09-27

更新于

2021-10-08

许可协议

评论