0%

在x分钟内学会go

参考资料:


go 类型

类型分类 类型示例 定义语法
基本类型 int, float32, bool, string, rune var x int var y float32 var isTrue bool var s string
复合类型 array, slice, map, struct var arr [5]int var s []int var m map[string]int type Person struct { Name string; Age int }
引用类型 slice, map, channel, pointer var p *int var ch chan int var s []int var m map[string]int
接口类型 interface{}, 自定义接口 var i interface{} type Animal interface { Speak() string }
函数类型 函数类型可以作为类型,传递函数 type AddFunc func(a int, b int) int var sum AddFunc
常量类型 常量类型通过 const 声明,值不可更改 const Pi = 3.14 const Name = "Go"
类型别名 type NewType oldType 用于为类型创建别名 type MyInt int var x MyInt

代码通识💡

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430

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

/* 构建标签是以 //go:build 开头的行注释
可以通过 go build -tags="foo bar" 命令执行。
构建标签放在包声明之前,靠近文件顶部
后面跟着一个空行或其他行注释。 */
//go:build prod || dev || test

// 每个源文件都以包声明开始。
// main 是一个特殊名称,声明一个可执行文件而不是库。
package main

// 导入声明声明在此文件中引用的库包。
import (
"fmt" // Go 标准库中的一个包。
"io" // 实现一些 I/O 工具函数。
m "math" // 带有本地别名 m 的数学库。
"net/http" // 是的,一个网络服务器!
_ "net/http/pprof" // 仅为副作用导入的分析库
"os" // 像处理文件系统的 OS 函数
"strconv" // 字符串转换。
)

// 函数定义。Main 是特殊的。它是可执行程序的入口点。
// 喜欢它或讨厌它,Go 使用大括号。
func main() {
// Println 输出一行到标准输出。
// 它来自 fmt 包。
fmt.Println("Hello world!")

// 在此包内调用另一个函数。
beyondHello()
}

// 函数可以有参数在括号中。
// 如果没有参数,空括号仍然是必需的。
func beyondHello() {
var x int // 变量声明。变量必须在使用前声明。
x = 3 // 变量赋值。
// "短"声明使用 := 来推断类型,声明并赋值。
y := 4
sum, prod := learnMultiple(x, y) // 函数返回两个值。
fmt.Println("sum:", sum, "prod:", prod) // 简单输出。
learnTypes() // < y 分钟,了解更多!
}

/* <- 多行注释
函数可以有参数和(多个!)返回值。
这里 `x`,`y` 是参数,`sum`,`prod` 是签名(返回的内容)。
注意 `x` 和 `sum` 接收类型 `int`。
*/
func learnMultiple(x, y int) (sum, prod int) {
return x + y, x * y // 返回两个值。
}

