go官网教程A Tour of Go

时间:2023-03-09 18:52:08
go官网教程A Tour of Go

http://tour.golang.org/#1

中文版:http://go-tour-cn.appsp0t.com/#4

package main

import (
"fmt"
"math"
)

func main() {
fmt.Println("Happy", math.Pi, "Day")
}

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

程序运行的入口从包的 main方法。

这个程序使用并导入了包 "fmt" 和 "math"

按惯例,包名与导入路径的最后一个目录一致。

这个代码用圆括号组合了导入,这是“factored”(分解因子)式导入声明。同样可以编写多个导入语句,例如:

        import "fmt"
import "math"

不过通常都会用 factored 格式来使代码工整。

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

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

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

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

package main

import "fmt"

func add(x int, y int) int {
return x + y
} func main() {
fmt.Println(add(, ))
}

函数可以有零个或多个参数。

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

注意类型声明在变量名之后。(个人认为这个可以理解)

(参考这篇关于 Go 语法定义的文章了解类型以这种形式出现的原因。)

func swap(x, y string) (string, string) {
return y, x
} func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}

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

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

func split(sum int) (x, y int) {
x = sum * /
y = sum - x
return
} func main() {
fmt.Println(split())
}

Functions take parameters. In Go, functions can return multiple "result parameters", not just a single value. They can be named and act just like variables.

If the result parameters are named, a return statement without arguments returns the current values of the results.

函数有参数。在 Go 中,函数可以返回多个“结果参数”,而不仅仅是一个值。它们可以像变量那样被命名和使用。

如果命名了返回值的参数,一个没有参数的 return 语句,会将当前的值作为返回值返回。

Var:

var x, y, z int
var c, python, java bool func main() {
fmt.Println(x, y, z, c, python, java)
}

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

0 0 0 false false false
var x, y, z int = , ,
var c, python, java = true, false, "no!" func main() {
fmt.Println(x, y, z, c, python, java)
}

变量声明时可以包含初始值,每个变量对应一个。

如果初始值是存在的,则可以省略类型声明;变量将从初始值中获得类型。

func main() {
var x, y, z int = , ,
c, python, java := true, false, "no!" fmt.Println(x, y, z, c, python, java)
}

在一个函数里面,短赋值语句:= 可以用于替代 var 的隐式类型声明。

:= 结构不能使用在函数外,函数外的每个语法块都必须以关键字开始。)

Constant:

const Pi = 3.14

func main() {
const World = "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day") const Truth = true
fmt.Println("Go rules?", Truth)
}

Constants are declared like variables, but with the const keyword.

Constants can be character, string, boolean, or numeric values.

注意这个Println。

数值常量:

const (
Big = <<
Small = Big>>
) func needInt(x int) int { return x* + }
func needFloat(x float64) float64 {
return x*0.1
} func main() {
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}

数值常量是高精度的

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

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

For:

func main() {
sum :=
for i := ; i < ; i++ {
sum += i
}
fmt.Println(sum)
}

Go 只有一种循环结构,for 循环。

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

func main() {
sum :=
for ; sum < ; {
sum += sum
}
fmt.Println(sum)
}

As in C or Java, you can leave the pre and post statements empty.

跟 C 或者 Java 中一样,前置、后置条件可以为空。

func main() {
sum :=
for sum < {
sum += sum
}
fmt.Println(sum)
}

基于这一点,你也可以省略分号: C 的 while 循环在 Go 中也是用 for 实现。

func main() {
for ; ; {
}
}

如果省略了循环条件,它会一直循环下去(译者:死循环或无限循环)。

func main() {
for {
}
}

为了避免累赘,分号也可以省略,这样一个无限循环可以被简洁地表达。

if:

import (
"fmt"
"math"
) func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// can't use v here, though
return lim
} func main() {
fmt.Println(
pow(, , ),
pow(, , ),
)
}

在 if 的简短声明处定义的变量同样可以在对应的 else 块中使用。这点要特别注意。

Go 的基本类型有

bool

string

int  int8  int16  int32  int64
uint uint8 uint16 uint32 uint64 uintptr byte // alias for uint8 rune // alias for int32
// represents a Unicode code point float32 float64 complex64 complex128

Structs

struct is a collection of fields.

