A Tour of Go - Go语言之旅

Hello,世界

这是Go语言官方的一个简明入门教程,可以帮助我们快速入门Go语言:Go语言之旅,它提供了一个在线编辑、编译、运行、格式化代码的平台。我们也可以通过

$ go get -u gitHub.com/Go-zh/tour tour

在自己的电脑上安装并运行此教程的程序。
首先让我们来看一下作为各种编程语言入门著名的hello world程序的Go语言版本

package main

import "fmt"

func main() {
    fmt.Println("Hello, 世界")
}

上面的代码运行结果输出如下:

Hello, 世界

1 基础

1.1 包

Go语言程序是由包构成的,main()函数作为执行程序的入口,只能包含在main包中。当需要使用外部包的时候,使用import导入外部包。import可能还会包含导入路径,包名与导入路径的最后一个元素一致。当需要导入多个包时,使用import加括号,括号中包含所需要的所有包名。
示例程序:

package main

import (
    "fmt"
    "math/rand" //导入math/rand包用以调用它定义的Into()函数
)

func main() {
    fmt.Println("My favorite number is", rand.Intn(10)) //返回10以内的随机数(没有定义随机数种子,所以每次都返回同一个数-伪随机数)
}

代码运行结果:

My favorite number is 1

1.2 导出名

在Go语言的包中,变量或者函数如果是以大写字母开头的表明它是可以被其他包访问的,如果不是则只能被包内部访问

package main

import (
    "fmt"
    "math"
)

func main() {
    //fmt.Println(math.pi) //这个例子其实并不合适math包里并没有pi这个变量,运行时只会报错undefined。
    fmt.Println(math.Pi)
}

1.3 函数

通常把实现某个需要被频繁访问调用的代码封装成一个函数,以使代码变得更简洁高效,Go语言定义函数的方式:

func <func-name> (parameters ) (return values) {
    do something
}

下面的代码实现一个加法函数:

package main

import "fmt"

func add(x int, y int) int { //Go语言支持同类型参数,省略类型符号,x int, y int等价于x, y int
    return x + y
}

func main() {
    fmt.Println(add(42, 13))
}

Go语言函数支持多值返回。

package main

import "fmt"

func swap(x, y string) (string, string) {
    return y, x
}

func main() {
    a, b := swap("hello", "world") //swap 返回两个值交换后的结果
    fmt.Println(a, b)
}

代码运行结果:

world hello

1.4 变量

变量声明格式:

var <var-name> <var-type>

变量初始化:

var <var-name> <var-type> = <var-value>
var <var-name1>, <var-name2>  <var-type> =  <var-value1>, <var-value2>

短变量声明:Go语言支持不用显式指出变量的类型,它可以根据初始化值的类型做类型自动判断。但是它的使用有一定限制 - 只能在函数体内部使用,如果在函数体外部,必须要使用var关键字。

k := 3 //k会被自动定义为Int类型

当类型推导存在有歧义的可能时,Go语言会根据类型的初始化值的精度判断具体类型。

1.5 基本类型

变量声明也可以同导入语句一样使用分组方式声明(括号),没有明确初始化值的变量会被赋予零值。

  1. bool - 零值:false
  2. string - 零值:""
  3. int(以及各种位数大小不同的整型派生类型)
  4. byte(8位无符号整型-uint8)
  5. rune(32位有符号整型-int32)
  6. float32,float64
  7. complex64, complex128
    数值类型的零值都是0
    Go语言中不存在隐式的类型转换,在需要类型转换时必须要显式地使用转换函数(),否则会编译报错:
./prog.go:12:15: cannot use f (variable of type float64) as type uint in variable declaration

1.6 常量

常量使用const关键字定义,且不能使用短变量语法进行声明(很容易理解,不然的话根短变量会产生冲突)

2 流程控制语句

2.1 for循环

Go语言只有for循环,且不需要括号对循环的部分进行包括
普通for循环:

for i := 1; i < 10; i++ {
    //do something 
}

类C语言while循环:

for i < 100 {
    i++
    //do something
}

无限循环:

for {
    //do something 
}

2.2 条件跳转

if语句

if condition {
    //do something 
}

可以在条件表达式之前执行一个表达式,需要用分号隔开,表达式变量的作用域仅限if之内:

if v := math.Pow(x, n); v < lim {
    return v
}

if-else语句
使用else为条件表达式的结果相反时进行另一个操作。

2.3 switch分支跳转

switch相当于一连串的if-else语句,它会从上到下匹配switch表达式的值,匹配成功就执行分支语句块。也可以省略switch表达式,将匹配判断放到case语句中去执行

switch {
    case t.Hour() < 12:
        fmt.Println("Good morning!")
    case t.Hour() < 17:
        fmt.Println("Good afternoon.")
    default:
        fmt.Println("Good evening.")
    }

2.4 defer语句

defer语句会将其后面的表达式推迟到外层函数返回之后才运行

package main

import "fmt"

func main() {
    defer fmt.Println("world") //这一段会等到main()函数返回之后才执行

    fmt.Println("hello")
}

推迟的函数调用会被压入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
9
8
7
6
5
4
3
2
1
0

3 指针

指针也是一种变量类型,它存储的是变量或者值的内存地址。

var p *int

指针的零值是nil,&取址操作符,*解引用操作符

i := 42
p = &i //p是一个指针保存了整型变量i的内存地址
*p = 20 //修改了p指向的内存地址出存储的值

与C语言不同的是 Go没有指针运算。(指针自增、自减等)

4 结构体

通过struct关键来定义一组字段创建结构体

