Go 语言学习笔记,内容来源于官方文档,翻译质量较差会逐步改进。
概述
- Go 的开发人员通常保存所有的单位在一个工作空间。
- 一个工作空间包含多个存在版本控制的仓库(例如被 git 管理的仓库)。
- 每一个仓库有一个或者多个包。
- 每一个包都是一个目录,目录中包含一个或者多个 Go 源文件。
- 包的路径直接决定了它的引入路径。
Workspaces
工作空间是一个包含下列两个目录的层次结构:
- src 包含了 Go 的源代码
- bin 包含了可执行文件
GOPATH 环境变量
GOPATH 指定了你的工作空间所在的位置。默认是一个叫做 go 的文件夹,在 UNIX 下的路径为 $HOME/go, 在 Plan 9 的路径为 $Home/go, Windows下通常是 %USERPROFILE%\go。
如果你要自定义你的工作空间,可以通过设置 GOPATH 环境变量来完成。
import paths
import paths 是用于唯一标识包的字符串。一个包的 import path 和它在本地工作空间和远程仓库中的位置相关。
标准库中的 import path 一般采用缩写,例如 “fmt” 和 “net/http”。对于你自己的包,你要选择一个 base path,来避免和未来的标准库和其他的包产生冲突。
包名
Go 源码文件第一行比如是包名
1 | package name |
按照约定,包名与导入路径的最后一个元素一致。
为了方便,Go 将 import path 的最后一个元素设置为包名,例如你的包被引入为“crypto/rot13”,则包名就是rot13。
Remote packages
import path 也可以是从远程仓库获取的路径。go 工具会根据这个地址自动的从远程仓库抓取这些包。
如果包不存在,则会用 go get 会自动执行,并将其放到指定的工作空间中(即设置的 GOPATH)。
导出名
在 Go 中, 如果一个名字以大写字母开头,那么它就是导出的。
例如,Pizza 就是个已导出名,Pi 也同样,它导出自 math 包。
在导入一个包时,你只能引用其中已导出的名字。任何“未导出”的名字在该包外均无法访问。
函数
Go 语言中函数的类型在变量名之后。
当连续两个或多个函数的已命名形参类型相同时,除最后一个类型以外,其它都可以省略。
例如:
1 | X int, y int |
Go 语言中,函数可以返回任意数量的返回值,下列的 swap 函数返回了两个字符串。
1 | package main |
Go 的返回值可以被命名,他们是定义在函数顶部的变量,也就是第二个括号就是返回值的信息,没有参数的 return 返回已命名的返回值,也就是直接返回。例如:
1 | package main |
变量
Go 语言中使用 var 来声明变量。
在函数中,可以使用 := 在类型明确的地方代替 var 声明。
注意:函数外的每个语句都必须以关键字开始(例如:var func),因此 := 方式不能再函数外使用。
基本类型
Go 的基本类型有
1 | bool |
int, uint 和 uintptr 在 32 位系统上通常为 32 位宽,在 64 位系统上则为 64 位宽。
类型转换
Go 在不同类型的项之间赋值时需要显式转换。
1 | var i int = 42 |
在声明一个变量而不指定其类型时,变量的类型由右值推到而出。
常量
Go 语言中,使用 const 来声明常量。
常量可以是字符、字符串、布尔值或数值。
常量不能用 := 语法声明。
数值常量是高精度的 值。
一个未指定类型的常量由上下文来决定其类型。
流程控制语句
for
Go 只有一种循环结构:for循环
基本的for循环由三个部分组成,
- 初始化语句:第一次迭代执行前
- 条件表达式:每次迭代前求值
- 后置语句:每次的迭代后执行
其中条件表达式是必须的,初始化语句和后置语句都是可选的
1 | package main |
Go 语言中没有 while,可以通过for来实现while
1 | for exp { |
上述结构中,不写exp,就可以构成无限循环
if
if 表达式外不需要小括号 ( ),但大括号 { } 是必须的。
与其他语言不同的是,if 可以再条件表达式前执行一个简单的语句。
1 | func pow(x, n, lim float64) float64 { |
switch
Go 语言的 switch 只运行选定的 case,即默认每个 case 后都有一个 break 语句。
switch 的 case 无需为常量,并且取值不必为整数。
switch 的 case 求值顺序是从上到下,至到匹配成功停止。
defer
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
指针
指针保存了值的内存地址。零值为 nil。
与 C 不同, Go 中没有指针运算。
结构体
一个结构体是一租字段,Go 中没有类的概念。
1 | package main |
如果有一个指向结构体的指针 p,那么可以通过 (*p).x 来访问其字段 x,为了方便,Go 语言允许我们使用隐式调用,即 p.x 就可以访问。
可以通过结构体文法来直接分配一个新的结构体。
1 | type Vertex struct { |
数组
1 | var array [10]int |
上述表达式表示,将变量声明为拥有10个整数的数组
注意:数组的长度是其类型的一部分,声明后数组长度不能改变。
切片
每个数组的大小都是固定的。而切片则为数组元素提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
类型 []T 表示一个元素类型为 T 的切片。
切片通过两个下标来界定,即一个上界和一个下界,二者以冒号分隔:
1 | a[low : high] |
它会选择一个半开区间,包括第一个元素,但排除最后一个元素。
切片类似数组的引用,切片不存储任何数据,更改切片的元素会影响底层数组。
新建切片时,可以利用默认行为来忽略上下界。
切片的下界的默认值为0,切片上界的默认值为该切片的长度。
切片拥有 长度 和 容量,注意,这是两个不同的概念。
切片的长度是它,即该切片所包含的元素个数。
切片的容量是从它的第一个元素开始,到底层数组末尾的长度。
切片 S 的长度和容量可以通过表达式 len(s) 和 cap(s) 来获取。
切片的零值是 nil。
使用 make 创建切片
切片可以使用内置函数 make 来创建,这也是创建动态数组的方式。
make 函数会分配一个元素为零值的数组并返回一个引用了它的切片
1 | var name = make(切片类型, 长度, 容量) |
切片可以包含任何类型,包括其他切片(实际操作未理解如何使用)
向切片追加元素
可以使用 append 函数来为切片追加新的元素。
1 | func append(s []T, vs ... T) [] T |
append 第一个参数 s 是元素类型为 T 的切片,其余类型为 T 的值会追加到该切片的末尾。
append 的返回值是一个包含原有切片所有元素加上新添加元素的切片。
如果 s 的底层数组太小,append 就会重新分配一个更大的底层数组,返回的切片会指向这个新的数组。
Range
for 循环中可以使用 range 的形式来遍历切片或映射。
1 | for index, value range array { |
range 再每次迭代是会返回两个值,第一个是当前元素下标,第二个值是对应元素的一份副本。
可以是用 _ 来忽略 range 返回的元素下标或者元素的值。
映射
即键值对。
映射的零值为 nil 。nil 映射既没有键,也不能添加键。
make 函数会返回给定类型的映射,并将其初始化备用。
1 | var m map[string]Vertex |
映射的文法和结构体类似,但必须有键名。
基本操作
1 | var elem T |
方法
Go 中 函数和方法看似很像,但实际引用场景不一样。
Go 中没有类吗,但可以为结构体类型定义方法。
方法实际上就是函数,是一类带有特殊的 接受者 参数的函数。
1 | type Vertex struct { |
方法也可以为非结构体类型声明方法,但要注意的是,就是接收者的类型定义和方法声明必须在同一包内;不能为内建类型声明方法。
1 | package main |
指针接受者
方法的接受者可以是类型 T,也可以是类型T的指针 *T。
若使用值接收者,那么方法会对原始值的副本进行操作。(对于函数的其它参数也是如此。)方法必须用指针接受者来更改 main 函数中声明的值。
1 | package main |
接口
接口类型是一组有方法签名定义的集合
接口类型的变量可以保存任何实现了这些方法的值。
1 | package main |
上述类型为 MyFloat 的类型实现了 Abser 接口。
接口与隐式实现
自定义的类型(结构体,非结构体)通过实现一个接口的 所有方法 来实现该接口。既然无需任何的显式声明,也就不用 “implement” 关键字来标识。
接口值
接口也是值,它们可以像其他值一样传递
接口值可以用作函数的参数或返回值。
在内部,接口值可以看做包含值和类型的元组
1 | (value, type) |
1 | package main |