包、变量和函数
package main
import "fmt"
func main() {
fmt.Println("hello world")
}
包
每个Go程序都是由包构成。
程序从main包开始运行。
导入包
分组形式导入包(推荐)
import (
"fmt"
"math"
)
编写多个导入语句
import "fmt"
import "math"
导出名
名字以大写字母开头是导出名,如Println。未导出的名字在该包外无法访问。
函数
类型在变量名之后。
func add(x int, y int) int
当连续两个或多个函数的已命名形参类型相同时,可以只写最后一个的类型。
func add(x, y int) int
效果与上面的例子一样。
多值返回
func swap(x, y string) (string, string) {
return y, x
}
命名返回值
返回值可以被命名,它们被视作定义在函数顶部的变量。
返回值名称应当具有一定意义,它可以作为文档使用。
没有参数的return语句返回已命名的返回值,也就是直接返回。
直接返回语句应当仅用在下面的短函数中,长函数会影响代码可读性。
变量
var语句用于声明一个变量列表。var语句可以出现在包或者函数级别。
初始化
变量声明可以包含初始值,每个变量对应一个。
如果初始化值已存在,则可以省略类型;变量会从初始值中获得类型。
短变量声明
在函数中,简介赋值语句:=可代替var。
在函数外必须以关键字(var,func等等)开始,不能用:=。
基本类型
基本类型有
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名
// 表示一个 Unicode 码点
float32 float64
complex64 complex128
变量声明可以和导入语句一样“分组”成一个语法块。
零值
没有明确初始值的变量声明被赋予零值。
数值类型为0,布尔类型为false,字符串为""(空字符串)。
类型转换
表达式 T(v) 将值 v 转换为类型 T。Go 在不同类型的项之间赋值时需要必须显式转换。
一些关于数值的转换:
var i int = 42
或者,更加简单的形式:
i := 42
类型推导
声明变量而不指定其类型时(即不带类型的:=或var =),变量类型由右值推导得出。
当右值声明了类型时,新变量的类型与其相同:
var i int
j := i //j也是一个int
当右值没有明确类型时,新变量的类型取决于右值常量的精度:
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
常量
使用const关键字。
常量不能用:=语法声明。
const Pi = 3.14
字符串操作
标准库strings里面有很多常用的字符串工具函数。比如contains判断一个字符串里面是否有包含另一个字符串,count,index查找某个字符串位置,join连接多个字符串。
字符串格式化
可以用标准fmt库里面的相关方法。比如用Printf时可以用%v来打印任何类型的变量,用%+v打印详细结果。
流程控制语句:for、if、else、switch和defer
for
无序小括号
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum) //45
for是Go中的“while”
去掉分号的for
无限循环
省略循环条件
for {
}
if
无需小括号
if简短语句
和for一样,if可以在条件表达式前执行一个简单的语句,该语句声明的变量作用域仅在if之内。
if和else
switch
Go 只运行选定的 case,而非之后所有的 case。Go 自动提供了在这些语言中每个 case 后面所需的 break 语句。 除非以 fallthrough 语句结束。
Go 的另一点重要的不同在于 switch 的 case 无需为常量,且取值不必为整数。
无条件的switch
同switch true
defer
defer 语句会将函数推迟到外层函数返回之后执行。
推迟调用的函数其参数会立即求值,但直到外层函数返回前该函数都不会被调用。
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
hello
world
defer栈
推迟的函数调用会被压入一个栈中。当外层函数返回时,被推迟的函数会按照后进先出的顺序调用。
更多类型:struct、slice和映射
指针
类型*T是指向T类型值的指针。其零值为nil。
var p *int
&操作符会生成一个指向其操作数的指针。
i := 42
p = &i
*操作符表示指针指向的底层值。
fmt.Println(*p) // 通过指针p读取i
*p = 21 //通过指针p设置i
结构体
一个结构体(struct)就是一组字段(field)。
结构体字段
结构体字段使用.来访问。
v.X = 4
结构体指针
通过(*p).X来访问其字段,也可以隐式间接引用p.X
v := Vertex{1, 2}
p := &v
p.X = 1e9
结构体用法
结构体通过直接列出字段的值来新分配一个结构体。
使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)
特殊的前缀 & 返回一个指向结构体的指针。
type Vertex struct {
X, Y int
}
var (
v1 = Vertex{1, 2} // 创建一个 Vertex 类型的结构体
v2 = Vertex{X: 1} // Y:0 被隐式地赋予
v3 = Vertex{} // X:0 Y:0
p = &Vertex{1, 2} // 创建一个 *Vertex 类型的结构体(指针)
)
JSON处理
对于struct类型,可以用encoding/json包里面的json.Marshaler来序列化或者反序列化。
数组
表达式
var a [10]int
切片
切片为数组提供动态大小的、灵活的视角。在实践中,切片比数组更常用。
形式:[]T
切片通过上届和下界来界定:a[low : high],左闭右开
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4]
fmt.Println(s)
切片就像数组的引用
切片不储存任何数据,它只是描述了底层数组中的一段。
更改切片的元素会修改其底层数组中对应的元素。
与它共享底层数组的切片都会观测到这些修改。
切片用法
类似于没有长度的数组。
[]bool{true, true, false}
切片下界的默认值为 0,上界则是该切片的长度。
var a [10]int
a[0:10] //等价于a[:10]、a[0:]和a[:]
切片的长度与容量
假设有切片s
长度:它所包含的元素个数。通过len(s)获取。
容量:从它的第一个元素开始数,到其底层数组元素末尾的个数。通过cap(s)获取。
nil切片
切片的零值是nil,nil切片的长度和容量为0且没有底层数组。
var s []int
用make创建切片
切片可以用内建函数 make 来创建,这也是你创建动态数组的方式。make 函数会分配一个元素为零值的数组并返回一个引用了它的切片:
a := make([]int, 5) // len(a)=5
要指定它的容量,需向 make 传入第三个参数:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
切片的切片
向切片添加元素
用append函数。
原型:
func append(s []T, vs ...T) []T
append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。
当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
var s []int //len=0 cap=0 []
// 添加一个空切片
s = append(s, 0) //len=1 cap=1 [0]
// 这个切片会按需增长
s = append(s, 1) //len=2 cap=2 [0 1]
// 可以一次性添加多个元素
s = append(s, 2, 3, 4) //len=5 cap=6 [0 1 2 3 4]
range
for 循环的 range 形式可遍历切片或映射。
当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
var pow = []int{1, 2, 4, 8}
for i, v := range pow {
fmt.Printf("下标:%d,对应元素:%d\n", i, v) //0,1 1,2 2,4 3,8
}
可以将下标或值赋予 _ 来忽略它。
for i, _ := range pow
for _, value := range pow
若只需要索引,忽略第二个变量即可。
for i := range pow
map映射
映射将键映射到值。映射的零值为 nil 。nil 映射既没有键,也不能添加键。make 函数会返回给定类型的映射,并将其初始化备用。
若顶级类型只是一个类型名,你可以在文法的元素中省略它。
var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
}
修改映射
在映射 m 中插入或修改元素:
m[key] = elem
获取元素:
elem = m[key]
删除元素:
delete(m, key)
通过双赋值检测某个键是否存在:
elem, ok = m[key]
若 key 在 m 中,ok 为 true ;否则,ok 为 false。若 key 不在映射中或者不存在,那么 elem 是该映射元素类型的零值。
注 :若 elem 或 ok 还未声明,可以使用短变量声明:
elem, ok := m[key]
函数值
函数也是值。它们可以像其它值一样传递。函数值可以用作函数的参数或返回值。
方法和接口
方法
Go 没有类。不过你可以为结构体类型定义方法。方法就是一类带特殊的 接收者 参数的函数。方法接收者在它自己的参数列表内,位于 func 关键字和方法名之间。
func (v Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
...
fmt.Println(v.Abs()) //调用方法
在此例中,Abs 方法拥有一个名为 v,类型为 Vertex 的接收者。
方法只是个带接收者参数的函数。
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
...
fmt.Println(Abs(v)) //调用函数
在为非结构体类型声明方法时,接收者的类型定义和方法声明必须在同一包内;不能为基本类型声明方法。
type MyFloat float64
func (f MyFloat) Abs() float64 { //不能直接func (f float64) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
指针接收者
可以为指针接收者声明方法。同样,接受者指针指向的类型不能是基本类型。指针用的更多。
func (v *Vertex) Scale(f float64) {
以指针为接收者的方法被调用时,接收者既能为值又能为指针:
func (v *Vertex) Scale(f float64) {
...
}
...
var v Vertex
v.Scale(5) // OK
p := &v
p.Scale(10) // OK
Go 会将语句 v.Scale(5) 解释为 (&v).Scale(5)。
同理,而以值为接收者的方法被调用时,接收者既能为值又能为指针。
错误
用一个函数来处理错误。错误处理是可以自定义的,