type Vertex struct {
    x int
    y int
}

结构体成员使用.操作符来访问,通过结构体指针来引用结构体成员时可以通过*解引用

package main

import "fmt"

type Vertex struct {
    X int
    Y int
}

func main() {
    v := Vertex{1, 2}
    p := &v
        (*p).X = 1e9
    // p.X = 1e9 为了简化表达,Go语言也支持这样隐式表达,不同于C语言p->X
    fmt.Println(v)
}

5 数组

定义数组:

var a [10]int

数组的大小是固定的,不能更改。

6 切片

  • 在类似数组的定义时,不限定它的大小,就构成了切片
var s []int 
  • 切片文法
[]bool{true, false, true}

切片的索引上界是它的长度,下界是0,使用[n:m]表示子切片时,不包括索引值为m的元素。

  • 切片长度
len(s)
  • 切片容量
cap(s)
  • 切片的零值是nil
  • 使用make创建切片
a := make ([]int, 5) //创建包含5个整型零值元素的切片,切片的长度是5
a := make([]int, 0, 5) //创建包含0个元素但是容量是5的切片
  • 切片本身也可以做为切片的元素,也就是说可以嵌套定义。
  • 可以通过内建的append()函数向切片追加元素,如果切片的容量已满,会分配一个更大的数组,返回的切片会指向新分配的数组。但是追加的元素必须要和原切片的类型相同。

7 Range

  • range形式的for循环可以用来遍历切片和循环
package main

import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}

func main() {
    for i, v := range pow {
        fmt.Printf("2**%d = %d\n", i, v)
    }
}
  • 可以将索引下标或者值赋予_来忽略它们
for i, _ := range pow
for _, value := range pow

8 映射

  • Go语言映射类型就是类似字典那样的键值对,映射的零值是nil,和切片类似,可以使用make()方法初始化映射
type Vertex struct {
    Lat, Long float64
}

m := make(map[string]Vertex)
  • 访问映射时必须要提供key值
  • 映射支持嵌套定义,也就是键值对里的值也可以是映射类型
  • 修改映射
m[key] = elem //如果key键存在,则更新对应的值为elem,如果不存在则向映射m添加{key:elem}元素
elem = m[key] //获取映射m中key键对应的值赋给elem
delete(m, key) //删除映射m中key键对应的元素
elem, ok = m[key] //如果key键存在于映射m中,则ok=true,elem被赋予对应的值;否则ok=false,elem赋予对应类型的零值
elem, ok := m[key] //如果elem,ok没有被声明则可以使用短变量声明格式

9 函数值

Go语言中,函数也可以作为一种值类型,它可以用作函数的参数或者返回值

package main

import (
    "fmt"
    "math"
)

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

func main() {
    hypot := func(x, y float64) float64 {
        return math.Sqrt(x*x + y*y)
    } //hypot被定义为一个需要传入两个float64类型参数,返回一个float64类型的函数值类型,它的定义由math.Sqrt()实现
    fmt.Println(hypot(5, 12))

    fmt.Println(compute(hypot)) //hypot作为函数值类型也可以作为compute函数的参数
    fmt.Println(compute(math.Pow))
}

10 函数的闭包

待补充

11 方法

  • Go语言没有类的概念,可以为结构体类型定义方法,方法就是带有指定的接收者参数的函数
package main

import (
    "fmt"
    "math"
)

type Vertex struct { //定义结构体Vertex
    X, Y float64
}

func (v Vertex) Abs() float64 { //为结构体Vertex定义方法-方法的接受者必须为Vertex类型变量
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs())
}
  • 并不是只能为结构体类型声明方法,也可以指定接受者类型为内建类型,但是内建类型必须要用type关键字指定别名。话说回来,在Go语言中一旦使用type为内建类型指定别名,这个别名就不能认为是和原内建类型一样的去考虑。
package main

import (
    "fmt"
    "math"
)

type MyFloat float64 //只有为float64类型指定别名MyFloat之后才能为其定义方法

func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

func main() {
    f := MyFloat(-math.Sqrt2)
    fmt.Println(f.Abs())
}
  • 方法接受者的类型也可以是指针类型,这样方法就可以读取和修改原始值,而不是只在值副本上进行操作
package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func (v *Vertex) Scale(f float64) {
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := Vertex{3, 4} //用3,4初始化一个Vertex
    v.Scale(10)//因为Scale()方法的接受者是指针类型,所以这里的v是指针,它可以修改原始值3*10,4*10,
    fmt.Println(v.Abs())//如果修改Scale()方法使方法接受者为Vertex类型变量,则Scale修改的只是v的副本,在计算v.Abs()时,仍然用的是原始值3,4
}
  • 在Go函数中,函数参数如果声明为指针类型必须要传入指针值,否则编译会报错
package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func Abs(v Vertex) float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func Scale(v *Vertex, f float64) { //如果在这里把*移除,编译就会报错
    v.X = v.X * f
    v.Y = v.Y * f
}

func main() {
    v := Vertex{3, 4}
    Scale(&v, 10)
    fmt.Println(Abs(v))
}
  • 而在方法定义中,即使方法接受者被定义为一个指针,那么在调用方法的类型即使是一个值也不会编译出错
var v Vertex
v.Scale(5)  // OK 因为Scale()方法有一个指针接收者,Go会将v.Scale(5)解释为(&v).Scale(5)
p := &v
p.Scale(10) // OK

这样做的原因有二:
首先,方法能够修改接受者指向的值
其次,避免在处理大型结构时,复制值造成的资源浪费

12 接口

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容