Let's Go

Let's Go

最后修改于 2022-5-8 ⋅ 共 8.9k 字 ⋅ 18分钟 / #Tutorial / #Go, #资料, #合集

相关链接 #

资料合集 Golang
资料合集 JAVA
资料合集 Kubernetes
资料合集 其他

评价 #

对比 JS #

评价

go 开发 web 其实效率很低,现在基本没有哪个框架够得上及格线。


莫慌,golang 也卷 而且目前看 golang 很有可能走 node 的老路
所谓中间件是个纯工程化的东西,不建议理论基础都还没打好又没有实际经验的时候深入。 遇到问就说大概看过原理,其他不知道就行。花式问应届生中间件的公司 /项目组大多也不靠谱。


竞品 Node.JS 凉凉 https://www.v2ex.com/t/630587

同意楼上观点,node 目前国内场景还是 node 微服务,写中台这些,纯 node 后台确实没有搞头 XD

想当然了吧,python 的确半死不活,但是绝对比 nodejs 好多了。

node.js 最火是七八年前, 现在也是前浪了, 就不要互相伤害了


https://www.v2ex.com/t/790207 https://www.v2ex.com/t/763188

py 和 js 的开发者从来就不是高重合的群体,都是各写各的,各用各的;
web 现在是红海,发展比较平稳,各语言份额很稳固;
nodejs 远远没有前几年预测的那么火,为了性能和时髦转 go 的更多;


新的后端项目不是 go 比较多么,国内很多公司都是 python 转 go 吧,没见过多少转 nodejs 的


前端也会有比较烦的,我举点例子:

  • react Filber 流程,scheduer 调度过程,diff 现在问的不多的样子了,区分一下 concurrent 和 legacy 模式
  • react 系列的 redux, mobx 等原理,hooks hoc render-props 之类的对比一下
  • webpack 内部执行流程,要写个插件怎么写,babel 原理,ast 树操作,写个插件
  • js 手写系列: Promise 一定需要完整的背下来不然问题会很多,dubounce, curry,compose 等一些小方法,比较老的是 new 啊,继承方面之类的实现
  • 算法方面 简单加中等题就好了,我面了几家让我写了好几次的反转链表啥的了
  • 协议方面,http https http2 系列加缓存配置这个需要背的烂熟, 对应的 nginx 相对应的配置啥的也要玩一下, 相应的浏览器加载网页的流程也要记的熟

语言占比 #

https://madnight.github.io/githut/#/pull_requests/2021/4

注意:go的模块直接发布在Github上,因此可能导致占比偏高

LanguagePercentage (YoY Change)
1Python17.926% (+1.438%)
2JavaScript14.058% (-4.714%)
3Java12.208% (+0.662%)
4TypeScript8.472% (+1.818%)
5Go8.161% (+0.027%)
6C++6.670% (-0.331%)
7Ruby6.165% (-0.783%)
8PHP5.252% (-0.322%)
9C#3.372% (-0.301%)
10C3.150% (+0.023%)

Tiobe https://www.tiobe.com/tiobe-index/

20222021LanguageRatingsChange
1 ↑3Python15.33%+4.47%
2 ↓1C14.08%-2.26%
3 ↓2Java12.13%+0.84%
44C++8.01%+1.13%
55C#5.37%+0.93%
66Visual Basic5.23%+0.90%
77JavaScript1.83%-0.45%
88PHP1.79%+0.04%
9 ↑10Assembly language1.60%-0.06%
10 ↓9SQL1.55%-0.18%
11 ↑13Go1.23%-0.05%
12 ↑15Swift1.18%+0.04%
13 ↓11R1.11%-0.45%
14 ↑16MATLAB1.03%-0.03%

就业 #

https://v2ex.com/t/836719

不管自学还是培训,完事后赶紧找个带互联网性质的公司(忽略公司大小,先入职),待段时间你就知道了这个行业大家都在用什么,应该学什么,然后重新梳理你需要学习的技能树,然后找资料自学,过程中准备提升学历,最后看情况,要不要跳到适合的好点的正儿八经的互联网公司。
BTW ,认真考虑下是否需要选 Java ,真的太卷了,举个例子,一两年甚至校招,Redis 、MySQL 、消息队列八股文必须熟才有资格往下继续面,用不用得上不重要,你得知道……可以考虑下往 GO 方向卷


