A Tour of Go

本文主要列举Go指南内容,建议直接进入官方链接学习,链接在文末。

1、包、变量和函数

每个 Go 程序都是由包组成的。

程序运行的入口是包 `main`。

这个程序使用并导入了包 "fmt" 和 `"math/rand"`。

按照惯例,包名与导入路径的最后一个目录一致。例如,`"math/rand"` 包由 package rand 语句开始。

注意: 这个程序的运行环境是固定的,因此 rand.Intn 总是会返回相同的数字。 (为了得到不同的数字,需要生成不同的种子数,参阅 rand.Seed。)

main函数和init函数

Go里面有两个保留函数,一个是init函数和main函数,前者能够在不同package中被调用,后者只能用于package main。这两个函数共同点是在定义时不能有任何的参数和返回值。尽管一个包里可以写任意多个init函数,但建议只写一个,便于阅读。每个init是可选的,但一个package有且只有一个main函数。

执行过程:程序的初始化和执行都起始于main包,若main包还导入了其他的包,在编译时就会依次导入,若一个包被多个同时导入,则只会被导入一次。当一个包被导入时,如果该包还导入了其他包,那么会先将其他包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行init函数,依次类推,等所有导入的包被加载完毕,就开始对main包中的包级常量和变量进行初始化,然后执行main包中的init函数,最后执行main函数。

导入

这个代码用圆括号组合了导入,这是“打包”导入语句。

同样可以编写多个导入语句,例如:

import "fmt"import "math"

不过使用打包的导入语句是更好的形式。

import

格式: import “path/package”

import ( “path1/package1” “path2/package2” … )

相对路径:import "./model" //当前文件同一目录的model目录,不建议这么用

绝对路径:import "shorturl/model" //加载gopath/src/shorturl/model模块

点操作示例:import (. "fmt") //.的含义表示这个包导入之后,在调用这个包的函数时,你可以省略前缀的包名fmt.Println("ceshi") => Println("ceshi")

别名操作示例:import (f "fmt")//fmt.Println("ceshi")=> f.Println("ceshi")

_操作示例:import (_ "ray/tech/goceshi")// _是引入goceshi该包,不直接使用包里面的函数,而是调研了该包里的init函数。

导出名

在导入了一个包之后,就可以用其导出的名称来调用它。

在 Go 中,首字母大写的名称是被导出的。

Foo 和 FOO 都是被导出的名称。名称 foo 是不会被导出的。

执行代码。然后将 math.pi 改名为 math.Pi 再试着执行一下。

函数

函数可以没有参数或接受多个参数。

在这个例子中,`add` 接受两个 int 类型的参数。

注意类型在变量名 _之后_。

函数(续)

当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略。

在这个例子中 ,

x int, y int

被缩写为

x, y int

变参

Go语言函数支持变参。接收变参的函数有不定数量的参数。

格式:func myfunc(arg … type){}

arg … type表示Go语言接受不定数量的参数,且具有相同类型type。

传值与传指针

Go语言中的函数值传递与Java语言中一样,均是指当传递一个参数值到被调用函数里面时,实际上传了这个值的一份copy,当在被调用函数中修改参数值的时候,调用函数中实参不会发生变化。

指针传递是指将变量存放在内存中的地址&x传入函数,若函数中改变了该地址的值,则意味着原传入函数的变量值也被改变了。

多值返回

函数可以返回任意数量的返回值。

swap 函数返回了两个字符串。

命名返回值

Go 的返回值可以被命名,并且像变量那样使用。

返回值的名称应当具有一定的意义,可以作为文档使用。

没有参数的 return 语句返回结果的当前值。也就是`直接`返回。

直接返回语句仅应当用在像下面这样的短函数中。在长的函数中它们会影响代码的可读性。

变量

var 语句定义了一个变量的列表;跟函数的参数列表一样,类型在后面。

就像在这个例子中看到的一样,`var` 语句可以定义在包或函数级别。

初始化变量

变量定义可以包含初始值,每个变量对应一个。

如果初始化是使用表达式,则可以省略类型;变量从初始值中获得类型。

短声明变量

在函数中,`:=` 简洁赋值语句在明确类型的地方,可以用于替代 var 定义。

函数外的每个语句都必须以关键字开始(`var`、`func`、等等),`:=` 结构不能使用在函数外。

_(下划线)是特殊变量名,任何赋予它的值都会被丢弃,为什么我们还用它?这就是go灵活的表现,在某些场景下,获取的返回值在接下来的程序没有实际用处,但如果声明了而不用,Go会在编译阶段报错提示。

基本类型

Go 的基本类型有Basic types

boolstringint  int8  int16  int32  int64uint uint8 uint16 uint32 uint64 uintptrbyte // uint8 的别名rune // int32 的别名     // 代表一个Unicode码float32 float64complex64 complex128

