中文版: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(42, 13)) }
函数可以有零个或多个参数。
在这个例子中,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 * 4/9 y = sum - x return } func main() { fmt.Println(split(17)) }
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 = 1, 2, 3 var c, python, java = true, false, "no!" func main() { fmt.Println(x, y, z, c, python, java) }
变量声明时可以包含初始值,每个变量对应一个。
如果初始值是存在的,则可以省略类型声明;变量将从初始值中获得类型。
func main() { var x, y, z int = 1, 2, 3 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 = 1<<100 Small = Big>>99 ) func needInt(x int) int { return x*10 + 1 } 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 := 0 for i := 0; i < 10; i++ { sum += i } fmt.Println(sum) }
Go 只有一种循环结构,for
循环。
基本的 for
循环看起来跟 C 或者 Java 中做的一样,除了没有了 ( )
之外(甚至强制不能使用它们),而 { }
是必须的。
func main() { sum := 1 for ; sum < 1000; { sum += sum } fmt.Println(sum) }
As in C or Java, you can leave the pre and post statements empty.
跟 C 或者 Java 中一样,前置、后置条件可以为空。
func main() { sum := 1 for sum < 1000 { 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 ", v, lim) } // can't use v here, though return lim } func main() { fmt.Println( pow(3, 2, 10), pow(3, 3, 20), ) }
在 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
A 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{1, 2}) }
Struct Fields(结构体成员变量)使用点号来访问。
func main(){ v :=Vertex{1,2} v.X=4 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{1,2} 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{1, 2} // has type Vertex q = &Vertex{1, 2} // has type *Vertex r = Vertex{X: 1} // 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 = 11, 9 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 m
, ok
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"] = 42 fmt.Println("The value:", m["Answer"]) m["Answer"] = 48 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{2, 3, 5, 7, 11, 13} fmt.Println("p ==", p) for i := 0; i < len(p); i++ { fmt.Printf("p[%d] == %d ", i, p[i]) } }
slice 可以重新切片,创建一个新的 slice 值指向相同的数组。
表达式
s[lo:hi]
表示从 lo
到 hi-1
的 slice 元素,含有两端。 因此
s[lo:lo]
是空的,而
s[lo:lo+1]
有一个元素。
func main() { p := []int{2, 3, 5, 7, 11, 13} fmt.Println("p ==", p) fmt.Println("p[1:4] ==", p[1:4]) // missing low index implies 0 fmt.Println("p[:3] ==", p[:3]) // missing high index implies len(s) fmt.Println("p[4:] ==", p[4:]) }
跟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, 5) printSlice("a", a) b := make([]int, 0, 5) printSlice("b", b) c := b[:2] printSlice("c", c) d := c[2:5] printSlice("d", d) e :=make([]int,10) 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 := 0 return func(x int) int { sum += x return sum } } func main() { pos, neg := adder(), adder() for i := 0; i < 10; i++ { fmt.Println( pos(i), neg(-2*i), ) } }
并且函数是完全闭包的。
函数 adder
返回一个闭包。每个闭包被绑定到自己的 sum
变量上。
Functions
And functions are full closures.
The adder
function returns a closure. Each closure is bound to its own sum
variable.
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, 10) for i := range pow { pow[i] = 1<<uint(i) } for _, value := range pow { fmt.Printf("%d ", 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() < 12: fmt.Println("Good morning!") case t.Hour() < 17: 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{3, 4} fmt.Println(v.Abs()) }
事实上,可以对包中的任意类型定义任意方法,而不仅仅是结构体。
不能对来自其他包的类型或基础类型定义方法。
type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { 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{3, 4} v.Scale(5) 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{3, 4} 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 < 0 { 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 ") }