本文档主要是来自官网的tour of Go。
- 一个程序从main package开始执行
package main
import(
"fmt"
)
func main() {
fmt.println("hello world!")
}
- imports 导入多个包以括号包起来,比如说
import(
"math"
"fmt"
)
- Exported names 如果是大写的,说明这个是exported的。当我们import的时候,只能访问它的exported的变量,比如说
package main
import (
"fmt"
"math"
)
func main() {
fmt.Println(math.Pi)
}
Pi就是exported from math package
- Functions 一个函数的定义非常简单。
func add(x int, y int) int {
return x+y
}
参数的类型写在参数名之后,返回值写在括号之后。如果参数都是相同类型,可以省略其类型,在最后一个参数表明其类型即可。比如下面代码
func add(x,y int) int {
return x+y
}
//这里表示x,y都是int类型
- Multiple results 一个函数有多个返回值。
func swap(x,y string)(string,string) {
return y, x
}
- Vairables 使用var keyword来定义变量,var 可以一次定义多个变量,比如说
var c,python,java string
func main() {
var i int
fmt.Println(i, c, python, java)
}
这表示定义三个变量,他们的类型都是string。另外从上面的例子,可以看到var既可以用于函数外也可以用于函数内。
- Variables with initializers var在定义变量的时候还可以初始化每个变量的值
var c, python, java = true, false, "no!"
var 还可以使用括号将变量包裹起来,声明多个不同类型的变量,如:
var (
name string = "jack"
age int = 12
boy bool = true
)
- Short variable declarations :=可以用于变量的快速定义,并且不需要指定它的类型,:=不同于var,它只能用于函数内部。但是var,const可以用于函数外部,如:
k :=3
jack := "jack"
- Zero value 未初始化的变量将会设置为他们的默认值。
0
for numeric types,false
for the boolean type, and""
(the empty string) for strings.
- Type conversions 使用T(v)来转变类型。比如说以下代码:
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
- Type inference 如果没有给出类型,变量的类型会根据所给出的值来推测。
var i int
j := i //那么j也是int,因为它是由i赋值的
- Constants 使用const了定义一个静态变量
const Pi = 3.14
Constants cannot be declared using the :=
syntax.
Flow control statements: for,if,else,switch and defer
- For Go只有一种循环,就是for循环。其基本形式如下:
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
}
注意, 循环体中的i只能在循环体内使用。而且 循环的 {}
是必须的。初始条件和迭代条件是可选的,比如说以下代码:
for ; sum < 1000; {
sum += sum
}
如果需要for
在功能上与while
相似,语法如下:
for sum < 1000 {
sum += sum
}
如果需要for循环在功能上如同死循环,语法如下
package main
func main() {
for {
}
}
- If if的使用和for比较相似,不需要括号,但是大括号
{}
是必须的。
if x < 0 {
fmt.println("hello world!")
}
也可以在if语句中执行一些语句。但是定义在if语句中的变量只能在scope之内使用。语法如下:
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
这个和C语言等其他都想类似应该很好理解。
- Switch switch语句和Java,C中的比较相似。除了Go当中每一个case不需要break,Go会自动提供break。另外一个就是case中的条件不需要是Integer,也不需要是constant. switch 的语法如下:
switch i {
case 0:
case f():
}
- Defer defer keyword主要用于延迟函数的执行。等待其他函数返回后再去执行defer修饰的函数。比如:
package main
import "fmt"
func main() {
defer fmt.Println("world")
fmt.Println("hello")
}
Note:The deferred call's arguments are evaluated immediately, but the function call is not executed until the surrounding function returns.
- Stack defers Note:Deferred function calls are pushed onto a stack. When a function returns, its deferred calls are executed in last-in-first-out order. 这个有汇编的只是应该很好理解。将defer 修饰的函数压入到栈当中,当包裹着defer函数的父函数返回的时候,接下来再去执行defer修饰的函数。如下面的代码:
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
输出结果为: counting,done,接着是for 循环内的数字。说明defer的执行在主函数之后。
下面代码说明了,defer的执行在其父函数之后:
package main
import(
"fmt"
)
func main() {
fmt.Println("enter main")
foo()
fmt.Println("main done")
}
func foo() {
fmt.Println("enter foo")
for i:=0; i< 10; i++ {
defer fmt.Println(i)
}
fmt.Println("foo done")
}
会得到一下输出结果:enter main,enter foo, foo done, 数字,main done.也就说明了,defer修饰的函数执行在所被包裹的父函数之后。
Pointers
- Go当中也有指针,一个指针持有某个值的内存地址。
var p *int
这个就是表示p是一个int类型的指针,指针的默认值是nil。用&
来获得一个指针。*
操作符表示指针的值。比如说以下代码:
fmt.Println(*p) //read the value of *p,dereferencing
*p = 21 //set 21 to pointer p,indirecting
Note:Unlike C, Go has no pointer arithmetic.这也就说明在Go当中不能执行 p++
- Structs 和C语言当中的类似,直接上语法了,不再过多赘述:
package main
import (
"fmt"
)
type Student struct {
name string
age int
gender bool
}
func main() {
var jack Student = Student{"jack",12,true}
fmt.Println(jack.name)
}
- Pointers to struct 这个和C语言结构体指针比较像。Note:To access the field
X
of a struct when we have the struct pointerp
we could write(*p).X
. However, that notation is cumbersome, so the language permits us instead to write justp.X
, without the explicit dereference. 这样一来通过指针访问结构体更加简单了。代码如下:
var jack Student = Student{"jack",12,true}
var p_jack *Student = &jack
fmt.Println((*p_jack).name) //golang支持省略*来直接访问结构体的成员变量,例如p_jack.name
- Struct Literals 这个就是可以通过结构体的成员变量名字来初始化变量。直接上代码,很好理解:
var jack Student = Student{name:"jack",age:12,gender:true}
当然一个结构体不需要初始化所有的成员变量也是可以的。比如说以下代码依然可以运行:
var jack Student = Student{name:"jack"}
- Arrays 数组的定义也十分简单。golang的数组真的好反人类。语法如下:
var a [10]int //声明一个数组
a[0] = 1 //修改数组某元素的值
primes := [6]int{1,2,3,4,5,6} // 初始化一个数组
- Slices 这个和python中的slices有一点相似。一个slices 通过
[low:high]
来实现。slice中的元素包括low开始,但是不包括high。slices的定义是[]T
,表示定义一个类型为T
的slices。代码如下:
package main
import "fmt"
func main() {
primes := [6]int{2, 3, 5, 7, 11, 13}
var s []int = primes[1:4] //获得数组从下标1开始到3元素(注意slices,不包括4)
fmt.Println(s)
}
- Slices are like references to arrays Note:A slice does not store any data, it just describes a section of an underlying array.Changing the elements of a slice modifies the corresponding elements of its underlying array. Other slices that share the same underlying array will see those changes. 这也就是说明了,slices并没有创建额外的数组。slices只是指向了原来数组中部分元素的一个引用。所以修改slices也会修改原来数组的内容。另外修改了数组的内容,其他共享这块区域的slices都会看到这部分改变。代码如下:
package main
import "fmt"
func main() {
names := [4]string {"jack","tim","ringo","paul"}
fmt.Println(names)
var foo []string = names[0:3]
foo[1] = "andrew"
fmt.Println(names)
}
以上代码的输出结果:jack tim ringo pual \n jack andrew ringo pual。这也就说明了slices会修改数组的值
- Slice literals 一个slice literal像定义一个数组,但是没有给出长度。比如说定义一个数组的语法如下:
var foo = [3]bool{true,false,true}
And this creates the same array as above, then builds a slice that references it:
var foo = []bool{true,false,true}
- Slices default 在使用slices的时候,可以忽略最高和最低,使用默认值来替代。对于low bound,默认值是0。对于high bound,默认值是length。 对于一个长度位10的数组,下列表达式是一样的: Note:这个和python的比较相似。
a[0:10]
a[:10]
a[0:]
a[:]
- Slice length and capcity 一个slice有length和capcity
- length: 一个slice的元素个数
- capcity:The capacity of a slice is the number of elements in the underlying array, counting from the first element in the slice. 可以通过函数len()和cap()函数来获得slice的length以及capcity。文档中的实例代码有部分地方没明白,高阶的操作先略过。
- Creating a slice with make 通过内置的函数make可以来创建一个slice。
make
function allocates a zeroed array and returns a slice that refers to that array。语法如下:
a := make([]int,5) //第二个参数是length,当然也可以指定capcity,即第三个参数
b := make([]int, 0, 5) // length = 0, capcity =5
- Slice of slices Slices可以包括任何类型。其实这个和二维数组有点像:代码如下
nums := [][]int {
{1,2},
{3,4},
}
- Appending to a slice 如果需要向一个slice中追加数据,那么就是用append()函数。Note:If the backing array of
s
is too small to fit all the given values a bigger array will be allocated. The returned slice will point to the newly allocated array. 这也就是说,如果append的slice的capcity不够了,那么就会新申请一个数组,然后再追加上内容。doc
package main
import "fmt"
func main() {
var s []int
printSlice(s)
// append works on nil slices.
s = append(s, 0)
printSlice(s)
// The slice grows as needed.
s = append(s, 1)
printSlice(s)
// We can add more than one element at a time.
s = append(s, 2, 3, 4)
printSlice(s)
}
func printSlice(s []int) {
fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s)
}
输出结果:
len=0 cap=0 []
len=1 cap=1 [0]
len=2 cap=2 [0 1]
len=5 cap=6 [0 1 2 3 4] //不明白这里的capcity为什么是6
- Range: range 和for循环组合在一起,能够遍历一个slice或者map。这里和for-each语法相似。Note:When ranging over a slice, two values are returned for each iteration. The first is the index, and the second is a copy of the element at that index. 代码如下:
var nums = []int {1,2,3,4,5,6,7}
for i,v := range nums {
fmt.Pritnf("index=%d,value=%d\n",i,v)
}
- Maps A map maps keys to values. map的zero value是nil.如果需要创建一个map需要用到make函数。 Note: 为什么需要make 函数来初始化一个map?下面以代码来说明
type Student struct {
name string
age int
gender bool
}
func main() {
var stuMap map[string]Student
fmt.Println(stuMap == nil) // true
//在这种情况之下,如果往map中插入数据
//stuMap["jack"] = Studnet{"jack",12,true}
//因为往nil中插入数据,会报错。
stuMap = make(map[string]Student)
fmt.Println(stuMap == nil) // false
}
上面的例子说明,如果只是声明(declare)。那么这map是nil。只有make之后才是一个初始化好的map,才可以调用一些map的方法。
- Map literals 不知道这个到底啥意思。但是我暂且理解为一种初始化方式。代码如下:
import "fmt"
type Student struct {
name string
age int
gender bool
}
func main() {
var stuMap = map[string]Student{ //这里指明了这个map是Student
"jack":Student{"jack",12,true} // Student可以省略
"tim":Student{"allen",13,true}
}
fmt.Println(stuMap)
}
Note:如果在map定义的时候指明了类型,在初始化map的时候不需要显式的写出类型。
- Function values 这个见怪不怪了。C语言中的函数指针,python javascript都有这种函数可以当作参数传给别的函数调用的的例子。
func main() {
fmt.Println("enter main")
foo(bar)
fmt.Println("main done")
}
func bar() {
fmt.Println("I am callback")
}
func foo(fn func()) {
fmt.Println("hello world!")
fn()
}
- Function closure 闭包老生常谈了。即使返回的函数中持有外部函数的变量。因为按照一般来说,代码执行完了它内部的临时变量就被释放了。直接上代码吧:
func main() {
foo1 := foo()
foo2 := foo()
fmt.Println(foo1()) // 输出2
fmt.Println(foo2()) // 输出2
}
func foo() func() int { //说明该函数的返回值是一个int类型的函数
sum := 1
return func() int { //在foo函数执行完之后,sum并不会释放,并且将来还可以继续使用
return sum*2
}
}
Note:Each closure is bound to its own sum
variable. 这说明closure中的变量是私有的,而不是共享的。
Methods and interfaces
- Method 在go当中没有classes,但是这不是说面向对象那一套就没用了。可以定义一个method.method就是一个带有special receiver argument的函数。receiver出现在函数名以及
func
关键字之间。代码如下:
type Student struct {
name string
age int
score float64
}
func (stu Student) getScore() float64 { //定义一个method
return stu.score
}
func main() {
var jack Student = Student{"jack",12,68}
fmt.Println(jack.getScore())
}
- Method are functions **Remember: **a method is just a function with a receiver argument. 所以说method归根到底也只是一个函数。上面的mehtod也可以写成平常的function。method在我看来只是让代码看起来更加面向对象一点(object oriented)
func getScore(stu Student) float64 {
return stu.score
}
- Pointer receivers 可以定义一个method其receivers 是pointers。 Note:Methods with pointer receivers can modify the value to which the receiver points.这里说的就是call by value 和call by reference的关系了。pointer receivers实现了call by reference。可以对参数进行修改。
type Student struct {
name string
age int
score float64
}
//表明这个函数的receiver是一个*Student
func (stu *Student) updateScore(new float64) { //如果去掉*,那么这个修改无效,此时是call by value
(*stu).score = new //可以简写为stu.score,但是为了看起来这是在操作一个指针
fmt.Println("update successfully")
}
func (stu Student) getScore() {
fmt.Println(stu.score)
}
func main() {
var jack Studnet = Student{"jack",12,68}
jack.getScore()
jack.updateScore(99)
jack.getScore()
}
- Methods and pointer indirection
对于一个普通的函数来说,如果它的参数是指针类型的,那么一定要使用指针类型。而对于pointer receiver来说,可以使用指针类型或者值来作为receiver。
比如说对于上面的代码中的updateScore()
函数,:
var jack Studnet = Student{"jack",12,68}
jack.updateScore(66) //这是可以的
(&jack).updateScore(100) //这也是可以的
这是因为,GO将jack.updateScore(66)
解释为(&jack).updateScore(66)
。所以即使receiver是pointer的,直接使用值也是可以的
从另外一方面来说,
对于一个普通函数来说,如果它的参数是value类型的,那么一定要使用值类型。但是对一个method来说,如果他的receiver是值类型的,那么它既可以使用值也可以使用指针。以上面代码为例子,getScore()
可以使用指针或者值来调用
var jack = Student{"jack",12,68}
jack.getScore() //这是可以的
(&jack).getScore() //这也是可以的
Note: (&jack).getScore()
被解释为(*jack).getScore()
,看官网的例子
- Interfaces Interface这个也比较常见了。有了接口能够更好的体现OO的思想。在Go当中接口的定义和其他语言都差不多。和Java当中类似的是,如果我们实现了一个接口,需要实现它所有的函数。代码如下,或者可以参照官网的例子:
import "fmt"
type Info interface {
getName() string
getAge() int
}
type Student struct {
name string
age int
}
func (stu Student) getName() string {
return stu.name
}
/*
这样的函数定义表明 Student这个类型实现了Info这个interface
如果只inerface中部分函数,getInfo函数会报错,这样就无法利用多态
*/
func (stu Student) getAge() int {
return stu.age
}
func getInfo(i Info) {
fmt.Println(i.getName())
fmt.Println(i.getAge())
}
func main() {
var jack = Student{"jack",12}
getInfo(jack) //可以实现多态
}
Note: 不同于Java,C#,C++等语言。实现一个接口不需要使用关键字。看上述代码中的注释即可明白,这里的代码可以理解到interface带来的多态的思想。
- Interface values interface values这个概念是第一次遇到的。就是说一个interface也有value和type。具体来说,An interface value holds a value of a specific underlying concrete type.
type Info interface {
getScore() float64
}
type Student struct {
name string
age int
score float64
}
//说明*Student实现了Info接口
func (stu *Student) getScore() float64 {
return stu.score
}
func describe(i Info) {
fmt.Println("(%v, %T)\n",i,i)
}
func main() {
var i Info
i = &Student{"jack",12,68}
describe(i)
}
会输出(&{jakc,12,68}, main.Student)。这就说明了interface有自己的value。
- Interface values with nil underlying values 一个interface的值可以是空的。比如说上面的代码:
var i Info // interface的zero value是 nil
describe(i) // describe()函数输出(nil,nil)
在有些函数当中传入null,会引发null pointer exception.这里没有理解,先放官网的的例子在这,doc
- The empty interfaces 如果变量的类型是
interface
,那么表明这个变量可能是任意类型的。 - Type assertions type assertion 提供了一种判断 interface value的方法。语法如下:
t := i.(T)
这个语句assert变量i的类型是T,并且将T的值赋值给t。如果i不是类型T,代码就会panic。
为了测试一个interface是否是某个指定类型,一个type assertion可以有两个返回值。第一个是类型的值,第二个是bool,反映assertion是否成功
代码如下:
func main() {
var i interface{} = "hello"
s,ok :=i.(int)
fmt.Println(s,ok)
}
如果i是类型int,s的值为hello, ok = true。如果不是,s = 0, ok = false。在例子当中,string肯定不是Int,所以。s= 0,ok =false。
- Type switches 这个就是用switch来判断参数的类型。语法并不复杂:
package main
import "fmt"
func do(i interface{}) {
switch v := i.(type) {
case int:
fmt.Printf("Twice %v is %v\n", v, v*2)
case string:
fmt.Printf("%q is %v bytes long\n", v, len(v))
default:
fmt.Printf("I don't know about type %T!\n", v)
}
}
func main() {
do(21)
do("hello")
do(true)
}
变量v
的类型是参数i
的Type,值是i
的value。
- Stringers 这个就是java里边的tostring()了,比较好理解。在Go当中,无处不在的接口是就是定义在fmt package 中的
Stringers
。
type Stringer interface {
String() string
}
实现这个接口的函数,就可以自己实现所需要的输出方式。
package main
import "fmt"
type Person struct {
Name string
Age int
}
func (p Person) String() string {
//Sprintf这个函数只是格式化string,并不会将字符串输出到standard output
//Println函数将会输出到standard output
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
func main() {
a := Person{"Arthur Dent", 42}
z := Person{"Zaphod Beeblebrox", 9001}
fmt.Println(a, z)
}
- Readers 这个应该是和IO有关,文档写的不是很详细,后面再补充