前言
Go语言起源
Go语言项目
- "软件的复杂性是乘法级相关的-----Rob Pike"
- 简洁的设计需要在工作开始的时候舍弃不必要的想法,并且在软件的生命周期内严格区别好的改变和坏的改变。通过足够的努力,一个好的改变可以在不破坏原有完整概念的前提下保持自适应,正如Fred Brooks所说的“概念完整性”;而一个坏的改变则不能达到这个效果,它们仅仅是通过肤浅的和简单的妥协来破坏原有设计的一致性。只有通过简洁的设计,才能让一个系统保持稳定、安全和持续的进化。
本书的组织
基础
- 第一章包含了本教程的基本结构,通过十几个程序介绍了用Go语言如何实现类似读写文件、文本格式化、创建图像、网络客户端和服务器通讯等日常工作。
- 第二章描述了Go语言程序的基本元素结构、变量、新类型定义、包和文件、以及作用域等概念。
- 第三章讨论了数字、布尔值、字符串和常量,并演示了如何显示和处理Unicode字符。
- 第四章描述了复合类型,从简单的数组、字典、切片到动态列表。
- 第五章涵盖了函数,并讨论了错误处理、panic和recover,还有defer语句。
第一章到第五章是基础部分,主流命令式编程语言这部分都类似。个别之处,Go语言有自己特色的语法和风格,但是大多数程序员能很快适应。其余章节是Go语言特有的:方法、接口、并发、包、测试和反射等语言特性。
进阶
- 第八章讨论了基于顺序通信进程(CSP)概念的并发编程,使用goroutines和channels处理并发编程。
- 第九章则讨论了传统的基于共享变量的并发编程。
- 第十章描述了包机制和包的组织结构。这一章还展示了如何有效地利用Go自带的工具,使用单个命令完成编译、测试、基准测试、代码格式化、文档以及其他诸多任务。
- 第十一章讨论了单元测试,Go语言的工具和标准库中集成了轻量级的测试功能,避免了强大但复杂的测试框架。测试库提供了一些基本构件,必要时可以用来构建复杂的测试构件。
- 第十二章讨论了反射,一种程序在运行期间审视自己的能力。反射是一个强大的编程工具,不过要谨慎地使用;这一章利用反射机制实现一些重要的Go语言库函数, 展示了反射的强大用法。第十三章解释了底层编程的细节,在必要时,可以使用unsafe包绕过Go语言安全的类型系统。
入门
Hello, World
尝试用100中方法打印出Hello, World
哈哈!
package main
func main() {
print("Hello, 世界") //Go语言原生支持Unicode,它可以处理全世界任何语言的文本。
}
命令行参数
os包以跨平台的方式,提供了一些与操作系统交互的函数和变量。程序的命令行参数可从os包的Args变量获取;os包外部使用os.Args访问该变量。
查找重复的行
-
bufio
包,它使处理输入和输出方便又高效。Scanner类型是该包最有用的特性之一,它读取输入并将其拆成行或单词;通常是处理行形式的输入最简单的方法。 - 格式化verb
%d 十进制整数 %x, %o, %b 十六进制,八进制,二进制整数。 %f, %g, %e 浮点数: 3.141593 3.141592653589793 3.141593e+00 %t 布尔:true或false %c 字符(rune) (Unicode码点) %s 字符串 %q 带双引号的字符串"abc"或带单引号的字符'c' %v 变量的自然形式(natural format) %T 变量的类型 %% 字面上的百分号标志(无操作数)
ioutil.ReadFile(filename)
函数返回一个字节切片(byte slice)
GIF动画
- 生成的图形名字叫利萨如图形(Lissajous figures)
获取URL
http.Get(url)
ioutil.ReadAll(resp.Body)
并发获取多个URL
goroutine和channel
Web服务
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", handler) // each request calls handler
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
// handler echoes the Path component of the requested URL.
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "URL.Path = %q\n", r.URL.Path)
}
- 如果你的请求pattern是以/结尾,那么所有以该url为前缀的url都会被这条规则匹配。
要点
-
控制流
Go语言并不需要显式地在每一个case后写breakswitch coinflip() { case "heads": heads++ case "tails": tails++ default: fmt.Println("landed on edge!")
如果你想要相邻的几个case都执行同一逻辑的话,需要自己显式地写上一个fallthrough语句来覆盖这种默认行为。 - 命名类型
-
指针
指针是一种直接存储了变量的内存地址的数据类型。指针是可见的内存地址,&操作符可以返回一个变量的内存地址,并且*操作符可以获取指针指向的变量内容,但是在Go语言里没有指针运算,也就是不能像c语言里可以对指针进行加或减操作。 -
方法和接口
Go语言里的方法可以被关联到任意一种命名类型。 - 包
- 注释
程序结构
命名
- 25个关键字
break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var
- 30多个预定义的名字
内建常量: true false iota nil 内建类型: int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr float32 float64 complex128 complex64 bool byte rune string error 内建函数: make len cap new append copy close delete complex real imag panic recover
- Go语言程序员推荐使用 驼峰式 命名
声明
变量
- 简短变量声明
i := 100
- 指针
x := 1 p := &x // p, of type *int, points to x fmt.Println(*p) // "1" *p = 2 // equivalent to x = 2 fmt.Println(x) // "2"
- new函数
p := new(int) // p, *int 类型, 指向匿名的 int 变量
- 变量的生命周期
Go语言的自动垃圾收集器对编写正确的代码是一个巨大的帮助,但也并不是说你完全不用考虑内存了。你虽然不需要显式地分配和释放内存,但是要编写高效的程序你依然需要了解变量的生命周期。例如,如果将指向短生命周期对象的指针保存到具有长生命周期的对象中,特别是保存到全局变量时,会阻止对短生命周期对象的垃圾回收(从而可能影响程序的性能)。
赋值
- 元组赋值
x, y = y, x a[i], a[j] = a[j], a[i]
- 可赋值性
可赋值性的规则对于不同类型有着不同要求,对每个新类型特殊的地方我们会专门解释。对于目前我们已经讨论过的类型,它的规则是简单的:类型必须完全匹配,nil可以赋值给任何指针或引用类型的变量。常量(§3.6)则有更灵活的赋值规则,因为这样可以避免不必要的显式的类型转换。
类型
包和文件
- 导入包
"fmt" . "fmt" //省略包名 _ "fmt" //只导入
- 包的初始化
func init() { /* ... */ }
作用域
- 不要将作用域和生命周期混为一谈。声明语句的作用域对应的是一个源代码的文本区域;它是一个编译时的属性。一个变量的生命周期是指程序运行时变量存在的有效时间段,在此时间区域内它可以被程序的其他部分引用;是一个运行时的概念。
- 控制流标号,就是break、continue或goto语句后面跟着的那种标号,则是函数级的作用域。
基础数据类型
整型
8、16、32、64bit
- 运算符
二元运算符有五种优先级。在同一个优先级,使用左优先结合规则,但是使用括号可以明确优先顺序,使用括号也可以用于提升优先级*(乘) /(除) %(余) <<(左移) >>(右移) &(位运算 AND) &^(位清空 (AND NOT)) +(加) -(减) | (位运算 OR) ^(位运算 XOR) ==(等于) != (不等于) < (小于) <=(小于或等于) > (大于) >=(大于或等于) &&(AND) ||(OR)
浮点数
float32和float64
// Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan.
// License: https://creativecommons.org/licenses/by-nc-sa/4.0/
// See page 58.
//!+
// Surface computes an SVG rendering of a 3-D surface function.
package main
import (
"fmt"
"math"
)
const (
width, height = 600, 320 // canvas size in pixels
cells = 100 // number of grid cells
xyrange = 30.0 // axis ranges (-xyrange..+xyrange)
xyscale = width / 2 / xyrange // pixels per x or y unit
zscale = height * 0.4 // pixels per z unit
angle = math.Pi / 6 // angle of x, y axes (=30°)
)
var sin30, cos30 = math.Sin(angle), math.Cos(angle) // sin(30°), cos(30°)
func main() {
fmt.Printf("<svg xmlns='http://www.w3.org/2000/svg' "+
"style='stroke: grey; fill: white; stroke-width: 0.7' "+
"width='%d' height='%d'>", width, height)
for i := 0; i < cells; i++ {
for j := 0; j < cells; j++ {
ax, ay := corner(i+1, j)
bx, by := corner(i, j)
cx, cy := corner(i, j+1)
dx, dy := corner(i+1, j+1)
fmt.Printf("<polygon points='%g,%g %g,%g %g,%g %g,%g'/>\n",
ax, ay, bx, by, cx, cy, dx, dy)
}
}
fmt.Println("</svg>")
}
func corner(i, j int) (float64, float64) {
// Find point (x,y) at corner of cell (i,j).
x := xyrange * (float64(i)/cells - 0.5)
y := xyrange * (float64(j)/cells - 0.5)
// Compute surface height z.
z := f(x, y)
// Project (x,y,z) isometrically onto 2-D SVG canvas (sx,sy).
sx := width/2 + (x-y)*cos30*xyscale
sy := height/2 + (x+y)*sin30*xyscale - z*zscale
return sx, sy
}
func f(x, y float64) float64 {
r := math.Hypot(x, y) // distance from (0,0)
return math.Sin(r) / r
}
//!-
复数
Go语言提供了两种精度的复数类型:complex64和complex128,分别对应float32和float64两种浮点数精度。内置的complex函数用于构建复数,内建的real和imag函数分别返回复数的实部和虚部:
布尔型
字符串
\a 响铃
\b 退格
\f 换页
\n 换行
\r 回车
\t 制表符
\v 垂直制表符
\' 单引号 (只用在 '\'' 形式的rune符号面值中)
\" 双引号 (只用在 "..." 形式的字符串面值中)
\\ 反斜杠
- 得益于UTF8编码优良的设计,诸多字符串操作都不需要解码操作。我们可以不用解码直接测试一个字符串是否是另一个字符串的前缀:
或者是后缀测试:func HasPrefix(s, prefix string) bool { return len(s) >= len(prefix) && s[:len(prefix)] == prefix }
或者是包含子串测试:func HasSuffix(s, suffix string) bool { return len(s) >= len(suffix) && s[len(s)-len(suffix):] == suffix }
func Contains(s, substr string) bool { for i := 0; i < len(s); i++ { if HasPrefix(s[i:], substr) { return true } } return false }
package main
import "fmt"
func main() {
s := "Hello, 世界"
fmt.Printf("%s\t%d\n", s, len(s))
for i := 0; i < len(s); i++ {
fmt.Printf("%d\t%v\t%q\n", i, s[i], s[i])
}
fmt.Println("...")
for i, r := range s {
fmt.Printf("%d\t%d\t%q\n", i, r, r)
}
}
结果
Hello, 世界 13
0 72 'H'
1 101 'e'
2 108 'l'
3 108 'l'
4 111 'o'
5 44 ','
6 32 ' '
7 228 'ä'
8 184 '¸'
9 150 '\u0096'
10 231 'ç'
11 149 '\u0095'
12 140 '\u008c'
...
0 72 'H'
1 101 'e'
2 108 'l'
3 108 'l'
4 111 'o'
5 44 ','
6 32 ' '
7 19990 '世'
10 30028 '界'
- 字符串和Byte切片
标准库中有四个包对字符串处理尤为重要:bytes、strings、strconv和unicode包。- strings包提供了许多如字符串的查询、替换、比较、截断、拆分和合并等功能。
- bytes包也提供了很多类似功能的函数,但是针对和字符串有着相同结构的[]byte类型。因为字符串是只读的,因此逐步构建字符串会导致很多分配和复制。在这种情况下,使用bytes.Buffer类型将会更有效,稍后我们将展示。
- strconv包提供了布尔型、整型数、浮点数和对应字符串的相互转换,还提供了双引号转义相关的转换。
- unicode包提供了IsDigit、IsLetter、IsUpper和IsLower等类似功能,它们用于给字符分类。每个函数有一个单一的rune类型的参数,然后返回一个布尔值。而像ToUpper和ToLower之类的转换函数将用于rune字符的大小写转换。所有的这些函数都是遵循Unicode标准定义的字母、数字等分类规范。strings包也有类似的函数,它们是ToUpper和ToLower,将原始字符串的每个字符都做相应的转换,然后返回新的字符串。
- 字符串和数字的转换
常量
- iota 常量生成器
const ( _ = 1 << (10 * iota) KiB // 1024 MiB // 1048576 GiB // 1073741824 TiB // 1099511627776 (exceeds 1 << 32) PiB // 1125899906842624 EiB // 1152921504606846976 ZiB // 1180591620717411303424 (exceeds 1 << 64) YiB // 1208925819614629174706176 )
复合数据类型
数组
Slice
- Slice内存技巧
Map
在Go语言中,一个map就是一个哈希表的引用,map类型可以写为map[K]V,其中K和V分别对应key和value。
内置的make函数可以创建一个map:
ages := make(map[string]int) // mapping from strings to ints
我们也可以用map字面值的语法创建map,同时还可以指定一些最初的key/value:
ages := map[string]int{ "alice": 31, "charlie": 34,}
使用内置的delete函数可以删除元素:
delete(ages, "alice") // remove element ages["alice"]
结构体
-
结构体比较
如果结构体的全部成员都是可以比较的,那么结构体也是可以比较的 -
结构体嵌入和匿名成员
- 需要注意的是Printf函数中%v参数包含的#副词,它表示用和Go语言类似的语法打印值。对于结构体类型来说,将包含每个成员的名字。
fmt.Printf("%#v\n", w)
- 需要注意的是Printf函数中%v参数包含的#副词,它表示用和Go语言类似的语法打印值。对于结构体类型来说,将包含每个成员的名字。
JSON
json.Marshal
和json.MarshalIndent
- json处理struct未导出成员
golang的结构体里的成员的名字如果以小写字母开头,那么其他的包是无法访问的,也就是json无法访问我们的结构体里小写字母开头的成员。两种解决方法- struct的成员用大写开头,然后加tag
package main
import (
"encoding/json"
"fmt"
"log"
)
type Movie struct {
Title string
Year int `json:"released"`
Color bool `json:"color,omitempty"`
Actors []string
}
func main() {
var movies = []Movie{
{Title: "Casablanca", Year: 1942, Color: false,
Actors: []string{"Humphrey Bogart", "Ingrid Bergman"}},
{Title: "Cool Hand Luke", Year: 1967, Color: true,
Actors: []string{"Paul Newman"}},
{Title: "Bullitt", Year: 1968, Color: true,
Actors: []string{"Steve McQueen", "Jacqueline Bisset"}},
// ...
}
data, err := json.Marshal(movies) // json.MarshalIndent(struct, "", " ")
if err != nil {
log.Fatalf("JSON marshaling failed: %s", err)
}
fmt.Printf("%s\n", data)
}
[{"Title":"Casablanca","released":1942,"Actors":["Humphrey Bogart","Ingrid Bergman"]},{"Title":"Cool Hand Luke","released":1967,"color":true,"Actors":["Paul Newman"]},{"Title":"Bullitt","released":1968,"color":true,"Actors":["Steve McQueen","Jacqueline Bisset"]}]
- 实现json.Marshaler接口
```
package main
import (
"encoding/json"
"fmt"
)
func main() {
var s S
s.a = 5
s.b[0] = 3.123
s.b[1] = 111.11
s.b[2] = 1234.123
s.c = "hello"
s.d[0] = 0x55
j, _ := json.Marshal(s)
fmt.Println(string(j))
}
type S struct {
a int
b [4]float32
c string
d [12]byte
}
func (this S) MarshalJSON() ([]byte, error) {
return json.MarshalIndent(map[string]interface{}{ // json.MarshalIndent(struct, "", " ")
"a": this.a,
"b": this.b,
"c": this.c,
"d": this.d,
}, "", " ")
}
```
{"a":5,"b":[3.123,111.11,1234.123,0],"c":"hello","d":[85,0,0,0,0,0,0,0,0,0,0,0]}
文本和HTML模板
函数
函数声明
递归
多返回值
错误
函数值
匿名函数
可变参数
Deferred函数
Panic异常
- 一般而言,当panic异常发生时,程序会中断运行,并立即执行在该goroutine(可以先理解成线程,在第8章会详细介绍)中被延迟的函数(defer 机制)
Recover捕获异常
方法
方法声明
基于指针对象的方法
通过嵌入结构体来扩展类型
方法值和方法表达式
Bit数组
封装
接口
当设计一个新的包时,新的Go程序员总是通过创建一个接口的集合开始和后面定义满足它们的具体类型。这种方式的结果就是有很多的接口,它们中的每一个仅只有一个实现。不要再这么做了。这种接口是不必要的抽象;它们也有一个运行时损耗。你可以使用导出机制(§6.6)来限制一个类型的方法或一个结构体的字段是否在包外可见。接口只有当有两个或两个以上的具体类型必须以相同的方式进行处理时才需要。
当一个接口只被一个单一的具体类型实现时有一个例外,就是由于它的依赖,这个具体类型不能和这个接口存在在一个相同的包中。这种情况下,一个接口是解耦这两个包的一个好好方式。
因为在Go语言中只有当两个或更多的类型实现一个接口时才使用接口,它们必定会从任意特定的实现细节中抽象出来。结果就是有更少和更简单方法(经常和io.Writer或 fmt.Stringer一样只有一个)的更小的接口。当新的类型出现时,小的接口更容易满足。对于接口设计的一个好的标准就是 ask only for what you need(只考虑你需要的东西)
Goroutines和Channels
基于共享变量的并发
包和工具
工具
测试
go test
反射
有疑问加站长微信联系(非本文作者)