// 一些内置类型和字面量。
func learnTypes() {
// 短声明通常会给你想要的东西。
str := "Learn Go!" // 字符串类型。

s2 := `A "raw" string literal
can include line breaks.` // 同样的字符串类型。

// 非 ASCII 字面量。Go 源代码是 UTF-8。
g := 'Σ' // rune 类型,是 int32 的别名,保存一个 unicode 码点。

f := 3.14159 // float64,IEEE-754 64 位浮点数。
c := 3 + 4i // complex128,内部用两个 float64 表示。

// 带有初始化器的 var 语法。
var u uint = 7 // 无符号,但实现依赖于大小,与 int 一样。
var pi float32 = 22. / 7

// 转换语法与短声明。
n := byte('\n') // byte 是 uint8 的别名。

// 数组的大小在编译时固定。
var a4 [4]int // 一个包含 4 个 int 的数组,初始化为全 0。
a5 := [...]int{3, 1, 5, 10, 100} // 一个初始化为固定大小为五的数组
// 元素,值为 3,1,5,10 和 100。

// 数组具有值语义。
a4_cpy := a4 // a4_cpy 是 a4 的副本,两个独立的实例。
a4_cpy[0] = 25 // 只有 a4_cpy 被更改,a4 保持不变。
fmt.Println(a4_cpy[0] == a4[0]) // false

// 切片具有动态大小。数组和切片各有优缺点
// 但切片的使用场景更为常见。
s3 := []int{4, 5, 9} // 与 a5 相比。这里没有省略号。
s4 := make([]int, 4) // 分配 4 个 int 的切片,初始化为全 0。
var d2 [][]float64 // 仅声明,没有分配任何内容。
bs := []byte("a slice") // 类型转换语法。

// 切片(以及映射和通道)具有引用语义。
s3_cpy := s3 // 两个变量指向同一个实例。
s3_cpy[0] = 0 // 这意味着两个都被更新。
fmt.Println(s3_cpy[0] == s3[0]) // true

// 因为它们是动态的,切片可以按需追加。
// 要向切片追加元素,使用内置的 append() 函数。
// 第一个参数是我们要追加的切片。通常,
// 切片变量就地更新,如下面的示例。
s := []int{1, 2, 3} // 结果是一个长度为 3 的切片。
s = append(s, 4, 5, 6) // 添加了 3 个元素。切片现在的长度为 6。
fmt.Println(s) // 更新后的切片现在是 [1 2 3 4 5 6]

// 要追加另一个切片,而不是原子元素的列表,我们可以
// 传递对切片的引用或像这样带有尾随省略号的切片字面量,
// 意思是取一个切片并展开其元素,
// 将它们追加到切片 s。
s = append(s, []int{7, 8, 9}...) // 第二个参数是一个切片字面量。
fmt.Println(s) // 更新后的切片现在是 [1 2 3 4 5 6 7 8 9]

p, q := learnMemory() // 声明 p,q 为指向 int 的指针。
fmt.Println(*p, *q) // * 后跟指针。这将打印两个 int。

// 映射是动态可增长的关联数组类型,类似于某些其他语言的
// 哈希或字典类型。
m := map[string]int{"three": 3, "four": 4}
m["one"] = 1
// 检查键是否存在于映射中,如下所示:
if val, ok := m["one"]; ok {
// 做一些事情
}

// 未使用的变量在 Go 中是一个错误。
// 下划线让你"使用"一个变量但丢弃其值。
_, _, _, _, _, _, _, _, _, _ = str, s2, g, f, u, pi, n, a5, s4, bs
// 通常你用它来忽略函数的返回值之一
// 例如,在一个快速而肮脏的脚本中,你可能会忽略
// 从 os.Create 返回的错误值,并期望文件
// 总是会被创建。
file, _ := os.Create("output.txt")
fmt.Fprint(file, "这就是你如何写入文件,顺便说一下")
file.Close()

// 当然,输出也算作使用一个变量。
fmt.Println(s, c, a4, s3, d2, m)

learnFlowControl() // 回到流程控制。
}

// 在许多其他语言中,Go 中的函数
// 可以有命名返回值。
// 在函数声明行中为返回的类型分配一个名称
// 允许我们轻松地从函数中的多个点返回,以及
// 只使用 return 关键字,而不需要其他内容。
func learnNamedReturns(x, y int) (z int) {
z = x * y
return // z 在这里是隐式的,因为我们之前命名了它。
}

// Go 是完全垃圾回收的。它有指针但没有指针运算。
// 你可以犯一个 nil 指针的错误,但不会通过递增指针。
// 与 C/C++ 不同,获取和返回局部变量的地址也是安全的。
func learnMemory() (p, q *int) {
// 命名返回值 p 和 q 的类型是指向 int 的指针。
p = new(int) // 内置函数 new 分配内存。
// 分配的 int 切片初始化为 0,p 不再是 nil。
s := make([]int, 20) // 分配 20 个 int 作为一块内存。
s[3] = 7 // 赋值其中一个。
r := -2 // 声明另一个局部变量。
return &s[3], &r // & 获取对象的地址。
}