这个例子演示了具有不同类型的变量。 同时与导入语句一样,变量的定义“打包”在一个语法块中。

零值

变量在定义时没有明确的初始化时会赋值为_零值_。

零值是:

数值类型为 `0`,

布尔类型为 `false`,

字符串为 `""`(空字符串)。

类型转换

表达式 T(v) 将值 v 转换为类型 `T`。

一些关于数值的转换:

var i int = 42var f float64 = float64(i)var u uint = uint(f)

或者,更加简单的形式:

i := 42f := float64(i)u := uint(f)

与 C 不同的是 Go 的在不同类型之间的项目赋值时需要显式转换。 试着移除例子中 float64 或 int 的转换看看会发生什么。

类型推导

在定义一个变量但不指定其类型时(使用没有类型的 var 或 := 语句), 变量的类型由右值推导得出。

当右值定义了类型时,新变量的类型与其相同:

var i intj := i // j 也是一个 int

但是当右边包含了未指名类型的数字常量时,新的变量就可能是 int 、 float64 或 `complex128`。 这取决于常量的精度:

i := 42 // int

f := 3.142 // float64

g := 0.867 + 0.5i // complex128

尝试修改演示代码中 v 的初始值,并观察这是如何影响其类型的。

常量

常量的定义与变量类似,只不过使用 const 关键字。

常量可以是字符、字符串、布尔或数字类型的值。

常量不能使用 := 语法定义。

数值常量

数值常量是高精度的 _值_。

一个未指定类型的常量由上下文来决定其类型。

也尝试一下输出 needInt(Big) 吧。

2、流程控制语句:for、if、else 和 switch

for

Go 只有一种循环结构——`for` 循环。

基本的 for 循环除了没有了 `( )` 之外(甚至强制不能使用它们),看起来跟 C 或者 Java 中做的一样,而 `{ }` 是必须的。

for(续)

跟 C 或者 Java 中一样,可以让前置、后置语句为空。

for 是 Go 的 “while”

基于此可以省略分号:C 的 while 在 Go 中叫做 `for`。

死循环

如果省略了循环条件,循环就不会结束,因此可以用更简洁地形式表达死循环。

if

if 语句除了没有了 `( )` 之外(甚至强制不能使用它们),看起来跟 C 或者 Java 中的一样,而 `{ }` 是必须的。(耳熟吗?)

if 的便捷语句

跟 for 一样,`if` 语句可以在条件之前执行一个简单的语句。

由这个语句定义的变量的作用域仅在 if 范围之内。

(在最后的 return 语句处使用 v 看看。)

if 和 else

在 if 的便捷语句定义的变量同样可以在任何对应的 else 块中使用。

switch

一个结构体(`struct`)就是一个字段的集合。

除非以 fallthrough 语句结束,否则分支会自动终止。

switch 的执行顺序

switch 的条件从上到下的执行,当匹配成功的时候停止。

(例如,

switch i

当 i==0 时不会调用 `f`。)

注意:Go playground 中的时间总是从 2009-11-10 23:00:00 UTC 开始, 如何校验这个值作为一个练习留给读者完成。

没有条件的 switch

没有条件的 switch 同 `switch true` 一样。

这一构造使得可以用更清晰的形式来编写长的 if-then-else 链。

defer

defer 语句会延迟函数的执行直到上层函数返回。

延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。

defer 栈

延迟的函数调用被压入一个栈中。当函数返回时, 会按照后进先出的顺序调用被延迟的函数调用。

3、复杂类型: struct、slice 和 map

指针

Go 具有指针。 指针保存了变量的内存地址。

类型 *T 是指向类型 T 的值的指针。其零值是 `nil`。

var p *int

& 符号会生成一个指向其作用对象的指针。

i := 42p = &i

* 符号表示指针指向的底层的值。

fmt.Println(*p) // 通过指针 p 读取 i

*p = 21         // 通过指针 p 设置 i

这也就是通常所说的“间接引用”或“非直接引用”。

与 C 不同,Go 没有指针运算。

结构体

一个结构体(`struct`)就是一个字段的集合。

(而 type 的含义跟其字面意思相符。)

结构体字段

结构体字段使用点号来访问。

结构体指针

结构体字段可以通过结构体指针来访问。

通过指针间接的访问是透明的。

结构体文法

结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。

使用 Name: 语法可以仅列出部分字段。(字段名的顺序无关。)

特殊的前缀 & 返回一个指向结构体的指针。

数组

类型 [n]T 是一个有 n 个类型为 T 的值的数组。

表达式

var a [10]int

定义变量 a 是一个有十个整数的数组。

