Go 程序语言设计笔记

0x00 正文前的 bb

第二次面试周期终于结束了,经历了被某公司放鸽子的社会毒打后,第二波投递广撒网,终于顺利结束了秋招。

有了工作就有了新的学习目标。准备接下来这段时间补充一下 go 语言的基础,买了一本被誉为 Go 语言圣经 的 《Go 程序设计语言》,在此记录下学习笔记 (这里自己只记了一些 Go 比较特殊的点或者比较难懂的点),供自己和有需要的同学参考。

0x01 1 - 5 章 语言基础

1、Go 原生的支持 Unicode,所以理论上它可以处理任何国家的语言。

2、go run [源文件] 指令是将某个 go 文件编译、链接、然后运行生成可执行文件。

go build [源文件] 构建出一个可执行的二进制文件。

3、Go 不需要在语句或者声明后面使用分号结尾【跟在特定符号后面的换行符被转换为分号】,除非有多个语句或声明出现在同一行。

4、{· 符必须和 func 关键字放在同一行。

5、gofmt 是一个将代码以标准格式重写的命令行工具(Go 自带),默认重写当前目录所有文件。

6、++i 或者 --i 在 Go 里面是不合法的,其只支持后缀。

7、for 循环是 Go 语言唯一的循环语句。

8、Go 不允许存在无用的临时变量,一般使用空标识符 _ 来处理需要丢弃的临时变量,如下面的 for range 循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import (
"fmt"
"os"
)
func main() {
s, sep := "", ""
// range 产生一对值:索引和这个索引处元素的值
for _, arg := range os.Args[1:] {
s += sep + arg
sep = " "
}
fmt.Println(s)
}

9、:= 这种短变量声明通常只在函数内使用,不适合当做全局变量等包级别的变量声明

10、map 里的键的迭代顺序不是固定的,通常是随机的,每次运行都不一致。这是有意设计的,以防止程序依赖某种特定的序列。[具有一定的随机安全性]

11、函数和其他包级别的实体可以以任意次序声明。

12、常用的 http 请求代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package main

import(
"fmt"
"io/ioutil"
"net/http"
"os"
)
func main() {
for _, url := range os.Args[1:] {
resp, err := http.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "reading %s: %v\n", url, err)
os.Exit(1)
}
fmt.Printf("%s", b)
}
}

13、包名本身总是以小写字母组成。实体的第一个字母的大小写决定其可见性是否跨包(首字母大写大写跨包可见,类比 Public 属性)。

14、命名习惯用驼峰(大小都可),特殊单词保持大小写同步,譬如 HTML, ASCII 等。

15、包级别的实体名字对同一个包里的所有源文件都可见。

16、数组和结构体这种复合变量的默认成员初始值都是 0。

17、包级别的初始化在 main 函数开始之前进行。

18、短变量声明最少声明一个新变量,否则编译无法通过。

1
2
3
f, err := os.Open(file)

f, err := os.Create(file) // 编译错误:没有新的变量

19、不是所有值都有地址,但是所有变量都有。

20、new 函数是内置的创建变量的函数,例子如下:

1
2
3
4
p := new(int) // *int 类型的 p,指向未命名的 int 变量
fmt.Println(*p) // 输出 0
*p = 2
fmt.Println(*p) // 输出 2

但其是一个预定义函数,不是关键字,可以在代码 的其他地方重新定义并利用。

21、nil 可以赋值给任何接口变量或者引用类型。

22、如果两个类型具有相同的底层类型或者二者都是指向相同底层类型变量的未命名指针类型,则二者是可以相互转换的。

23、包级别声明可以被同一个包的任何文件引用。导入的包是文件级别的,只能在被导入的文件内才能使用。

24、 Go 的数据类型分为四大类:基础类型,聚合类型【数组,结构体】,引用类型【指针,slice,map,函数,通道】,接口类型。

25、rune 类型是 int32 类型的同义词,常常用于指明一个值是 Unicode 码点。同样,byte 类型和 uint8 类型是同义词,强调一个值是原始数据,而非量值。

