本文是一篇翻译,原文地址是:https://learnxinyminutes.com/docs/go/
强烈建议读原文,而不是在这里浪费时间
可以从 https://learnxinyminutes.com/docs/files/learngo.go 获取到代码
go 语言是为了解决问题被发明的,虽然使用 go 不是计算机科学界最新的趋势,但是却是解决真实世界问题的最新最快的方法(译注:go不是最新的,也不是最快的,这两者go都表现不错,是某种折中)
go 与命令式的静态类型语言有些相似的概念。它编译速度很快,运行速度也很快,通过增加一些容易理解的并发能力,可以充分利用当今的多核 CPU,也有很多功能是针对大规模程序设计的。
go 有一个很棒的标准库,社区也非常的活跃。
强烈建议读原文,而不是在这里浪费时间
可以从 https://learnxinyminutes.com/docs/files/learngo.go 获取到代码
go 语言是为了解决问题被发明的,虽然使用 go 不是计算机科学界最新的趋势,但是却是解决真实世界问题的最新最快的方法(译注:go不是最新的,也不是最快的,这两者go都表现不错,是某种折中)
go 与命令式的静态类型语言有些相似的概念。它编译速度很快,运行速度也很快,通过增加一些容易理解的并发能力,可以充分利用当今的多核 CPU,也有很多功能是针对大规模程序设计的。
go 有一个很棒的标准库,社区也非常的活跃。
// 这是单行注释
/* 这是多行
注释 */
// 每个源文件都从一行 package 语句开始
// main 是一个特殊的名字,声明这是一个可执行程序,而不是一个库
package main
// import 声明这个文件内使用到的包 (译注:包名是个字符串,需要引号)
import (
"fmt" // go 标准库里的一个包
"io/ioutil" // 一些 IO 工具函数
m "math" // 数学库,本文件可以用 m 这个别名来代替使用
"net/http" // 是的,这是一个 web server
"os" // OS 相关函数,比如和文件系统打交道
"strconv" // 字符串转换
)
// 函数定义。main 是特别的,它是一个程序的入口
// 不管你是否喜欢,go 使用大括号表示开始和结束
func main() {
// Println 向 stdout 输出一行
// 使用 `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 := 'Σ' // 符号类型,int32 的别名,保存unicode的编码点。
f := 3.14195 // 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} // 用 5 个固定元素初始化的数组。
// 它们的值是 3, 1, 5,10 和 100
// 分片可以变长。数组和分片各有优点
// 但是使用分片要更常见
s3 := []int{4, 5, 9} // 和 a5 做一下对比,这里没有省略号
s4 := make([]int, 4) // 分配 4 个 int 的分片,全部初始化为 0
var d2 [][]float64 // 只是申明,什么都没有分配
bs := []byte("a slice") // 类型转换语法
// 因为分片是变长的,所以可以按需增加
// 内置函数 append() 用来为分片增加元素
// 第一个元素是需要增加元素的分片。通常,
// 数组变量就地更新,如下所示。
s := []int{1, 2, 3} // Result is a slice of length 3.
s = append(s, 4, 5, 6) // Added 3 elements. Slice now has length of 6.
fmt.Println(s) // Updated slice is now [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() // 声明指向 int 的指针 p,q
fmt.Println(*p, *q) // * 后接着指针值,这会打印两个 int 值
// map 是动态增长的关系型数据类型,像别的语言的hash表或是字典类型
m := map[string]int{"three": 3, "four": 4}
m["one"] = 1
// 未被使用的变量在 go 里是一种错误
// `_` 让你 “使用” 变量,但实际上丢弃该值
_, _, _, _, _, _, _, _, _, _ = str, s2, g, f, u, pi, n, a5, s4, bs
// 通常你可以通过它忽略一个函数的返回值
// 比如,在一个快糙猛的脚本里,你可能忽略 os.Create 返回的错误
// 期望文件总是会被创建成功
file, _ := os.Create("output.txt")
fmt.Fprint(file, "This is how you write to a file, by the way")
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 有完整的垃圾回收。它有指针,但是不允许指针运算
// 你可能在空指针上犯错,但是不会因为指针的增加运算
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 语句需要大括号,但是不需要小括号
if true {
fmt.Println("told ya")
}
// 命令行命令 “go fmt” 给出了格式标准
if false {
// Pout.
} else {
// Gloat.
}
// 多级 if 可以用 switch 语句
x := 42.0
switch x {
case 0:
case 1:
case 42:
// 这里不会("fallthrough")向下执行
/*
有一个 `fallthrough` 的关键字,参考:
https://github.com/golang/go/wiki/Switch#fall-through
*/
case 43:
// 不会运行到这里
default:
// 默认行为是可选的
}
// 和 if 一样,for 也不使用小括号
// 在 for 和 if 里声明的变量都属于它们自己的本地作用域
for x := 0; x < 3; x++ { // ++ 是语句(statement)
fmt.Println("iteration", x)
}
// 这里 x == 42
// for 是 go 里唯一的循环语句,但是有别的可选的形式
for { // 死循环
break // 开个玩笑
continue // 不能运行到这里
}
// 你可以使用 range 来迭代访问数组,slice,字符串,map,或者是 channel
// range 返回一个(channel)或两个值(数组,slice,字符串,map或是一个channel)
for key, value := range map[string]int{"one": 1, "two": 2, "three": 3} {
// 对 map 里的每对值,打印键和值
fmt.Printf("key=%s, value=%d\n", key, value)
}
// 如果你只需要值,用下划线作为键
for _, name := range []string{"Bob", "Bill", "Joe"} {
fmt.Printf("Hello, %s\n", name)
}
// 和 for 一样,在 if 语句中的 := 表示先给 y 声明和赋值
// 然后测试 y > x
if y := expensiveComputation(); y > x {
x = y
}
// 函数字面值(function literal)是闭包
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
// 而且,函数字面值(function literal)可能被定义和内联调用
// 只要满足以下条件,就可以作为函数参数:
// a) 函数字面值(function literal)立即被调用
// b) 返回值和参数类型匹配
fmt.Println("Add + double two numbers: ",
func(a, b int) int {
return (a + b) * 2
}(10, 2)) // 用参数 10 和 2 调用
// => Add + double two numbers: 24
// 当你需要的时候,你会喜欢它的
goto love
love:
learnFunctionFactory() // 函数返回函数是 fun(3)(3)
learnDefer() // 一个重要关键字的速成教程
learnInterfaces() // 好东西即将登场!
}
func learnFunctionFactory() {
// 下面两个是等同的,第二种更实用
fmt.Println(sentenceFactory("summer")("A beautiful", "day!"))
d := sentenceFactory("summer")
fmt.Println(d("A beautiful", "day!"))
fmt.Println(d("A lazy", "afternoon!"))
}
// 装饰器模式在别的语言里很常见,在 go 里也可用带参数的函数字面值(function literal)实现
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 fmt.Println("deferred statements execute in reverse (LIFO) order.")
defer fmt.Println("\nThis line is being printed first because")
// defer 经常用来关闭文件,这样打开文件和关闭文件的功能语句就会在一起
return true
}
// 用一个方法 String 来定义一个 Stringer 的接口类型
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 称为 "receiver"
// Sprintf 是 fmt 里另一个 public 函数
// 引用 p 的成员使用点语法
return fmt.Sprintf("(%d, %d)", p.x, p.y)
}
func learnInterfaces() {
// 大括号语法表示的是一个 "struct literal"。
// 它会得到一个初始化后的结构体。:= 语法声明和初始化 p 为这个结构体
p := pair{3, 4}
fmt.Println(p.String()) // 调用 pair 类型的 p 的方法 String
var i Stringer // 声明 Stringer 接口
i = p // 合法有效,又因 pair 实现了 Stringer
// 调用 Stringer 类型的 i 的 String 方法,输出和上面相同
fmt.Println(i.String())
// fmt 包里的函数会调用需要打印的对象本身的 String 方法
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,因为 1 不在 map 里
fmt.Println("no one there")
} else {
fmt.Print(x) // 如果在 map 里,x 就是对应的值.
}
// 错误值不仅仅表明简单的 “ok” 而是关于错误的更多信息
if _, err := strconv.Atoi("non-int"); err != nil { // _ 丢弃返回值
// 打印 'strconv.ParseInt: parsing "non-int": invalid syntax'
fmt.Println(err)
}
// 我们等会儿会继续回到接口的内容。于此同时,我们学习并发
learnConcurrency()
}
// c 是一个 channel,一个并发安全的通讯对象
// c is a channel, a concurrency-safe communication object.
func inc(i int, c chan int) {
c <- i + 1 // 当一个 channel 出现在左边时,<- 表示一个发送操作符
}
// 我们使用 inc 来并发的让一些数字增加
func learnConcurrency() {
//和先前创建 slice 相同的 make 函数。make 创建并初始化 slice,map 和 channel
c := make(chan int)
// 开始 3 个并发的 goroutine。数字会同时增加,
// 如果机器有能力并且正确得到了正确的配置,可能时并行运行
// 3 个数字都会发送到相同的 channel
go inc(0, c) // go 是一个语句,但是会开启一个 goroutine
go inc(10, c)
go inc(-805, c)
// 从 channel 中读取 3 个结果值,并打印出来
// 结果到达的顺序是不确定的!
fmt.Println(<-c, <-c, <-c) // channel 在右边, <- 表示一个接收操作符
cs := make(chan string) // 另一个channel,这个表示字符串
ccs := make(chan chan string) // 这个表示一个字符串 channel
go func() { c <- 84 }() // 开启一个简单发送值的 channel
go func() { cs <- "wordy" }() // 同上,不过是针对 cs
// select 和 switch 有相似的语法,除了每个分支有个 channel 操作
// select 会在通讯就绪的分支中随机挑选一个
select {
case i := <-c: // 收到的值可以赋给变量
fmt.Printf("it's a %T", i)
case <-cs: // 或者丢弃收到的值
fmt.Println("it's a string")
case <-ccs: // 空的 channel,通讯未就绪
fmt.Println("didn't happen.")
}
// 到此,c 和 cs 里的值已经被取走,上面启动的两个 goroutine 中的一个
// 已经完成,其它的继续阻塞
learnWebProgramming() // 网络编程 go 做到了,你肯定也想。
}
// http 包里的一个函数就可以实现 web 服务
func learnWebProgramming() {
// ListenAndServe的第一个参数是监听地址
// 第二个参数是一个特定的 http.Handler 接口
go func() {
err := http.ListenAndServe(":8080", pair{})
fmt.Println(err) // 不要忽略错误
}()
requestServer()
}
// 实现 http.Handler 唯一接口 ServeHTTP,使 pair 成它成为一个 http.Handler
func (p pair) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// 使用 http.ResponseWriter 来返回数据
w.Write([]byte("You learned Go in Y minutes!"))
}
func requestServer() {
resp, err := http.Get("http://localhost:8080")
fmt.Println(err)
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Printf("\nWebserver said: `%s`", string(body))
}