|
| 1 | +--- |
| 2 | +title: GoPL读书笔记1 |
| 3 | +categories: |
| 4 | +- Go |
| 5 | +--- |
| 6 | + |
| 7 | + |
| 8 | +### Ch.1 |
| 9 | +第一章是例子oriented的,内容相对散乱。 |
| 10 | +1. Go是编译型语言。使用`go run`运行程序,`go build`生成binary。 |
| 11 | +2. Go看起来让人想到Rust那个跟麦格教授一样严格的编译器。import的package必须使用,不需要的变量应该使用`_`表示空标识符。Go并没有相对温和~~可以忽视~~的warning,要么是error要么编译通过,可以说force了良好的编程习惯? |
| 12 | +3. 同样的严格在formatting上也有所体现。gofmt会把所有代码重写为唯一的标准格式,虽然非常强硬,但是也使得自动化的代码工具成为了可能。 |
| 13 | +4. Go只有for一种循环,但它可以通过条件的缺省做到多种用法。 |
| 14 | + |
| 15 | +```go |
| 16 | +//常规循环,不需要加括号 |
| 17 | +for initialization; condition; post { |
| 18 | + // ... |
| 19 | +} |
| 20 | + |
| 21 | +// 省略初始化和post,就可以变为while loop |
| 22 | +for condition { |
| 23 | + // ... |
| 24 | +} |
| 25 | +// 省略全部条件,则变为死循环。当然可以使用break/return跳出循环 |
| 26 | +for { |
| 27 | + // ... |
| 28 | +} |
| 29 | + |
| 30 | +//使用range关键字,成为for each loop |
| 31 | +for idx, n := range arr { |
| 32 | + // ... |
| 33 | +} |
| 34 | +``` |
| 35 | + |
| 36 | +5. Go最标志性的特性应该就在于concurrent programming了。通过`go`关键字调用函数,可以创建goroutine进行并行操作。goroutine之间通过channel进行thread safe的通信。下面这个例子并行地将两个等长的数组element-wise地相加,通过channel传回结果并且print。 |
| 37 | + |
| 38 | +```go |
| 39 | +func add(a, b int, result chan int) int { |
| 40 | + result <- a + b |
| 41 | +} |
| 42 | + |
| 43 | +func main() { |
| 44 | + arr1 = ... |
| 45 | + arr2 = ... |
| 46 | + result := make(chan int) |
| 47 | + for i, _ := range arr1 { |
| 48 | + go add(arr1[i], arr2[i], result) |
| 49 | + fmt.Println(result) |
| 50 | + } |
| 51 | +} |
| 52 | +``` |
| 53 | + |
| 54 | +### Ch.2 |
| 55 | +1. Go有25个关键字和30多个预声明的常量/类型/函数。前者无法作为名称,只能用在语法所指定的地方,但后者可以被重定义(注意作用域)。 |
| 56 | +2. 变量的命名风格:大写字母而非下划线,缩写词使用同样的大小写。 |
| 57 | +3. Go程序的基本结构包括:package声明,import声明,包level的变量/函数等声明。 |
| 58 | + |
| 59 | +```go |
| 60 | +package main |
| 61 | + |
| 62 | +import ( |
| 63 | + "fmt" |
| 64 | + "io" |
| 65 | + ... |
| 66 | +) |
| 67 | + |
| 68 | +const c = ... |
| 69 | + |
| 70 | +func foo() { |
| 71 | + // ... |
| 72 | +} |
| 73 | + |
| 74 | +func main() { |
| 75 | + // ... |
| 76 | +} |
| 77 | +``` |
| 78 | + |
| 79 | +4. 函数的声明包括:函数的名字,参数列表,返回值列表,函数体。其中返回值可以省略。 |
| 80 | + |
| 81 | +```go |
| 82 | +func fToC(f float64) float64 { |
| 83 | + // body |
| 84 | +} |
| 85 | +``` |
| 86 | + |
| 87 | +5. 变量的声明的通用格式是: |
| 88 | + |
| 89 | +```go |
| 90 | +var name type = expr |
| 91 | +``` |
| 92 | +可以省略type,让类型由type inference推导;也可以省略初始值,那么变量会被初始化为对应类型的零值(0,false,空字符串,nil指针等等),这被称为零值机制。零值机制保证了所有变量都被良好地初始化了,减少了undefined behavior。我们也可以使用短声明来声明变量: |
| 93 | + |
| 94 | +```go |
| 95 | +name := expr |
| 96 | +``` |
| 97 | +这种声明对于局部变量比较常见。需要注意`:=`代表声明而`=`代表赋值,我们也可以进行多重赋值和多重声明: |
| 98 | + |
| 99 | +```go |
| 100 | +var b, f, s = true, 2.3, "four" //会被分别推导为bool, float64, string |
| 101 | +i, j := 0, 1 |
| 102 | +i, j = j, i //交换i,j |
| 103 | +``` |
| 104 | +可以看到Go的多重赋值可以很方便地进行交换或者更多的操作。我们还可以在短声明里同时混杂声明和赋值,但是当我们使用`:=`的时候,语句里必须至少有一个声明才行。 |
| 105 | + |
| 106 | +```go |
| 107 | +f, err := os.Open(f1) |
| 108 | +f, err := os.Open(f2) // 编译不通过,因为没有声明新的变量,应该使用=而非:= |
| 109 | +``` |
| 110 | + |
| 111 | +6. 指针的声明可以通过普通声明或者是`new`函数。 |
| 112 | + |
| 113 | +```go |
| 114 | +x := 1 |
| 115 | +p1 := &x |
| 116 | +p2 := new(int) |
| 117 | +*p2 = 2 |
| 118 | +``` |
| 119 | + |
| 120 | +7. 类型的声明是包级别的,对整个package都可见。如果类型的名字大写,那么其他的包也可以导入这个类型。 |
| 121 | + |
| 122 | +```go |
| 123 | +type name underlying-type |
| 124 | +``` |
| 125 | +自定义的类型之间可以显式地转换,但不可以直接进行运算,就算它们本来属于一样的底层类型也不可以。 |
| 126 | + |
| 127 | +```go |
| 128 | +type Celsius float64 |
| 129 | +type Fahrenheit float64 |
| 130 | + |
| 131 | +var f Fahrenheit |
| 132 | +var c Celsius |
| 133 | +f = c*9/5 +32 // error |
| 134 | +f = Fahrenheit(c*9/5 +32) // ok |
| 135 | +``` |
| 136 | + |
| 137 | +8. Go中的包(package)和其他语言中类似的概念相似。它支持模块化、封装、提供了独立的namespace。我们通过命名的大小写控制可见性:大写字母开头的indentifier对外可见。包的初始化会从包级别变量开始,并且可以自动解析dependency: |
| 138 | + |
| 139 | +```go |
| 140 | +var a = b + c // 最后再初始化a |
| 141 | +var b = f() // 然后调用f来初始化b |
| 142 | +var c = 1 // 首先初始化c |
| 143 | +func f() int { return c + 1 } |
| 144 | +``` |
| 145 | +我们还可以通过`init`函数来对包进行初始化。`init`函数可以在同一个文件里声明多次,不可以被调用或者引用,只会在程序启动的时候按照声明的顺序执行。 |
| 146 | + |
| 147 | +9. 作用域和生命周期的区别:生命周期是运行时属性,而作用域是编译时属性。像其他语言一样,Go从内到外逐级在词法块(block)里寻找名字的声明,并且引用最近的一个。特别注意的是如果使用`:=`进行多重声明,就不会对外部变量赋值而是创建一个新的变量: |
| 148 | + |
| 149 | +```go |
| 150 | +var cwd string // global |
| 151 | + |
| 152 | +func init() { |
| 153 | + cwd, err := os.Getwd() |
| 154 | + // ... |
| 155 | +} |
| 156 | +``` |
| 157 | +在上述init函数中,不会给全局变量cwd赋值,而是在函数内部新建了一个cwd变量,同时还会导致未使用cwd的编译错误。最简单的方法是不要使用`:=`了: |
| 158 | + |
| 159 | +```go |
| 160 | +var cwd string // global |
| 161 | + |
| 162 | +func init() { |
| 163 | + var err error |
| 164 | + cwd, err = os.Getwd() |
| 165 | + // ... |
| 166 | +} |
| 167 | +``` |
| 168 | +Placeholder |
0 commit comments