数组的长度是其类型的一部分,因此数组不能改变大小。 这看起来是一个制约,但是请不要担心; Go 提供了更加便利的方式来使用数组。

slice

一个 slice 会指向一个序列的值,并且包含了长度信息。

[]T 是一个元素类型为 T 的 slice。

对 slice 切片

slice 可以重新切片,创建一个新的 slice 值指向相同的数组。

表达式

s[lo:hi]

表示从 lo 到 hi-1 的 slice 元素,含两端。因此

s[lo:lo]

是空的,而

s[lo:lo+1]

有一个元素。

构造 slice

slice 由函数 make 创建。这会分配一个零长度的数组并且返回一个 slice 指向这个数组:

a := make([]int, 5)  // len(a)=5

为了指定容量,可传递第三个参数到 `make`:

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

nil slice

slice 的零值是 `nil`。

一个 nil 的 slice 的长度和容量是 0。

向 slice 添加元素

向 slice 添加元素是一种常见的操作,因此 Go 提供了一个内建函数 `append`。 内建函数的文档对 append 有详细介绍。

func append(s []T, vs ...T) []T

append 的第一个参数 s 是一个类型为 T 的数组,其余类型为 T 的值将会添加到 slice。

append 的结果是一个包含原 slice 所有元素加上新添加的元素的 slice。

如果 s 的底层数组太小,而不能容纳所有值时,会分配一个更大的数组。 返回的 slice 会指向这个新分配的数组。

range

for 循环的 range 格式可以对 slice 或者 map 进行迭代循环。

range(续)

可以通过赋值给 _ 来忽略序号和值。

如果只需要索引值,去掉“, value”的部分即可。

map

map 映射键到值。

map 在使用之前必须用 make 而不是 new 来创建;值为 nil 的 map 是空的,并且不能赋值。

map 的文法

map 的文法跟结构体文法相似,不过必须有键名。

map 的文法(续)

如果顶级的类型只有类型名的话,可以在文法的元素中省略键名。

修改 map

在 map m 中插入或修改一个元素:

m[key] = elem

获得元素:

elem = m[key]

删除元素:

delete(m, key)

通过双赋值检测某个键存在:

elem, ok = m[key]

如果 key 在 m 中,`ok` 为 true 。否则, ok 为 `false`,并且 elem 是 map 的元素类型的零值。

同样的,当从 map 中读取某个不存在的键时,结果是 map 的元素类型的零值。

make及new操作

make用于内建类型(map,slice,channel)的内存分配

new用于各种类型的内存分配,new 返回指针,new(T)分配了零值填充的T类型内存空间,返回一个*T类型的值

make(T,args)与new(T)的区别:

make只能创建slice,map和channel,并返回一个有初始值(非零)的T类型,而不是*T.make返回初始化后的(非零)值.

常用类型变量未填充前的默认值

int     0

int8    0

int32   0

int64   0

uint    .0x0

rune    //rune实际类型为int32byte  

0x0 //byte实际是uint8

float32//长度为4 byte

float64 //长度为 8byte

bool    false

string  ""

函数值

函数也是值。

函数的闭包

Go 函数可以是闭包的。闭包是一个函数值,它来自函数体的外部的变量引用。 函数可以对这个引用值进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。

例如,函数 adder 返回一个闭包。每个闭包都被绑定到其各自的 sum 变量上。

4、方法和接口

方法

Go 没有类。然而,仍然可以在结构体类型上定义方法。

方法接收者 出现在 func 关键字和方法名之间的参数中。

方法(续)

你可以对包中的 任意 类型定义任意方法,而不仅仅是针对结构体。

但是,不能对来自其他包的类型或基础类型定义方法。

接收者为指针的方法

方法可以与命名类型或命名类型的指针关联。

刚刚看到的两个 Abs 方法。一个是在 *Vertex 指针类型上,而另一个在 MyFloat 值类型上。 有两个原因需要使用指针接收者。首先避免在每个方法调用中拷贝值(如果值类型是大的结构体的话会更有效率)。其次,方法可以修改接收者指向的值。

尝试修改 Abs 的定义,同时 Scale 方法使用 Vertex 代替 *Vertex 作为接收者。

当 v 是 Vertex 的时候 Scale 方法没有任何作用。`Scale` 修改 `v`。当 v 是一个值(非指针),方法看到的是 Vertex 的副本,并且无法修改原始值。

Abs 的工作方式是一样的。只不过,仅仅读取 `v`。所以读取的是原始值(通过指针)还是那个值的副本并没有关系。

接口

接口类型是由一组方法定义的集合。

接口类型的值可以存放实现这些方法的任何值。

注意: 列子代码的 22 行存在一个错误。 由于 Abs 只定义在 *Vertex(指针类型) 上, 所以 Vertex(值类型) 不满足 `Abser`。

隐式接口

