Golang tutorial

本文档主要是来自官网的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 pointer p we could write (*p).X. However, that notation is cumbersome, so the language permits us instead to write just p.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。makefunction 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有关,文档写的不是很详细,后面再补充
暂无评论

发送评论 编辑评论

|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