(And a type declaration does what you'd expect.)

一个结构体(struct)就是一个成员变量的集合。

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

type Vertex struct {
X int
Y int
} func main() {
fmt.Println(Vertex{, })
}

Struct Fields(结构体成员变量)使用点号来访问。

func main(){
v :=Vertex{,}
v.X=
fmt.Println(v.X)
}

Pointers

Go has pointers, but no pointer arithmetic.

Struct fields can be accessed through a struct pointer. The indirection through the pointer is transparent.

Go 有指针,但是没有指针运算。

结构体成员变量可以通过结构体指针来访问。通过指针的间接访问也是透明的。

type Vertex struct{
X int
Y int
}
func main(){
p := Vertex{,}
q :=&p
q.X=1e9
fmt.Println(p)
}

输出:{1000000000 2}

说明可以直接输出结构体。

Struct Literals

Struct Literals(结构体文法)表示通过结构体成员变量的值作为列表来新分配一个结构体。

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

特殊的前缀 & 构造了指向结构体文法的指针。


Struct Literals

A struct literal denotes a newly allocated struct value by listing the values of its fields.

You can list just a subset of fields by using the Name: syntax. (And the order of named fields is irrelevant.)

The special prefix & constructs a pointer to a struct literal.

type Vertex struct {
X, Y int
} var (
p = Vertex{, } // has type Vertex
q = &Vertex{, } // has type *Vertex
r = Vertex{X: } // Y:0 is implicit
s = Vertex{} // X:0 and Y:0
) func main() {
fmt.Println(p, q, r, s)
}
{1 2} &{1 2} {1 0} {0 0}

new 函数

表达式 new(T) 分配了一个零初始化的 T 值,并返回指向它的指针。(感觉这个语法有点奇怪,()里面为类型,怎么初始化呢?

var t *T = new(T)

t := new(T)

The new function

The expression new(T) allocates a zeroed T value and returns a pointer to it.

var t *T = new(T)

or

t := new(T)
type Vertex struct {
X, Y int
} func main() {
v := new(Vertex)
fmt.Println(v)
v.X, v.Y = ,
fmt.Println(v)
}

Map

map 映射键到值。

map 必须用 make 来创建(不是 new);一个值为 nil 的 map 是空的,并且不能赋值。


Maps

A map maps keys to values.

Maps must be created with make (not new) before use; the nil map is empty and cannot be assigned to.

type Vertex struct {
Lat, Long float64
} var m map[string]Vertex func main() {
m = make(map[string]Vertex)
m["Bell Labs"] = Vertex{
40.68433, 74.39967,
}
fmt.Println(m["Bell Labs"])
}

map literals(map 的文法)跟struct literals(结构体文法)相似,但是键是必须的。


Maps

Map literals are like struct literals, but the keys are required.

type Vertex struct {
Lat, Long float64
} var m = map[string]Vertex{
"Bell Labs": Vertex{
40.68433, -74.39967,
},
"Google": Vertex{
37.42202, -122.08408,
},
} func main() {
fmt.Println(m)
}
map[Bell Labs:{40.68433 -74.39967} Google:{37.42202 -122.08408}]

Map

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


Maps

If the top-level type is just a type name, you can omit it from the elements of the literal.

type Vertex struct {
Lat, Long float64
} var m = map[string]Vertex{
"Bell Labs": {40.68433, -74.39967},
"Google": {37.42202, -122.08408},
} func main() {
fmt.Println(m)
}

Insert or update an element in map m:

m[key] = elem

Retrieve an element:

elem = m[key]

Delete an element:

delete(m, key)

Test that a key is present with a two-value assignment:

elem, ok = m[key]

If key is in mok is true. If not, ok is false and elem is the zero value for the map's element type.

Similarly, when reading from a map if the key is not present the result is the zero value for the map's element type.

func main() {
m := make(map[string]int) m["Answer"] =
fmt.Println("The value:", m["Answer"]) m["Answer"] =
fmt.Println("The value:", m["Answer"]) delete(m, "Answer")
fmt.Println("The value:", m["Answer"]) v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
}

Slice

slice 指向数组的值,并且同时包含了长度信息。

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


Slices

A slice points to an array of values and also includes a length.

[]T is a slice with elements of type T. (java是T【】)

func main() {
p := []int{, , , , , }
fmt.Println("p ==", p) for i := ; i < len(p); i++ {
fmt.Printf("p[%d] == %d\n",
i, p[i])
}
}

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

表达式

s[lo:hi]

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

s[lo:lo]

是空的,而

s[lo:lo+1]

有一个元素。

func main() {
p := []int{, , , , , }
fmt.Println("p ==", p)
fmt.Println("p[1:4] ==", p[:]) // missing low index implies 0
fmt.Println("p[:3] ==", p[:]) // missing high index implies len(s)
fmt.Println("p[4:] ==", p[:])
}

跟python一样。

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

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

slice 有长度和容量。slice 的容量是底层数组可以增长的最大长度。

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

b := make([]int, 0, 5)
// len(b)=0, cap(b)=5

slice 可以通过“重新切片”来扩容(增加容量):

b = b[:cap(b)] // len(b)=5, cap(b)=5
b = b[1:] // len(b)=4, cap(b)=4
func main() {
a := make([]int, )
printSlice("a", a)
b := make([]int, , )
printSlice("b", b)
c := b[:]
printSlice("c", c)
d := c[:]
printSlice("d", d) e :=make([]int,)
printSlice("e",e);
}

打印slice通过printSlice

function

函数也是值。

func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}

fmt.Println(hypot(3, 4))
}