https://www.v2ex.com/t/814711

Go 和 Java 都写过,我入行不久,但是也面试过别人了
从业务开发程序员角度说下几点希望有帮助:
1.面试:
Java 无疑比 Go 卷多了,Go 的岗位虽然不及 Java ,但是大公司的 Go 岗位并不少;
2.编程:
Go 写起来语法上比 Java 舒服,如果你写过应该会有一样的体会吧;


https://v2ex.com/t/779200

我写 c#的转 golang 用了一周,虽然写起来没那么舒服,但是挣钱还是没问题吧
7 个小时足够熟练 Golang 了
参考基准为 https://github.com/no1xsyzy/project-v2c/tree/master/v2c-go-gin
基本涵盖了除了数据库交互外的 80% 情景,但拍脑袋写出来的复用性不高。


https://www.v2ex.com/t/763345

按照我转语言的经验,找个公司也转的,或者背点语言相关八股文,比如你学的 go 的 GMP 调度模型,垃圾回收机制,锁,sync 包的一些东西,context 包,channel 的一些东西,原子操作的一些东西,语言就过关了


https://v2ex.com/t/757354

先保证 C 不太差,再学 JAVA,再学 python,最后 golang
C 语言是你理解数据结构和算法的最佳语言(用 C 理解数组扩容、树的遍历是最直接、印象最深的) JAVA 是在成渝地区应届生就业的最佳选择(用 C++的腾讯系除外,当然成都腾讯也不一样)
python 作为动态语言(也被部分观念传统的人称为脚本语言)的代表之一,是需要非核心系统(当然也有用 python 做核心系统的国际大厂)不错的选择
golang 因为 goroutine 和 io 特性独树一帜,目前势头非常猛,岗位也在快速变多,不少公司规模上来之后选择用 golang 重构(如 B 站),最大的问题是作为应届生基本找不到 golang 的工作

简单来说,C 语言是你打牢计算机知识体系最好的锤子,JAVA 是你毕业就业的最佳选择(而且本身 JAVA 从语言设计到实践都能带给你非常全面的软件工程领域的知识)
python 和 golang 可以在你觉得枯燥的时候用来找找乐子,两者分别是新兴动态、静态语言的杰出代表(虽然新兴的时代不同),对这两个语言的学习过程更像是“看看这个世界上的牛人们都搞出了什么科幻的东西”。

学习曲线上来讲 C-JAVA & Golang 是比较平滑的,甚至我个人觉得 Golang 像是 C 的扩扩扩扩展包 python 如果放在他们之后学,会不停地让你觉得“这 tm 也可以?”,但是当你要写个小东西,或者要很快的实现一个新产品的时候你会想到它的


https://v2ex.com/t/800281

之前主要写 java 。也是找了份 go 的工作(找之前不知道是写 go ),然后边工作边学习,大概2 周出活。感觉如果真的要花点儿时间学的话大概两方面,一是 go routine,sync 和 channel ;二是 go module

Boss 直聘 #

https://www.zhipin.com/c101210100/?query=golang&ka=sel-city-101210100

https://v2ex.com/t/837108
大部分是外包,BOSS 上找工作之前先把公司类型为”计算机软件“ 的公司都屏蔽了

