TranslateProject/translated/tech/20181224 An Introduction to Go.md

275 lines
9.5 KiB
Markdown
Raw Normal View History

2019-01-03 13:51:22 +08:00
[#]: collector: (lujun9972)
[#]: translator: (LazyWolfLin)
[#]: reviewer: ( )
[#]: publisher: ( )
[#]: url: ( )
[#]: subject: (An Introduction to Go)
[#]: via: (https://blog.jak-linux.org/2018/12/24/introduction-to-go/)
[#]: author: (Julian Andres Klode https://blog.jak-linux.org/)
Go 简介
======
(以下内容是我的硕士论文的摘录,几乎整个 2.1 章节,向具有 CS 背景的人快速介绍 Go
2019-01-07 13:33:15 +08:00
Go 是一门用于并发编程的命令式编程语言,它主要由创造者 Google 进行开发,最初主要由 Robert Griesemer、Rob Pike 和 Ken Thompson开发。这门语言的设计起始于 2017 年,并在 2019 年推出最初版本;而第一个稳定版本是 2012 年发布的 1.0。
2019-01-03 13:51:22 +08:00
2019-01-08 13:32:56 +08:00
Go 有 C 风格语法没有预处理器垃圾回收机制而且类似它在贝尔实验室里被开发出来的前辈们Newsqueak (Rob Pike)、Alef (Phil Winterbottom) 和 Inferno (Pike, Ritchie, et al.),使用所谓的 goroutines 和 channels一种基于 Hoare 的“通信顺序进程”理论的协程)提供内建的并发支持。
2019-01-03 13:51:22 +08:00
2019-01-08 13:32:56 +08:00
Go 程序以包的形式组织。包本质是一个包含 Go 文件的文件夹。包内的所有文件共享相同的命名空间,而包内的符号有两种可见行:以大写字母开头的符号对于其他包是可见,而其他符号则是该包私有的:
2019-01-03 13:51:22 +08:00
```
func PublicFunction() {
fmt.Println("Hello world")
}
func privateFunction() {
fmt.Println("Hello package")
}
```
### 类型
2019-01-08 13:32:56 +08:00
Go 有一个相当简单的类型系统:没有子类型(但有类型转换),没有泛型,没有多态函数,只有一些基本的类型:
2019-01-03 13:51:22 +08:00
2019-01-08 13:32:56 +08:00
1. 基本类型:`int`、`int64`、`int8`、`uint`、`float32`、`float64` 等。
2019-01-03 13:51:22 +08:00
2. `struct`
2019-01-08 13:32:56 +08:00
3. `interface` \- 一类方法
2019-01-03 13:51:22 +08:00
2019-01-08 13:32:56 +08:00
4. `map[K, V]` \- 从键类型到值类型的映射
2019-01-03 13:51:22 +08:00
2019-01-08 13:32:56 +08:00
5. `[number]Type` \- 一些元素类型组成的数组
2019-01-03 13:51:22 +08:00
2019-01-08 13:32:56 +08:00
6. `[]Type` \- 某种类型的切片(指向具有长度和功能的数组)
2019-01-03 13:51:22 +08:00
2019-01-08 13:32:56 +08:00
7. `chan Type` \- 一个线程安全的队列
2019-01-03 13:51:22 +08:00
2019-01-08 13:32:56 +08:00
8. 指针 `*T` 指向其他类型
2019-01-03 13:51:22 +08:00
2019-01-08 13:32:56 +08:00
9. 函数
2019-01-03 13:51:22 +08:00
2019-01-11 09:58:30 +08:00
10. 具名类型 - 可能具有关联方法的其他类型的别名(译者注:这里的别名并非指 Go 1.9 中的新特性“类型别名”):
2019-01-03 13:51:22 +08:00
2019-01-08 13:32:56 +08:00
```
type T struct { foo int }
type T *T
type T OtherNamedType
```
2019-01-03 13:51:22 +08:00
2019-01-11 09:58:30 +08:00
具名类型完全不同于他们的底层类型,所以你不能让他们互相赋值,但一些运输符,例如 `+`,能够处理同一底层数值类型的具名类型对象们(所以你可以在上面的示例中把两个 `T` 加起来)。
2019-01-03 13:51:22 +08:00
2019-01-11 09:58:30 +08:00
Maps、slices 和 channels 是类似于引用的类型——他们实际上是包含指针的结构。包括数组(具有固定长度并可被拷贝)在内的其他类型则是值(拷贝)传递。
2019-01-03 13:51:22 +08:00
#### 类型转换
2019-01-11 09:58:30 +08:00
类型转换类似于 C 或其他语言中的转换。它们写成这样子:
2019-01-03 13:51:22 +08:00
```
TypeName(value)
```
#### 常量
2019-01-11 09:58:30 +08:00
Go 有“无类型”字面量和常量。
2019-01-03 13:51:22 +08:00
```
2019-01-11 09:58:30 +08:00
1 // 无类型整数字面量
const foo = 1 // 无类型整数常量
const foo int = 1 // int 类型常量
2019-01-03 13:51:22 +08:00
```
2019-01-11 09:58:30 +08:00
无类型值可以分为以下几类:`UntypedBool`、`UntypedInt`、`UntypedRune`、`UntypedFloat`、`UntypedComplex`、`UntypedString` 以及 `UntypedNil`Go 称它们为基础类型other basic kinds are available for the concrete types like `uint8`)。一个无类型值可以赋值给一个从基础类型中派生的具名类型;例如:
2019-01-03 13:51:22 +08:00
```
type someType int
const untyped = 2 // UntypedInt
2019-01-11 09:58:30 +08:00
const bar someType = untyped // OK: untyped 可以被赋值给 someType
2019-01-03 13:51:22 +08:00
const typed int = 2 // int
2019-01-11 09:58:30 +08:00
const bar2 someType = typed // error: int 不能被赋值给 someType
2019-01-03 13:51:22 +08:00
```
### 接口和对象
2019-01-14 17:15:45 +08:00
正如上面所说的接口是一组方法的集合。Go 本身不是一种面向对象的语言,但它支持将方法关联到命名类型上:当声明一个函数时,可以提供一个接收者。接收者是函数的一个额外参数,可以在函数之前传递并参与函数查找,就像这样:
2019-01-03 13:51:22 +08:00
```
type SomeType struct { ... }
func (s *SomeType) MyMethod() {
}
func main() {
var s SomeType
s.MyMethod()
}
```
2019-01-14 17:15:45 +08:00
如果对象实现了所有方法,那么它就实现了接口;例如,`*SomeType`(注意指针)实现了下面的接口 `MyMethoder`,因此 `*SomeType` 类型的值就能作为 `MyMethoder` 类型的值使用。最基本的接口类型是 `interface{}`,它是一个带空方法集的接口——任何对象都满足该接口。
2019-01-03 13:51:22 +08:00
```
type MyMethoder interface {
MyMethod()
}
```
2019-01-14 17:15:45 +08:00
合法的接收者类型是有些限制的;例如,命名类型可以是指针类型(例如,`type MyIntPointer *int`),但这种类型不是合法的接收者类型。
2019-01-03 13:51:22 +08:00
### 控制流
2019-01-17 13:29:16 +08:00
Go 提供了三个主要的控制了语句:`if`、`switch` 和 `for`。这些语句同其他 C 风格语言内的语句非常类似,但有一些不同:
2019-01-03 13:51:22 +08:00
2019-01-17 13:29:16 +08:00
* 条件语句没有括号,所以条件语句是 `if a == b {}` 而不是 `if (a == b) {}`。大括号是必须的。
2019-01-03 13:51:22 +08:00
2019-01-17 13:29:16 +08:00
* 所有的语句都可以有初始化,比如这个
`if result, err := someFunction(); err == nil { // use result }`
* `switch` 语句在 cases 里可以使用任何表达式
2019-01-03 13:51:22 +08:00
2019-01-17 13:29:16 +08:00
* `switch` 语句可以处理空的表达式(等于 true
2019-01-03 13:51:22 +08:00
2019-01-17 13:29:16 +08:00
* 默认情况下Go 不会从一个 case 进入下一个 case不需要 `break`),在程序块的末尾使用 `fallthrough` 则会进入下一个 case。
2019-01-03 13:51:22 +08:00
2019-01-17 13:29:16 +08:00
* 循环语句 `for` 不止能循环值域:`for key, val := range map { do something }`
2019-01-03 13:51:22 +08:00
### Goroutines
2019-01-22 13:52:56 +08:00
关键词 `go` 会产生一个新的 goroutine一个可以并行执行的函数。它可以用于任何函数调用甚至一个匿名函数
2019-01-03 13:51:22 +08:00
```
func main() {
...
go func() {
...
}()
go some_function(some_argument)
}
```
2019-01-22 13:52:56 +08:00
### 信道
2019-01-03 13:51:22 +08:00
2019-01-24 12:57:46 +08:00
Goroutines 通常同信道结合以提供一种通信顺序进程的扩展。信道是一个并发安全的队列,而且可以缓冲或者不缓冲:
2019-01-03 13:51:22 +08:00
```
2019-01-24 12:57:46 +08:00
var unbuffered = make(chan int) // 直到数据被读取时完成数据块发送
var buffered = make(chan int, 5) // 最多有 5 个未读取的数据块
2019-01-03 13:51:22 +08:00
```
2019-01-24 12:57:46 +08:00
运输符 `<-` 用于同单个信道通信。
2019-01-03 13:51:22 +08:00
```
valueReadFromChannel := <- channel
otherChannel <- valueToSend
```
2019-01-24 12:57:46 +08:00
语句 `select` 允许多个信道进行通信:
2019-01-03 13:51:22 +08:00
```
select {
case incoming := <- inboundChannel:
2019-01-24 12:57:46 +08:00
// 一条新消息
2019-01-03 13:51:22 +08:00
case outgoingChannel <- outgoing:
2019-01-24 12:57:46 +08:00
// 可以发送消息
2019-01-03 13:51:22 +08:00
}
```
### `defer` 声明
2019-01-24 12:57:46 +08:00
Go 提供语句 `defer` 允许函数退出时调用执行预定的函数。它可以用于资源释放,例如:
2019-01-03 13:51:22 +08:00
```
func myFunc(someFile io.ReadCloser) {
defer someFile.close()
2019-01-24 12:57:46 +08:00
/* 文件相关操作 */
2019-01-03 13:51:22 +08:00
}
```
2019-01-24 12:57:46 +08:00
当然,它允许使用匿名函数作为被调函数,而且编写被调函数时可以像平常一样使用任何变量。
2019-01-03 13:51:22 +08:00
### 错误处理
2019-01-25 13:31:04 +08:00
Go 没有提供异常类或者结构化的错误处理。然而,它通过第二个及后续的返回值来返回错误从而处理错误:
2019-01-03 13:51:22 +08:00
```
func Read(p []byte) (n int, err error)
2019-01-25 13:31:04 +08:00
// 内建类型:
2019-01-03 13:51:22 +08:00
type error interface {
Error() string
}
```
2019-01-25 13:31:04 +08:00
必须在代码中检查错误或者赋值给 `_`
2019-01-03 13:51:22 +08:00
```
2019-01-25 13:31:04 +08:00
n0, _ := Read(Buffer) // 忽略错误
2019-01-03 13:51:22 +08:00
n, err := Read(buffer)
if err != nil {
return err
}
```
There are two functions to quickly unwind and recover the call stack, though: `panic()` and `recover()`. When `panic()` is called, the call stack is unwound, and any deferred functions are run as usual. When a deferred function invokes `recover()`, the unwinding stops, and the value given to `panic()` is returned. If we are unwinding normally and not due to a panic, `recover()` simply returns `nil`. In the example below, a function is deferred and any `error` value that is given to `panic()` will be recovered and stored in an error return value. Libraries sometimes use that approach to make highly recursive code like parsers more readable, while still maintaining the usual error return value for public functions.
```
func Function() (err error) {
2019-01-25 13:31:04 +08:00
defer func() {
s := recover()
switch s := s.(type) { // type switch
case error:
err = s // s has type error now
default:
panic(s)
}
}
2019-01-03 13:51:22 +08:00
}
```
### Arrays 和 slices
As mentioned before, an array is a value type and a slice is a pointer into an array, created either by slicing an existing array or by using `make()` to create a slice, which will create an anonymous array to hold the elements.
```
slice1 := make([]int, 2, 5) // 5 elements allocated, 2 initialized to 0
slice2 := array[:] // sliced entire array
slice3 := array[1:] // slice of array without first element
```
There are some more possible combinations for the slicing operator than mentioned above, but this should give a good first impression.
A slice can be used as a dynamically growing array, using the `append()` function.
```
slice = append(slice, value1, value2)
slice = append(slice, arrayOrSlice...)
```
Slices are also used internally to represent variable parameters in variable length functions.
### Maps
Maps are simple key-value stores and support indexing and assigning. They are not thread-safe.
```
someValue := someMap[someKey]
someValue, ok := someMap[someKey] // ok is false if key not in someMap
someMap[someKey] = someValue
```
--------------------------------------------------------------------------------
via: https://blog.jak-linux.org/2018/12/24/introduction-to-go/
作者:[Julian Andres Klode][a]
选题:[lujun9972][b]
译者:[LazyWolfLin](https://github.com/LazyWolfLin)
校对:[校对者ID](https://github.com/校对者ID)
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
[a]: https://blog.jak-linux.org/
[b]: https://github.com/lujun9972