The Go Programming Language
本系列将从《The Go Programming Language》的第二章开始,记录一些阅读过程中遇到的值得记录的知识点,也希望可以坚持下去,读完整本书
Chapter 2
Program Structure
Names
- 在函数外定义的实体(函数本身或者变量),会根据其首字母大小来决定其访问权限,小写字母开头的实体只能在包内访问,大写字母开头的会被
export
,也即其他包中可以通过import
的方式访问到,如fmt.Printf
。包名本身始终是小写的 - 使用驼峰式而不是下划线
Declarations
四种主要的声明
- var
- const
- type
- func
函数没有
void
返回选项,如果什么都不返回,直接为空即可
Variables
使用
var
来声明变量1
var name type = expression
可以省略类型
type
或者不赋值,省略右侧的expression
,省略类型会进行自动的类型推断,省略表达式会自动将变量赋值为其类型所对应的零值,在go中没有所谓的未初始化的变量可以在一条声明语句中初始化多个不同类型的变量
1
2var i, j, k int
var b, f, s = true, 2.3, "four"使用简写声明(
short variable declaration
)来声明或者赋值局部变量1
2anim := gif.GIF{LoopCount: nframes}
freq := rand.Float64() * 3.0局部变量基本上都是使用这种方法进行声明, 只有在某些情况下才需要使用
var
(Avar
declaration tends to be reserved for local variables that need an explicit type that differs from that of the initializer expression, or for when the variable will be assigned a value later and its inital value is unimportant)如1
2
3
4
5i := 100
var boiling float64 = 100 // 类型与右侧不一致
var names []string // 仅仅是声明
var err error
var p Point同时注意
:=
是声明,而=
是赋值,Go中有类似于Python中的语法,称为tuple assignment
1
2i, j = j, i // 交换i和j的值
f, err := os.Open(name) // 此处注意和上面的区别使用
:=
的时候,左侧的不一定非要是新的变量,如果左侧的变量名在之前出现过,那么对该变量的操作就会是赋值,而不是新建一个新的同名变量.但至少要保证有一个新的变量被声明1
2
3
4
5f, err := os.Open(infile) // 声明了新的变量f和err
// ...
f, err := os.Creat(outfile) // 编译错误,此时f和err都已经出现过,所以没有新的变量出现,应该使用`=`而不是`:=`
out, err := os.Create(outfile) // 正确,其中out为新声明的变量,而对err是进行了赋值操作指向函数内局部变量的指针
在Go中,可以放心的返回指向函数内部变量的指针,而不必担心其指向的对象被销毁1
2
3
4
5
6
7var p = f()
func f() *int {
v := 1
return &v
} // 没毛病
fmt.Println(f() == f()) // false
// 每一次调用会产生新的值此处没有讲具体的实现细节
使用
new
函数来创建指针变量
注意new
是函数不是关键字(所以可以被覆盖),使用new
可以创建一个无名的变量并返回指向其的指针1
2
3
4
5
6p := new(int)
/*等价于
var unname int
p := &unname
*/
fmt.Println(*p) // 0使用
new
函数通常来说得到的指针应该指向不同的地址,但是对于struct{}
或者[0]int
,依赖于编译器的具体实现,可能会产生相同的地址
总的来说用到new
函数的机会其实很少,主要是因为,最常用的无名变量只有struct
,而对于struct
有更为方便的字面量来表示.
Assignments
- 元组赋值(
tuple assignment
)很多函数都会返回元组,常常是两个值,一个是返回的数据,另一个表示错误. 同时在1
2
3
4
5
6func gcd(x, y int) int {
for y != 0 {
x, y = x, x%y // 元组赋值
}
return x
}- map查找(map lookup)
- 类型推断(type assertion)
- 管道接收(channel receive)
中也会返回两个值使用1
2
3v, ok = m[key]
v, ok = x.(T)
v, ok = <-ch_
来丢弃不需要的值
- 使用等号进行比较的两个变量之间必须是可赋值的(
assignable
), 如nil
可以赋值给所有的引用变量或者接口
Type Declarations
- 为某种类型提供一个别名, 类似于C中的
typedef
,可以更方便的表达语义,如int
可以是秒数也可以是日期。这种别名常常用在包一级别中,如以下的例子注意,虽然1
2
3
4
5
6
7
8
9
10
11
12
13package tempconv
import "fmt"
type Celsius float64
type Fahrenheit float64
const (
AbsoluteZeroC Celsius = -273.15
FreezingC Celsius = 0
BoilingC Celsius = 100
)
func CToF(c Celsius) Fahrenheit { return Fahrenheit(c * 9/5 + 32) }
func FToC(f Fahrenheit) Celsius { return Celsius((f - 32) * 5 / 9)}Celsius
和Fahrenheit
本质上都是float64
类型,但是依然认定为两个不同的类型,这两个类型之间不能进行比较或者在同一个数学表达式中使用。 - 在定义一个类型之后可以为其编写特定的方法,称为the type’s methods
Packages and Files
- 包的初始化
包在初始化是会按照顺序执行包级别的变量初始化(initializing package-level variables)。而包本身的加载顺序则是通过go本身先对.go
文件进行排序决定的。对于一些简单的变量来说这样做可能没什么问题,但是对于比较复杂的情况常常使用初始化函数(init function),而不是零零散散的变量声明.1
func init() { /* ... */}
init
函数不能被调用或者引用,在包被导入的时候,init
函数会自动执行以进行初始化(init函数里面的变量应该还是作为局部变量而不是包级变量,也即init函数只能进行初始化相关工作,而不能进行变量的声明,我猜)