// 使用别名数学库(见上面的导入)
func expensiveComputation() float64 {
return m.Exp(10)
}

func learnFlowControl() {
// 如果语句需要大括号,并且不需要括号。
if true {
fmt.Println("我告诉过你")
}
// 格式化由命令行命令 "go fmt" 标准化。
if false {
// 呜呜。
} else {
// 得意。
}
// 使用 switch 优先于链式 if 语句。
x := 42.0
switch x {
case 0:
case 1, 2: // 可以在一个 case 中有多个匹配
case 42:
// case 不会"穿透"。
/*
然而,有一个 `fallthrough` 关键字,见:
https://go.dev/wiki/Switch#fall-through
*/
case 43:
// 未到达。
default:
// 默认情况是可选的。
}

// 类型开关允许根据某个东西的类型而不是值进行切换
var data interface{}
data = ""
switch c := data.(type) {
case string:
fmt.Println(c, "是一个字符串")
case int64:
fmt.Printf("%d 是一个 int64\n", c)
default:
// 所有其他情况
}

// 与 if 一样,for 也不使用括号。
// 在 for 和 if 中声明的变量是局部的。
for x := 0; x < 3; x++ { // ++ 是一个语句。
fmt.Println("迭代", x)
}
// x == 42 在这里。

// for 是 Go 中唯一的循环语句,但它有替代形式。
for { // 无限循环。
break // 开玩笑的。
continue // 未到达。
}

// 你可以使用 range 遍历数组、切片、字符串、映射或通道。
// range 返回一个(通道)或两个值(数组、切片、字符串和映射)。
for key, value := range map[string]int{"one": 1, "two": 2, "three": 3} {
// 对于映射中的每一对,打印键和值
fmt.Printf("key=%s, value=%d\n", key, value)
}
// 如果你只需要值,使用下划线作为键
for _, name := range []string{"Bob", "Bill", "Joe"} {
fmt.Printf("你好,%s\n", name)
}

// 与 for 一样,:= 在 if 语句中意味着先声明并赋值
// y,然后测试 y > x。
if y := expensiveComputation(); y > x {
x = y
}
// 函数字面量是闭包。
xBig := func() bool {
return x > 10000 // 引用上面 switch 语句中声明的 x。
}
x = 99999
fmt.Println("xBig:", xBig()) // true
x = 1.3e3 // 这使得 x == 1300
fmt.Println("xBig:", xBig()) // 现在是 false。

// 更重要的是,函数字面量可以在线定义和调用,
// 作为函数的参数,只要:
// a) 函数字面量立即调用(),
// b) 结果类型与参数的预期类型匹配。
fmt.Println("加 + 乘以两个数字:",
func(a, b int) int {
return (a + b) * 2
}(10, 2)) // 用参数 10 和 2 调用
// => 加 + 乘以两个数字:24

// 当你需要它时,你会喜欢它。
goto love
love:

learnFunctionFactory() // func 返回 func 是有趣的(3)(3)
learnDefer() // 一个快速的绕道到一个重要的关键字。
learnInterfaces() // 好东西即将到来!
}

func learnFunctionFactory() {
// 接下来的两个是等效的,第二个更实用
fmt.Println(sentenceFactory("summer")("一个美丽的", "日子!"))

d := sentenceFactory("summer")
fmt.Println(d("一个美丽的", "日子!"))
fmt.Println(d("一个懒惰的", "下午!"))
}

// 装饰器在其他语言中很常见。Go 中也可以做到
// 使用接受参数的函数字面量。
func sentenceFactory(mystring string) func(before, after string) string {
return func(before, after string) string {
return fmt.Sprintf("%s %s %s", before, mystring, after) // 新字符串
}
}