类型通过实现那些方法来实现接口。 没有显式声明的必要;所以也就没有关键字“implements“。

隐式接口解藕了实现接口的包和定义接口的包:互不依赖。

因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。

包 io 定义了 Reader 和 `Writer`;其实不一定要这么做。

Stringers

一个普遍存在的接口是 fmt 包中定义的 Stringer。

type Stringer interface {    String() string}

Stringer 是一个可以用字符串描述自己的类型。`fmt`包 (还有许多其他包)使用这个来进行输出。

错误

Go 程序使用 error 值来表示错误状态。

与 fmt.Stringer 类似,`error` 类型是一个内建接口:

type error interface {    Error() string}

(与 fmt.Stringer 类似,`fmt` 包在输出时也会试图匹配 `error`。)

通常函数会返回一个 error 值,调用的它的代码应当判断这个错误是否等于 `nil`, 来进行错误处理。

i, err := strconv.Atoi("42")if err != nil {    fmt.Printf("couldn't convert number: %v\n", err)}fmt.Println("Converted integer:", i)

error 为 nil 时表示成功;非 nil 的 error 表示错误。

Readers

io 包指定了 io.Reader 接口, 它表示从数据流结尾读取。

Go 标准库包含了这个接口的许多实现, 包括文件、网络连接、压缩、加密等等。

io.Reader 接口有一个 Read 方法:

func (T) Read(b []byte) (n int, err error)

Read 用数据填充指定的字节 slice,并且返回填充的字节数和错误信息。 在遇到数据流结尾时,返回 io.EOF 错误。

例子代码创建了一个 strings.Reader。 并且以每次 8 字节的速度读取它的输出。

Web 服务器

包 http 通过任何实现了 http.Handler 的值来响应 HTTP 请求:

package httptype Handler interface {    ServeHTTP(w ResponseWriter, r *Request)}

在这个例子中,类型 Hello 实现了 `http.Handler`。

访问 http://localhost:4000/ 会看到来自程序的问候。

注意: 这个例子无法在基于 web 的指南用户界面运行。为了尝试编写 web 服务器,可能需要安装 Go。

图片

Package image 定义了 Image 接口:

package imagetype Image interface {    ColorModel() color.Model    Bounds() Rectangle    At(x, y int) color.Color}

*注意*:`Bounds` 方法的 Rectangle 返回值实际上是一个 image.Rectangle, 其定义在 image包中。

(参阅文档了解全部信息。)

color.Color 和 color.Model 也是接口,但是通常因为直接使用预定义的实现 image.RGBA 和 image.RGBAModel 而被忽视了。这些接口和类型由image/color 包定义。

5、并发

goroutine

goroutine 是由 Go 运行时环境管理的轻量级线程。

go f(x, y, z)

开启一个新的 goroutine 执行

f(x, y, z)

f , x , y 和 z 是当前 goroutine 中定义的,但是在新的 goroutine 中运行 `f`。

goroutine 在相同的地址空间中运行,因此访问共享内存必须进行同步。sync 提供了这种可能,不过在 Go 中并不经常用到,因为有其他的办法。(在接下来的内容中会涉及到。)

channel

channel 是有类型的管道,可以用 channel 操作符

ch

(“箭头”就是数据流的方向。)

和 map 与 slice 一样,channel 使用前必须创建:

ch := make(chan int)

默认情况下,在另一端准备好之前,发送和接收都会阻塞。这使得 goroutine 可以在没有明确的锁或竞态变量的情况下进行同步。

缓冲 channel

channel 可以是 _带缓冲的_。为 make 提供第二个参数作为缓冲长度来初始化一个缓冲 channel:

ch := make(chan int, 100)

向缓冲 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。当缓冲区清空的时候接受阻塞。

修改例子使得缓冲区被填满,然后看看会发生什么。

range 和 close

发送者可以 close 一个 channel 来表示再没有值会被发送了。接收者可以通过赋值语句的第二参数来测试 channel 是否被关闭:当没有值可以接收并且 channel 已经被关闭,那么经过

v, ok :=

之后 ok 会被设置为 `false`。

循环 `for i := range c` 会不断从 channel 接收值,直到它被关闭。

注意: 只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic。

还要注意: channel 与文件不同;通常情况下无需关闭它们。只有在需要告诉接收者没有更多的数据的时候才有必要进行关闭,例如中断一个 `range`。

select

select 语句使得一个 goroutine 在多个通讯操作上等待。

select 会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。当多个都准备好的时候,会随机选择一个。

默认选择

当 select 中的其他条件分支都没有准备好的时候,`default` 分支会被执行。

为了非阻塞的发送或者接收,可使用 default 分支:

select

官方链接: https://tour.golang.org/welcome/2

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180717G0CVTN00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券