Go学习——《Go语言程序设计》Chap1

开始

编辑、编译与运行

Go 语言的官方编译器被称为 gc,包括编译工具5g6g8g,链接工具5l6l8l,以及文档查看工具godoc,这些古怪的命名习惯源自 Plan 9 操作系统,例如用数字来表示处理器的架构(5 代表 ARM,6 代表包括 Intel 64 位处理器在内的 AMD64 架构,而 8 则代表 Intel 386)

Go 语言针对的处理单元是包而非文件,这意味着我们可以将包拆分成任意数量的文件。在 Go 编译器看来,如果所有这些文件的包声明都是一样的,那么它们就同样属于一个包,这跟把所有内容放在一个单一的文件里是一样的。

执行顺序:init()->main(),如果一个包里包含了一个或多个 init()函数,那么它们会在 main()函数之前被自动执行,而且 init()函数不能被显示调用。

:=操作符,在 Go 语言中叫做快速变量声明,这条语句同时声明并初始化了一个变量,也就是说我们不必声明一个具体类型的变量,因为 Go 语言可以从其初始化值中推导出其类型,但需要强调的是,Go 是强类型的语言,即便使用了快速变量声明,后续也只能将对应识别类型的值赋给该变量。

切片

1
var lowPrimes = []int{2,3,5,7,11,13,17,19}

循环

C、C++、Java 习惯的for row := 0; row< len; row++可以,同时也提供for ... range的语法,与 P ython 类似,更短更方便。(++在 Go 中只可用于语句而非表达式,更进一步,只可用做后缀操作符而非前缀操作府)

自定义类型及其方法

一些 7788

虽然 Go 语言支持面向对象变成,但它既没有类也没有继承(is-a 关系)这样的概念。但是 Go 语言支持创建自定义类型,而且很容易创建聚合(has-a 关系)结构。Go 语言也支持将其数据和行为完全分离,同时也支持鸭子类型(一种抽象机制)。

所有这些一起,提供了一种游离于类和继承之外的更加灵活强大的选择,但如果要从 Go 语言的面向对象特性中获益,习惯于传统方法的我们必须在概念上做一些重大调整。

Go 语言使用内置的基础类型如 bool、int 和 string 等类型来表示数据,或者使用 struct 来对基本类型进行聚合 (不同于 C++,Go 的 struct 不是伪类)。

接口也是一种类型,可以通过指定一组方法的方式定义。接口是抽象的,因此不可以实例化。如果某个具体类型实现了某个接口所有的方法,那么这个类型就被认为实现了该接口。

空接口(没有定义方法的接口)用interfae{}来表示,可以用来表示任意值(效果上相当于一个指向任意类型的指针,类似 Java 中的Object或者 C/C++中的void*)。

自定义类型的导入

一个 Go 语言程序或者包的导入语句会首先搜索GOPATH定义的路径,然后再搜索GOROOT定义的路径。

包导入路径使用 Unix 风格的“/”来声明。

1
2
3
4
import {
"fmt"
"stacker/stack"
}

自定义类型的定义使用

按照管理,该文件开始处生命包,然后导入使用的包

1
2
3
4
5
6
7
8
9
10
11
package stack
import "errors"

// 下面每个export的类型或者方法按照VScode golint的要求需要附上注释说明,以方法/类型名开头

// Stack is a type
type Stack []interface()

func (stack Stack) Len() int {
return len(stack)
}

函数和方法都用关键字func定义。但是,定义方法的时候,方法所作用的值的类型需写在func关键字之后方法名之前,并用圆括号包围起来(如上面代码段的stack Stack)。函数或方法名之后,则是小括号包围起来的参数列表,每个参数用逗号分隔(每个参数以variableName type这种形式命名,和 C/C++/Java 类型在前的形式有差别)。参数后面,可以直接是左大括号,或者一个单一返回之,也可以是一对圆括号包围起来的返回值列表之后再紧跟一个作大括号。

调用该方法的值命名,Go 术语为“接收器”,接收器命名与包名并不冲突,区分按值(对原接收器不作改变)、按地址、按引用传递。

Go 语言使用nil来表示空指针(以及空引用),与 C/C++中的 NULL 或 0,Java 中的 null,Objective-C 中的 nil 是等价的。

文件、映射和闭包

I/O 处理包

  • buffio包提供了带缓冲的 I/O 处理的功能,包括从 UTF-8 编码文件中读写字符串的能力。
  • io包提供了底层的 I/O 功能
  • io/ioutil包提供了一系列高级文件处理函数
  • regexp包提供了强大的正则表达式支持