函数

func adder() func(int) int {
sum :=
return func(x int) int {
sum += x
return sum
}
} func main() {
pos, neg := adder(), adder()
for i := ; i < ; i++ {
fmt.Println(
pos(i),
neg(-*i),
)
}
}

并且函数是完全闭包的。

函数 adder 返回一个闭包。每个闭包被绑定到自己的 sum 变量上。


Functions

And functions are full closures.

The adder function returns a closure. Each closure is bound to its own sumvariable.

Range

可以将值赋值给 _ 来忽略键和值。

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


Range

You can skip the index or value by assigning to _.

If you only want the index, drop the “, value” entirely.

    pow := make([]int, )
for i := range pow {
pow[i] = <<uint(i)
}
for _, value := range pow {
fmt.Printf("%d\n", value)

Switch

你可能已经猜到 switch 的形式了。

case 语句匹配后会自动终止,除非用 fallthrough 语句作为结尾。

import (
"fmt"
"runtime"
) func main() {
fmt.Print("Go runs on ")
switch os := runtime.GOOS; os {
case "darwin":
fmt.Println("OS X.")
case "linux":
fmt.Println("Linux.")
default:
// freebsd, openbsd,
// plan9, windows...
fmt.Printf("%s.", os)
}
}

没有条件的 switch 与 switch true 一样。

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

import (
"fmt"
"time"
) func main() {
t := time.Now()
switch {
case t.Hour() < :
fmt.Println("Good morning!")
case t.Hour() < :
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}

方法

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

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

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 main() {
v := &Vertex{, }
fmt.Println(v.Abs())
}

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

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

type MyFloat float64

func (f MyFloat) Abs() float64 {
if f < {
return float64(-f)
}
return float64(f)
} func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}

接收者为指针的方法

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

刚刚看到的两个 Abs 方法。一个是在 *Vertex 指针类型上,而另一个在MyFloat 值类型上。

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

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

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

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

type Vertex struct {
X, Y float64
} func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
} func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
} func main() {
v := &Vertex{, }
v.Scale()
fmt.Println(v, v.Abs())
}
&{15 20} 25
去掉指针值没有变化。

接口

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

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


Interfaces

An interface type is defined by a set of methods.

A value of interface type can hold any value that implements those methods.

type Abser interface {
Abs() float64
} func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{, } a = f // a MyFloat implements Abser
a = &v // a *Vertex implements Abser
a = v // a Vertex, does NOT
// implement Abser fmt.Println(a.Abs())
} type MyFloat float64 func (f MyFloat) Abs() float64 {
if f < {
return float64(-f)
}
return float64(f)
} type Vertex struct {
X, Y float64
} func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

一种类型通过实现那些方法来实现接口。

没有显式声明的必要。

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

也鼓励明确的接口定义,因为这样就无需找到每一个实现,并对其加上新的接口名称。

Package io 定义了 Reader 和 Writer;但不是一定要这么做。

import (
"fmt"
"os"
) type Reader interface {
Read(b []byte) (n int, err error)
} type Writer interface {
Write(b []byte) (n int, err error)
} type ReadWriter interface {
Reader
Writer
} func main() {
var w Writer // os.Stdout implements Writer
w = os.Stdout fmt.Fprintf(w, "hello, writer\n")
}