//defer-后进先出(LIFO)顺序执行
func learnDefer() (ok bool) {
// defer 语句将函数调用推送到列表中。保存的
// 调用列表在周围的函数返回后执行。
defer fmt.Println("延迟语句以相反的顺序执行(LIFO)。")
defer fmt.Println("\n这一行首先被打印,因为")
// defer 通常用于关闭文件,因此关闭文件的函数
// 保持在打开文件的函数附近。
return true
}

// 定义 Stringer 作为具有一个方法 String 的接口类型。
type Stringer interface {
String() string
}

// 定义 pair 作为具有两个字段的结构体,命名为 x 和 y 的 int。
type pair struct {
x, y int
}

// 在类型 pair 上定义一个方法。Pair 现在实现了 Stringer,因为 Pair 定义了接口中的所有方法。
func (p pair) String() string { // p 被称为"接收者"
// Sprintf 是 fmt 包中的另一个公共函数。
// 点语法引用 p 的字段。
return fmt.Sprintf("(%d, %d)", p.x, p.y)
}

func learnInterfaces() {
// 大括号语法是"结构字面量"。它计算为初始化
// 结构。:= 语法声明并初始化 p 为此结构。
p := pair{3, 4}
fmt.Println(p.String()) // 调用类型为 pair 的 p 的 String 方法。
var i Stringer // 声明 i 为接口类型 Stringer。
i = p // 有效,因为 pair 实现了 Stringer
// 调用类型为 Stringer 的 i 的 String 方法。输出与上面相同。
fmt.Println(i.String())

// fmt 包中的函数调用 String 方法以请求对象
// 的可打印表示。
fmt.Println(p) // 输出与上面相同。Println 调用 String 方法。
fmt.Println(i) // 输出与上面相同。

learnVariadicParams("很棒的", "学习", "在这里!")
}

// 函数可以有变参。
func learnVariadicParams(myStrings ...any) { // any 是 interface{} 的别名
// 迭代每个变参的值。
// 这里的下划线忽略了数组的索引参数。
for _, param := range myStrings {
fmt.Println("参数:", param)
}

// 将变参值作为变参传递。
fmt.Println("参数:", fmt.Sprintln(myStrings...))

learnErrorHandling()
}

func learnErrorHandling() {
// ", ok" 习语用于告诉某件事情是否成功。
m := map[int]string{3: "three", 4: "four"}
if x, ok := m[1]; !ok { // ok 将为 false,因为 1 不在映射中。
fmt.Println("没有人")
} else {
fmt.Print(x) // 如果 x 在映射中,它将是值。
}
// 错误值不仅传达"ok",还传达更多关于问题的信息。
if _, err := strconv.Atoi("非整数"); err != nil { // _ 丢弃值
// 打印 'strconv.ParseInt: parsing "non-int": invalid syntax'
fmt.Println(err)
}
// 我们稍后会重新访问接口。与此同时,
learnConcurrency()
}

// c 是一个通道,一个并发安全的通信对象。
func inc(i int, c chan int) {
c <- i + 1 // <- 是当通道出现在左侧时的"发送"运算符。
}

// 我们将使用 inc 来并发地递增一些数字。
func learnConcurrency() {
// 与之前用于创建切片的 make 函数相同。Make 分配和
// 初始化切片、映射和通道。
c := make(chan int)
// 启动三个并发的 goroutine。数字将被递增
// 并发地,可能在机器能够并且
// 正确配置的情况下并行。所有三个发送到同一个通道。
go inc(0, c) // go 是一个语句,启动一个新的 goroutine。
go inc(10, c)
go inc(-805, c)
// 从通道读取三个结果并打印它们。
// 没有办法知道结果将以什么顺序到达!
fmt.Println(<-c, <-c, <-c) // 通道在右侧,<- 是"接收"运算符。

cs := make(chan string) // 另一个通道,这个处理字符串。
ccs := make(chan chan string) // 一个字符串通道的通道。
go func() { c <- 84 }() // 启动一个新的 goroutine 仅仅发送一个值。
go func() { cs <- "wordy" }() // 再次,为 cs 这次。
// select 的语法类似于 switch 语句,但每个 case 涉及
// 通道操作。它随机选择准备通信的 case。
select {
case i := <-c: // 接收到的值可以分配给一个变量,
fmt.Printf("它是一个 %T", i)
case <-cs: // 或者接收到的值可以被丢弃。
fmt.Println("它是一个字符串")
case <-ccs: // 空通道,未准备好通信。
fmt.Println("没有发生。")
}
// 此时,从 c 或 cs 中取出了一个值。上面启动的两个
// goroutine 中的一个已经完成,另一个将保持阻塞。

learnWebProgramming() // Go 做到了。你也想这样做。
}