26、Go 中浮点型数字转换为整型数字会舍弃小数部分。

27、float32 能精确表示的正整数范围有限。

28、内置的 complex 函数根据给定的实部和虚部创建复数,而内置的 real 函数提取复数的实部,函数 imag 提取复数的虚部。

29、布尔值无法隐式地转换为数值。

30、内置的 len 函数返回字符串的字节数,并非文本数。譬如 中国 这个字符串的函数返回结果是 6。

31、Go 中字符串下标访问 a[0] 返回的是对应索引的 unicode 编码。

32、字符串不可变,所以字符串内部的值不能修改:

1
s[0] = "L" // 编译错误:s[0] 无法赋值

33、字符串不可变意味着两个字符串能安全地共用同一段底层内存。类似,字符串及其子串可以安全地共享数据,这两种情况都没有分配新内存,开销低廉。

34、Go 中的原生字符串使用反引号包围。

35、bytes.Buffer 是高效处理字节 slice 的类型。

36、strconv,Itoa 可以将整数转换为字符串,或者使用 fmt.Sprintf() 函数。

37、Go 语言中,只有大小不明确的 int 类型,却不存在大小不确定的 float 类型和 complex 类型,原因是,如果浮点型数据的大小不明,就很难写出正确的数值算法。

38、若字符串包含一个字节数组,创建后它就无法改变。相反,字节 slice 的元素允许随意修改。

39、常见不易记住的 Printf 的转义值:

1
2
3
4
%t	==> 布尔型
%q ==> 带引号的字符串
%v ==> 内置格式的任何值
%T ==> 任何值的类型

40、由于数组长度固定,所以在 Go 里很少直接使用数组。

41、数组长度中出现省略号,此时数组长度由初始化数组的元素个数决定。

42、数组的长度也是数组类型的一部分,所以 [3]int[4]int 是两种不同的数组类型,也不能拿来比较了。

43、slice 表示一个拥有相同类型元素的可变长度的序列。通常写为 []T。Go 内置函数 len(), cap 用来返回 slice 的长度和容量。

44、和数组不同的是,slice 无法比较。标准库里面提供了针对字节 slice 的比较 bytes.Equal,但是对于其他类型的 slice,我们需要自己设计方法进行比较。

45、slice 类型的 0 值是 nil,是 slice 唯一允许比较的对象。

46、由于无法保证 append() 操作后 slice 对应的底层数组是否改变,我们一般在 append() 操作后显示的再次赋给被操作的 slice。

1
runes = append(runes, r)

47、map 元素不是一个变量,不能获取它的地址。其中一个原因就是 map 的动态增长可能会使原有元素的地址改变。

48、向零值 map 设置值会导致错误。

49、如果 map 中的键不存在,会返回 map 值类型的零值。一般用两个值来接收 map 的元素访问:

1
age, ok := ages["bob"] // 第二个值返回的是布尔值,用来报告该元素是否存在

50、访问成员变量时,点号同样可以用在结构体指针上。

51、一个结构体可以同时包含可导出和不可导出的成员变量。

52、聚合类型不能包含自己,可以包含自己的类型指针。

53、不可以在一个结构体里面定义两个相同类型的匿名成员。

54、为了避免 html/template 对常见特殊字符的转义,可以使用 template.HTML 类型的字符串避免模板的自动转义。

55、函数多个形参的类型如果一致的话,那么函数声明的时候只需要写一次即可。

56、在 Go 语言中,如果函数声明没有函数体,说明这个函数使用除了 Go 以外的语言实现。

57、Go 语言实现了可变长度的栈,栈的大小会随着使用而增长,可达到 1GB 左右的上下。所以 Go 可以放心安全的使用深度递归不用担心一般的栈溢出问题。

58、函数如果有命名的返回值,可以使用 裸返回(即只需要 return 关键字即可,返回参数顺序与函数声明的返回值序列一致)。

59、Go 程序使用通常的控制流机制(if 和 return 语句)应对错误。