公司类型规模要求
1滴滴-100020k常用数据结构和算法 常用存储、缓存、消息队列等基础中间件产品 有良好的业务架构和技术架构能力,大型复杂分布式系统架构设计和调优
1天玑数据5008k有kubernetes实际使用经验 kubernetes相关原理
1长亭安全50020k对容器技术,K8S有一定了解 一种常见的后端编程语言 了解掌握常用的数据库,缓存数据库,消息队列,对象存储服务等中间件 对 Go Web 方向有强烈兴趣
3不鸣游戏10015k游戏开发经验,网络编程,对TCP/IP,UDP等协议有深刻理解 游戏服务器常用的网络同步技术
2万朋-50021kmysql、redis,etcd等数据库的使用 高性能网络服务设计和开发,HTTP、TCP/IP网络协议
-佰钧成-100016kK8S,有K8S二次开发经验者,或者对K8S调度原理有深入了解 主流开源框架,比如:gin、viper、beego、gorm、seaweedfs等 使用经历(如:etcd、dubbo、grpc、redis、kafka
1谐云软件1003k计算机/软件/通信相关专业,基础扎实,参加过ACM等编程比赛的优先 了解Docker、Kubernetes 等容器技术优先
1门链-10012kLinux 操作系统,常见命令行工具 对 Nginx、OpenResty 等有一定了解和使用经验 了解容器相关知识,对 Docker, Kubernetes 等有实际操作使用经验
1巴比特-10012k分布式系统,了解微服务框架,运维开发,监控预警系统,硬件虚拟化等其中一种技术
1老酒商务208k了解golang语言,MVC框架和ORM框架 Git 有前端经验、小程序、Uniapp经验优先
1云际物联硬件2010k至少掌握Go/Java/C/C++语言之一,Linux开发环境
1华为软件-15kkubernetes,istio, docker,Openstack,大数据,AI等云计算相关技术优先 有redis/kafka/zookeeper/etcd等中间件开发和维护经验者
1默安安全50018kGolang进行Web,微服务应用开发 mongodb、redis、elasticsearch的应用 掌握 beego、gin、iris框架的优先
1同花顺-100016k有主流框架的项目经验 常见设计模式、常见数据结构算法、MySQL等关系型数据库、缓存技术、HTTP协议
-网易--15k对操作系统、网络等底层基础知识有深入的理解 Kubernetes、Docker 原理及应用 Hadoop 生态,有分布式系统开发经验者优先
3江小白其他100015kMySQL或者PostgreSql,redis/MemCache的应用开发、设计原理和性能优化
2万佳安-50012k至少一个golang web开发框架(gin,beego) web后台技术,包括http、tcp/ip及MQTT协议,mysql数据库,redis及rabbitmq/kafka消息中间件,ElasticSearch搜索引擎 使用git并有协同开发经验 protocolbuffer,grpc,有微服务,docker,k8s开发经验经验者优先考虑
2纵横-100015k掌握socket、HTTP、web等相关技术 具有高负载、高并发开发设计经验 MySql、Redis等数据库编程
-鸿衿服装10008k数据库(mysql, sqlserver中任意一种), 缓存(redis)的编程应用 了解Docker;git
1创达软件100020k业务Docker容器化和kubernetes集群运维管理
1乐港音乐50012kMySQL,能够编写符合优化级别的SQL语句,Redis和Kafka使用 Etcd、Nats、ElasticSearch、Prometheus
placehold
1澜盾教育1005k了解或者熟悉 Java/C++/Go中任一语言,具有较强的逻辑思维

需求 #

职友集 #

找到 728 条结果, 来自 8 家招聘网站
https://www.jobui.com/jobs?jobKw=golang&cityKw=%E6%9D%AD%E5%B7%9E&experienceType=1-3%E5%B9%B4

参考上一节招聘

#数量
kubernetes/k8s12
redis9
数据库 mysql7
docker7
缓存 中间件4
容器4
etcd http kafka4
gin beego git elasticsearch3
grpc2
viper gorm nginx1

[上海闵行] 游戏服务器开发(Go 优先)-初创公司-上海语昼科技https://www.v2ex.com/t/800585

  1. 使用 golang 语言开发游戏服务器功能
  2. 根据策划需求,完成功能模块的设计,编码和测试工作 任职要求
  3. 计算机相关专业,熟悉 linux 开发环境
  4. 熟悉 golang 编程语言(C 语系均可),熟悉一门脚本语言更佳
  5. 熟悉 redis 、mysql 数据库者优先
  6. 熟悉网络编程、并发编程
  7. 了解游戏上线前后的相关流程 加分项
  8. 熟悉 k8s 等相关技术优先
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 使用 https://www.78901.net/Participle/ 分词
var words='分词 结果 tool'.match(/[a-z]+[\w]+/ig);
//.replace(/[,。()、]| {1,}/, '').toLowerCase().split(' ');
var count={};
for (var i=0;i<words.length;i++){
    words[i]=words[i].toLowerCase();
    if (count[words[i]])
        count[words[i]]++;
    else
        count[words[i]]=1;
}
var sorted=Object.keys(count).sort(function(i, j){return count[j]-count[i]});
var res=[];
for (var i=0;i<sorted.length;i++){
    res.push([sorted[i],count[sorted[i]]]);
}
console.log(res);

资料 #

https://www.v2ex.com/t/819007

路线: https://roadmap.sh/golang
路线图
博客:Go 语言设计与实现为什么这么设计:关于计算机领域中程序设计决策
多少分钟学会:其中 X=Go
大合集:Github:最全空降Golang资料补给包(满血战斗) 在线操作:go 指南

Spring #

附录 #

Y分钟速成X,其中 X=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
 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
// 单行注释
/* 多行
    注释 */

// 导入包的子句在每个源文件的开头。
// Main比较特殊,它用来声明可执行文件,而不是一个库。
package main

// Import语句声明了当前文件引用的包。
import (
    "fmt"       // Go语言标准库中的包
    "io/ioutil" // 包含一些输入输出函数
    m "math"    // 数学标准库,在此文件中别名为m
    "net/http"  // 一个web服务器包
    "os"        // 系统底层函数,如文件读写
    "strconv"   // 字符串转换
)

// 函数声明:Main是程序执行的入口。
// 不管你喜欢还是不喜欢,反正Go就用了花括号来包住函数体。
func main() {
    // 往标准输出打印一行。
    // 用包名fmt限制打印函数。
    fmt.Println("你好世界")

    // 调用当前包的另一个函数。
    beyondHello()
}

// 函数可以在括号里加参数。
// 如果没有参数的话,也需要一个空括号。
func beyondHello() {
    var x int   // 变量声明,变量必须在使用之前声明。
    x = 3       // 变量赋值。
    // 可以用:=来偷懒,它自动把变量类型、声明和赋值都搞定了。
    y := 4
    sum, prod := learnMultiple(x, y)        // 返回多个变量的函数
    fmt.Println("sum:", sum, "prod:", prod) // 简单输出
    learnTypes()                            // 少于y分钟,学的更多!
}

/* <- 快看快看我是跨行注释_(:з」∠)_
Go语言的函数可以有多个参数和 *多个* 返回值。
在这个函数中, `x`、`y` 是参数,
`sum`、`prod` 是返回值的标识符(可以理解为名字)且类型为int
*/
func learnMultiple(x, y int) (sum, prod int) {
    return x + y, x * y // 返回两个值
}

// 内置变量类型和关键词
func learnTypes() {
    // 短声明给你所想。
    str := "少说话多读书!" // String类型

    s2 := `这是一个
可以换行的字符串` // 同样是String类型

    // 非ascii字符。Go使用UTF-8编码。
    g := 'Σ' // rune类型,int32的别名,使用UTF-8编码

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

    // Var变量可以直接初始化。
    var u uint = 7  // unsigned 无符号变量,但是实现依赖int型变量的长度
    var pi float32 = 22. / 7

    // 字符转换
    n := byte('\n') // byte是uint8的别名

    // 数组(Array)类型的大小在编译时即确定
    var a4 [4] int              // 有4个int变量的数组,初始为0
    a3 := [...]int{3, 1, 5}     // 有3个int变量的数组,同时进行了初始化

    // Array和slice各有所长,但是slice可以动态的增删,所以更多时候还是使用slice。
    s3 := []int{4, 5, 9}    // 回去看看 a3 ,是不是这里没有省略号?
    s4 := make([]int, 4)    // 分配4个int大小的内存并初始化为0
    var d2 [][]float64      // 这里只是声明,并未分配内存空间
    bs := []byte("a slice") // 进行类型转换

    // 切片(Slice)的大小是动态的,它的长度可以按需增长
    // 用内置函数 append() 向切片末尾添加元素
    // 要增添到的目标是 append 函数第一个参数,
    // 多数时候数组在原内存处顺次增长,如
    s := []int{1, 2, 3}     // 这是个长度3的slice
    s = append(s, 4, 5, 6)  // 再加仨元素,长度变为6了
    fmt.Println(s) // 更新后的数组是 [1 2 3 4 5 6]

    // 除了向append()提供一组原子元素(写死在代码里的)以外,我们
    // 还可以用如下方法传递一个slice常量或变量,并在后面加上省略号,
    // 用以表示我们将引用一个slice、解包其中的元素并将其添加到s数组末尾。
    s = append(s, []int{7, 8, 9}...) // 第二个参数是一个slice常量
    fmt.Println(s)  // 更新后的数组是 [1 2 3 4 5 6 7 8 9]

    p, q := learnMemory()       // 声明p,q为int型变量的指针
    fmt.Println(*p, *q)         // * 取值

    // Map是动态可增长关联数组,和其他语言中的hash或者字典相似。
    m := map[string]int{"three": 3, "four": 4}
    m["one"] = 1

    // 在Go语言中未使用的变量在编译的时候会报错,而不是warning。
    // 下划线 _ 可以使你“使用”一个变量,但是丢弃它的值。
    _, _, _, _, _, _, _, _, _, _ = str, s2, g, f, u, pi, n, a3, 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 is implicit here, because we named it earlier.
}

// Go全面支持垃圾回收。Go有指针,但是不支持指针运算。
// 你会因为空指针而犯错,但是不会因为增加指针而犯错。
func learnMemory() (p, q *int) {
    // 返回int型变量指针p和q
    p = new(int)    // 内置函数new分配内存
    // 自动将分配的int赋值0,p不再是空的了。
    s := make([]int, 20)    // 给20个int变量分配一块内存
    s[3] = 7                // 赋值
    r := -2                 // 声明另一个局部变量
    return &s[3], &r        // & 取地址
}

func expensiveComputation() int {
    return 1e6
}

func learnFlowControl() {
    // If需要花括号,括号就免了
    if true {
        fmt.Println("这句话肯定被执行")
    }
    // 用go fmt 命令可以帮你格式化代码,所以不用怕被人吐槽代码风格了,
    // 也不用容忍别人的代码风格。
    if false {
        // pout
    } else {
        // gloat
    }
    // 如果太多嵌套的if语句,推荐使用switch
    x := 1
    switch x {
    case 0:
    case 1:
        // 隐式调用break语句,匹配上一个即停止
    case 2:
        // 不会运行
    }
    // 和if一样,for也不用括号
    for x := 0; x < 3; x++ { // ++ 自增
        fmt.Println("遍历", x)
    }
    // x在这里还是1。为什么?

    // for 是go里唯一的循环关键字,不过它有很多变种
    for { // 死循环
        break    // 骗你的
        continue // 不会运行的
    }

    // 用range可以枚举 array、slice、string、map、channel等不同类型
    // 对于channel,range返回一个值,
    // array、slice、string、map等其他类型返回一对儿
    for key, value := range map[string]int{"one": 1, "two": 2, "three": 3} {
        // 打印map中的每一个键值对
        fmt.Printf("索引:%s, 值为:%d\n", key, value)
    }
    // 如果你只想要值,那就用前面讲的下划线扔掉没用的
    for _, name := range []string{"Bob", "Bill", "Joe"} {
        fmt.Printf("你是。。 %s\n", name)
    }

    // 和for一样,if中的:=先给y赋值,然后再和x作比较。
    if y := expensiveComputation(); y > x {
        x = y
    }
    // 闭包函数
    xBig := func() bool {
        return x > 100 // x是上面声明的变量引用
    }
    fmt.Println("xBig:", xBig()) // true (上面把y赋给x了)
    x /= 1e5                     // x变成10
    fmt.Println("xBig:", xBig()) // 现在是false

    // 除此之外,函数体可以在其他函数中定义并调用,
    // 满足下列条件时,也可以作为参数传递给其他函数:
    //   a) 定义的函数被立即调用
    //   b) 函数返回值符合调用者对类型的要求
    fmt.Println("两数相加乘二: ",
        func(a, b int) int {
            return (a + b) * 2
        }(10, 2)) // Called with args 10 and 2
    // => Add + double two numbers: 24

    // 当你需要goto的时候,你会爱死它的!
    goto love
love:

    learnFunctionFactory() // 返回函数的函数多棒啊
    learnDefer()      // 对defer关键字的简单介绍
    learnInterfaces() // 好东西来了!
}

func learnFunctionFactory() {
    // 空行分割的两个写法是相同的,不过第二个写法比较实用
    fmt.Println(sentenceFactory("原谅")("当然选择", "她!"))

    d := sentenceFactory("原谅")
    fmt.Println(d("当然选择", "她!"))
    fmt.Println(d("你怎么可以", "她?"))
}

// Decorator在一些语言中很常见,在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) // new string
    }
}

