在正式开始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上。
|
|