panic 异常

在 Go 语言中,panic是一个运行时错误(很像其他语言中的异常,本书将panic直接翻译为“异常”)。我们可以使用内置的panic()函数来触发一个异常,还可以使用recover()函数来在其调用栈上阻止该异常的传播。理论上,Go 语言的panic/recover功能可以用于多用途的错误处理机制,但我们并不推荐这么用。更合理的错误处理方式是让函数或者方法返回一个error值作为其最后或者唯一的返回值(如果没错就返回nil),并让其他调用方式来检查所收到的错误值。panic/recover机制的目的是用来处理真正的异常(即不可预料的异常)而非常规错误。

并发简述

Go 语言的一个关键特性在于其充分利用现代计算机的多处理器和多核的功能,且无需给程序员带来太大负担。完全无需任何显式锁 🔓 就可写出许多并发程序(虽然 Go 语言也提供了锁原语以便在底层代码需要用到时使用,我们将在第 7 章中详细阐述)。

Go 语言有两个特性使得用它座并发编程非常轻松。

  • 无需继承什么“线程”(thread)类,就可创建 goroutine(实际上是非常轻量级的县城或者协程)。
  • 通道(channel)为 goroutine 之间提供了类型安全的单项或者双向通信,这也可以用来同步 goroutine。

示例:极坐标到笛卡尔坐标交互程序

引入的包

1
2
3
4
5
6
7
import (
"bufio"
"fmt"
"math"
"os"
"runtime"
)
  • math包提供了操作浮点数的数学函数
  • runtime包提供了一些运行时控制
1
2
3
4
5
6
7
8
9
type polar struct {
radius float64
θ float64
}

type cartesian struct {
x float64
y float64
}

聚合两个内置类型 float64 来构建自定义类型,Go 语言的结构体是一种能够用来保存(聚合或者嵌入)一个或者多个通道据字段的类型。

需要注意通道是,虽然这两个结构体恰好包含了完全相同的通道段类型,但它们仍属不同类型,两者之间也不能自通道地相互转换(这符合我们对强类型语言的印象)。通道

通道

基于 Unix 上管道思想被设计出来的,FIFO 队列行为,保序,通道内数据不可被删除。

通道创建

1
msessages := make(chan string, 10)

利用make()创建通道,语法为chan Type, num,表示通道接受的数据类型和缓冲区大小。

  • 向通道发送

    <-做二元运算符,左操作数必须是一个通道,右操作数必须是发往该通道的数据。I

    1
    messages <- "Leader"
  • 从通道接受

    <-做一元运算符,左操作数必须是一个通道,是一个接收器,一直阻塞知道获得一个可以返回的数据。

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
func main() {
questions := make(chan polar)
defer close(questions)
answers := createSolver(questions)
defer close(answers)
interact(questions, answers)
}

func createSolver(questions chan polar) chan cartesian {
answers := make(chan cartesian)
go func() {
for {
polarCoord := <-questions
θ := polarCoord.θ * math.Pi / 180.0 // degrees to radians
x := polarCoord.radius * math.Cos(θ)
y := polarCoord.radius * math.Sin(θ)
answers <- cartesian{x, y}
}
}()
return answers
}

func interact(questions chan polar, answers chan cartesian) {
reader := bufio.NewReader(os.Stdin)
fmt.Println(prompt)
for {
fmt.Printf("Radius and angle: ")
line, err := reader.ReadString('\n')
if err != nil {
break
}
var radius, θ float64
if _, err := fmt.Sscanf(line, "%f %f", &radius, &θ); err != nil {
fmt.Fprintln(os.Stderr, "invalid input")
continue
}
questions <- polar{radius, θ}
coord := <-answers
fmt.Printf(result, radius, θ, coord.x, coord.y)
}
fmt.Println()
}

原理解释:

用户输入了合法数字并已经以polar结构体的格式发送到questions通道,那么就会阻塞主goroutine,等待 answers 通道的响应。createSolver()函数额外创建的一个goroutine会阻塞等待questions通道接收到一个polar类型的数据,因此当我们发送polar数据后,这个goroutine将会执行计算,并将计算结果发送会answer通道,然后等待另一个问题的输入(在开始阻塞自身),而一旦interact()函数在answers通道上接收到cartesian,就不再阻塞,这样就会打印出相关信息。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!