func learnDefer() (ok bool) {
    // defer表达式在函数返回的前一刻执行
    defer fmt.Println("defer表达式执行顺序为后进先出(LIFO)")
    defer fmt.Println("\n这句话比上句话先输出,因为")
    // 关于defer的用法,例如用defer关闭一个文件,
    // 就可以让关闭操作与打开操作的代码更近一些
    return true
}

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

// 定义pair为一个结构体,有x和y两个int型变量。
type pair struct {
    x, y int
}

// 定义pair类型的方法,实现Stringer接口。
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                   // 有效!因为p实现了Stringer接口(类似java中的塑型)
    // 调用i的String方法,输出和上面一样
    fmt.Println(i.String())

    // fmt包中的Println函数向对象要它们的string输出,实现了String方法就可以这样使用了。
    // (类似java中的序列化)
    fmt.Println(p) // 输出和上面一样,自动调用String函数。
    fmt.Println(i) // 输出和上面一样。

    learnVariadicParams("great", "learning", "here!")
}

// 有变长参数列表的函数
func learnVariadicParams(myStrings ...interface{}) {
    // 枚举变长参数列表的每个参数值
    // 下划线在这里用来抛弃枚举时返回的数组索引值
    for _, param := range myStrings {
        fmt.Println("param:", param)
    }

    // 将可变参数列表作为其他函数的参数列表
    fmt.Println("params:", fmt.Sprintln(myStrings...))

    learnErrorHandling()
}

