在正式开始Mit 6.824之前,我先按照A Tour of Go ,学习Go语言的语法和相关知识。
A Tour of Go分为三个章节。第一个章节描述了基本的语法和数据结构,第二个章节讨论了方法和接口,第三个章节介绍了Go语言的并发原语。
本节描述了Go语言的一些基本知识。包括变量声明,方法调用,以及一些其他的知识。
Hello,world!
|
|
包,变量和方法
包
每个Go程序都由包组成。程序由main
包处开始运行。比如说下面的程序使用导入的包”fmt”和”math/rand”。
|
|
导入
略
Exported names
在Go中,如果一个单词是以大写字母开头,它就是exported name。比如说Pizza是一个exported name,Pi是一个来自math
包的exported name。
如下图,直接使用math
包中的Pi
|
|
方法
一个方法可以有一个或多个参数。
注意类型在变量名之后。
|
|
当两个或多个参数都是同一个类型的时候,可以只保留最后一个类型。
比如说把
写成
多个返回结果
一个方法可以返回多个结果。如下,swap返回两个字符串。
命令返回值
Go的返回值可以被命令,就像在方法中命令变量一样。如下图,命令了返回值为x和y.
|
|
变量
可以用var
来声明一个变量列表。类型在后面。
|
|
变量初始化
一个var
声明可以包含初始化,每个变量对应一个。
如果变量被初始化了,类型可以省略,
|
|
声明短变量
在一个方法中,:=
语句可以代替一个var
声明。而在方法之外,:=
是不可用的。
|
|
基本类型
Go的基本类型有:
bool
string
int int8 int16 int32 int64
uint uint8 uint16 uint32 uint64 uintptr
byte
rune
float32 float64
complex64 complex128
int
,uint
,uintptr
的宽度和系统是一样的。当你需要一个整型值,应该使用int
。除非有特别的理由来使用一个定长的或无符号的整数类型。
零值
当变量被声明而没被显示的初始化的时候,这些变量会被初始化为零值。
数值类型为0
布尔类型为false
字符串为""
类型转换
表达式T(v)
将v
转换为类型T
。
一些数值类型的转换:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
也可以有更简单的写法:
i := 42
f := float64(i)
u := uint(f)
类型推测
当声明一个变量而没有显示的指定类型的时候,会从右侧的值推测出变量的类型。
当右边的值已经被指定类型,这个新的变量就是同样的类型。
var i int
j := i // j is an int
但是右边是没有类型的数值常量,新的变量可能是int
,float64
,或complex128
,这取决于常量的精度:
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
常量
常量声明和普通变量一样,但是要带上const
关键字。
常量可以是字符、字符串、布尔类型、或数值类型的值。
常量声明不能使用:=
语法。
数值常量
数值常量是高精度的值。
一个没有类型的常量会根据上下文来决定类型。
流程控制语句
For
Go语言只有一个循环结构,就是for
循环。
for循环有三个用分号隔开的成分:
初始化语句:在第一次开始循环之前被执行。
条件表达式:每次开始循环的时候被求值。
post语句:每次循环结束的时候执行。
一旦条件表达式是false
,循环将会停止。
|
|
初始化和post语句都是可以被省略的:
|
|
Go中的”while”
当去掉分号的时候,for
就是C语言中的while
。
|
|
无限循环
当省略掉条件表达式的时候,for循环就变成了无限循环。
|
|
If语句
Go中if和for循环差不多,表达式都不需要被”()”括起来。
|
|
If中的表达式
像For
一样,if
可以在执行判断条件之前执行一个简单的语句。
|
|
if and else
在if
中声明的变量,在else
中也有用。
|
|
Switch 以及 Switch的执行顺序
Switch 从上到下依次执行,当条件满足的时候停止。
比如说:
switch i {
case 0:
case f():
}
如果i==0,不会调用f。
没有条件的Switch
没有条件的Switch就和Switch true
是一样的。
Defer
一个defer语句推迟方法的执行直到方法返回。
|
|
defer栈
defer方法调用被压入到栈中,当方法返回的时候,defer调用按照后进先出的顺序被执行。
|
|
更多的类型:structs,slices,和maps
指针
Go拥有指针。一个指针是一个变量的内存地址。
类型*T
是指向值T
的一个指针。它的零值是nil
。
var p *int
&
操作符会生成一个指向它的操作数的指针。
i := 42
p = &i
*
操作符表示指针的底层的值。
fmt.Println(*p) // 通过指针p读取i的值
*p = 21 // 通过指针p给i设置值
但是Go没有指针运算。
结构体
一个struct
是字段的一个集合。
|
|
结构体字段
通过点号来访问结构体字段。
|
|
指向结构体的指针
可以通过结构体指针来访问结构体字段。
可以通过(*p).X
来访问一个结构体的字段X
。但是这样写很笨重,所以可以直接写p.X
。
|
|
数组
类型[n]T
是一个有n个类型为T的值的数组。
表达式像下面这样:
var a [10]int
上面的语法声明有10个整型的数组。
数组不能改变大小。
|
|
Slices
Slices是一个固定大小的数组。另一方面,slice是一个数组的动态大小的、灵活的视图。实际上,Slices比数组更通用。
类型[]T
是类型T的一个元素的一个slice。
下面的表达式创建了数组a的前五个元素的一个slice:
a[0:5]
|
|
Slices 就像是数组的引用
一个slice本身不存储任何数据,仅仅描述了一个数组的一部分。
更新一个slice的元素相当于更新数组。
|
|
默认Slice
当进行切片的时候,可以省略最高和最低的界限值。
对于数组
var a [10]int
下面的表达式都是相等的:
a[0:10]
a[:10]
a[0:]
a[:]
Slice的长度和容量
一个slice既有长度和容量。
长度就是slice包含的元素个数。
容量是数组元素的个数,从slice中的第一个元素开始计数。
一个sliceS
的长度和容量可以使用表达式len(s)
和cap(s)
来获取。
|
|
Nil slices
一个slice的零值是nil
。
一个nil slice的长度和容量为0,没有对应的数组。
|
|
使用make方法创建一个slice
可以使用make
方法创建Slices,这就是创建一个动态大小数组的方式。
make
方法分配一个零值的数组,然后返回一个slice指向这个数组。
a := make([]int, 5) // len(a)=5
传入第三个参数来指定容量:
b := make([]int, 0, 5) // len(b)=0, cap(b)=5
b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
Slices of slices
Slices可以包含任何类型,也可以包含其他的slices。
|
|
向一个slice添加元素
追加一个新元素到slice是很普遍的,所以Go提供了一个apend
方法:
func append(s []T, vs ...T) []T
第一个参数s
是一个slice,剩下的参数是追加到slice中的值。
|
|
Range
for
循环的range
格式可以迭代一个slice或map。
当rangey一个slice,每次迭代都会返回两个值,第一个是index,第二个是该index位置元素的副本。
|
|
Range continued
通过分配_
来跳过index或value。
如果你仅仅只是需要index,去掉”, value”。
|
|
Maps
一个map是keys到value的映射。
map的零值是nil。nil的map没有keys,也不能添加key。
make
方法返回一个map。
|
|
Map操作
插入或更新map中的一个元素:
m[key] = elem
获取一个元素:
elem = m[key]
删除一个元素:
delete(m, key)
测试一个key是否存在:
elem, ok = m[key]
如果key存在,ok为true,如果不存在,ok为false。
如果key不存在,elem为零值。
|
|
方法值
方法也是一个值。可以像其他值一样被传递。
|
|
闭包
Go的方法可以是一个闭包,闭包是一个方法值,引用了方法体之外的值。这个方法可以读取和分配值给这个引用的变量。换句话说,方法被绑定在变量上。
如下的例子,adder
方法返回一个闭包。每个闭包绑定在它自己的变量sum
上。
|
|