// package http 中的一个单一函数启动一个网络服务器。
func learnWebProgramming() {
// ListenAndServe 的第一个参数是要监听的 TCP 地址。
// 第二个参数是一个接口,特别是 http.Handler。
go func() {
err := http.ListenAndServe(":8080", pair{})
fmt.Println(err) // 不要忽略错误
}()

requestServer()
}

// 通过实现其唯一方法 ServeHTTP,使 pair 成为 http.Handler。
func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 使用 http.ResponseWriter 的方法提供数据。
w.Write([]byte("你在 Y 分钟内学会了 Go!"))
}

func requestServer(){
//函数向 http://localhost:8080 发送一个 GET 请求。
resp,err := http.Get("http://localhost:8080")
fmt.Println(err)
defer resp.Body.Close()//在函数执行完毕后关闭请求
//读取 resp.Body 中的所有内容-响应体
body,err := io.ReadAll(resp.Body)
fmt.Printf("\nWebserver 说: `%s`", string(body))
}

难点解析💡

一、装饰器

装饰器的概念:

  • 装饰器(Decorator) 是一种常见的设计模式,目的是在不改变原有函数的情况下对其功能进行扩展或修改。在 Go 语言中,通过闭包可以实现类似装饰器的功能。
  • 在本例中,sentenceFactory 就是一个简单的装饰器,它将一个常量字符串(mystring)和传入的参数组合成新的句子。
  1. sentenceFactory 函数工厂:

    sentenceFactory 是一个函数工厂,它接收一个字符串(如 "summer")并返回一个新的函数,这个返回的函数接收两个参数 beforeafter,并返回一个格式化的字符串。

    1
    2
    3
    4
    5
    func sentenceFactory(mystring string) func(before, after string) string {
    return func(before, after string) string {
    return fmt.Sprintf("%s %s %s", before, mystring, after)
    }
    }

    这个函数工厂的核心是:

    • 它返回一个匿名函数(也叫做闭包),这个匿名函数接受两个字符串参数 beforeafter,并通过 fmt.Sprintf 格式化并返回一个新的字符串。
    • 在格式化的过程中,mystring 是函数工厂的输入参数,代表固定的一部分字符串。beforeafter 则是调用这个函数时传入的参数。
  2. learnFunctionFactory 使用:

    learnFunctionFactory 函数演示了如何使用 sentenceFactory

    1
    2
    3
    4
    5
    6
    7
    func learnFunctionFactory() {
    fmt.Println(sentenceFactory("summer")("一个美丽的", "日子!"))

    d := sentenceFactory("summer")
    fmt.Println(d("一个美丽的", "日子!"))
    fmt.Println(d("一个懒惰的", "下午!"))
    }
    • sentenceFactory("summer")("一个美丽的", "日子!") 这一行创建了一个使用 "summer" 字符串的函数,并立即调用它,传入 "一个美丽的""日子!" 作为参数。输出将是:"一个美丽的 summer 日子!".
    • d := sentenceFactory("summer") 先将工厂返回的函数赋值给 d,然后可以多次调用 d,传入不同的参数,生成不同的句子。第二次调用时传入的是 "一个懒惰的""下午!",输出将是:"一个懒惰的 summer 下午!"