func learnErrorHandling() {
    // ", ok"用来判断有没有正常工作
    m := map[int]string{3: "three", 4: "four"}
    if x, ok := m[1]; !ok { // ok 为false,因为m中没有1
        fmt.Println("别找了真没有")
    } else {
        fmt.Print(x) // 如果x在map中的话,x就是那个值喽。
    }
    // 错误可不只是ok,它还可以给出关于问题的更多细节。
    if _, err := strconv.Atoi("non-int"); err != nil { // _ discards value
        // 输出"strconv.ParseInt: parsing "non-int": invalid syntax"
        fmt.Println(err)
    }
    // 待会再说接口吧。同时,
    learnConcurrency()
}

// c是channel类型,一个并发安全的通信对象。
func inc(i int, c chan int) {
    c <- i + 1 // <-把右边的发送到左边的channel。
}

// 我们将用inc函数来并发地增加一些数字。
func learnConcurrency() {
    // 用make来声明一个slice,make会分配和初始化slice,map和channel。
    c := make(chan int)
    // 用go关键字开始三个并发的goroutine,如果机器支持的话,还可能是并行执行。
    // 三个都被发送到同一个channel。
    go inc(0, c) // go is a statement that starts a new goroutine.
    go inc(10, c)
    go inc(-805, c)
    // 从channel中读取结果并打印。
    // 打印出什么东西是不可预知的。
    fmt.Println(<-c, <-c, <-c) // channel在右边的时候,<-是读操作。

    cs := make(chan string)       // 操作string的channel
    cc := make(chan chan string)  // 操作channel的channel
    go func() { c <- 84 }()       // 开始一个goroutine来发送一个新的数字
    go func() { cs <- "wordy" }() // 发送给cs
    // Select类似于switch,但是每个case包括一个channel操作。
    // 它随机选择一个准备好通讯的case。
    select {
    case i := <-c: // 从channel接收的值可以赋给其他变量
        fmt.Println("这是……", i)
    case <-cs: // 或者直接丢弃
        fmt.Println("这是个字符串!")
    case <-cc: // 空的,还没作好通讯的准备
        fmt.Println("别瞎想")
    }
    // 上面c或者cs的值被取到,其中一个goroutine结束,另外一个一直阻塞。

    learnWebProgramming() // Go很适合web编程,我知道你也想学!
}

// http包中的一个简单的函数就可以开启web服务器。
func learnWebProgramming() {
    // ListenAndServe第一个参数指定了监听端口,第二个参数是一个接口,特定是http.Handler。
    go func() {
        err := http.ListenAndServe(":8080", pair{})
        fmt.Println(err) // 不要无视错误。
    }()

    requestServer()
}

// 使pair实现http.Handler接口的ServeHTTP方法。
func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 使用http.ResponseWriter返回数据
    w.Write([]byte("Y分钟golang速成!"))
}

func requestServer() {
    resp, err := http.Get("http://localhost:8080")
    fmt.Println(err)
    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    fmt.Printf("\n服务器消息: `%s`", string(body))
}