var a []int x = a[1] var p *int value := *p pointer := &value
多值返回
函数可以返回任意数量的返回值。
1 2 3 4 5 6 7 8 9
package main import"fmt" funcswap(a, b string)(string, string){ return b, a } funcmain(){ a, b := swap("hello", "world") // '' 与 ""不同 fmt.Println("a, b:", a, b) }
命名返回值
Go 的返回值可被命名,它们会被视作定义在函数顶部的变量。
返回值的名称应当具有一定的意义,它可以作为文档使用。
没有参数的 return 语句返回已命名的返回值。也就是 直接 返回。
直接返回语句应当仅用在下面这样的短函数中。在长的函数中它们会影响代码的可读性。
1 2 3 4 5 6 7 8 9 10
package main import"fmt" funcsplit(sum int)(x, y int) { x = sum * 4 /9 y = sum - x return } funcmain() { fmt.Println(split(17)) }
变量
var语句用于声明一个变量列表,跟函数的参数列表一样,类型在最后。
就像在这个例子中看到的一样,var 语句可以出现在包或函数级别。
1 2 3 4 5 6 7 8 9 10
package main import ( "fmt" ) var c, python, java bool; var i int; // var x float32, y int16; syntax error funcmain() { fmt.Println(i, c, python, java); }
变量的初始化
变量声明可以包含初始值,每个变量对应一个。
如果初始化值已存在,则可以省略类型;变量会从初始值中获得类型。
1 2 3 4 5 6 7
package main import"fmt" var i, j int = 1, 2 funcmain() { var c, python, java = true, false, "no!" fmt.Println(i, j, c, python, java) }
短变量声明
在函数中,简洁赋值语句:=可在类型明确的地方代替var声明。
函数外的每个语句都必须以关键字开始(var, func 等等),因此 :=结构不能在函数外使用。
1 2 3 4 5 6 7 8 9
package main import"fmt" funcmain() { var i, j int = 1, 2 k := 3 c, python, java := true, false, "no!"
package main import"fmt" funcmain() { var i int var f float64 var b bool var s string fmt.Printf("%v %v %q %q\n", i, f, b, s)// %v 打印值;%T 打印类型,%q 打印全部信息 }
类型转换
表达式T(v)将值v转换为类型T 一些关于数值的转换:
1 2 3 4 5 6 7
var i int = 42 var f float64 = float64(i) var u int = uint(f) // 或者,更加简单的形式: i := 42 f := float64(i) u := uint(f)
与 C 不同的是,Go 在不同类型的项之间赋值时需要显式转换。试着移除例子中 float64 或 uint 的转换看看会发生什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
package main import ( "fmt" "math" ) funcmain() { var x, y int = 3, 4 // var f float64 = math.Sqrt(float64(x*x + y*y)) // var u uint = uint(f) // var f = math.Sqrt(float64(x*x + y*y)) // var u = uint(f) f := math.Sqrt(float64(x*x + y*y)) u := uint(f) // fmt.Printf(x, f, u) fmt.Printf针对string fmt.Println(x, f, u) }
i := 2// int f := 3.142// float64 g := 0.867 + 0.5i//complex128 package main import"fmt" funcmain() { v := 42// 修改这里! fmt.Printf("v is of type %T\n", v) }
package main import ( "fmt" ) const ( // create a huge number by shifting 1 bit left 100 spaces // In other words, the binary number that is 1 followed by 100 zeros Big = 1 << 100 // shift it right again in 99 places, so we end up with 1<<1 or 2. the binary number Small = Big >> 99 ) funcneedInt(x int)int { return10*x + 1} funcneedFloat(x float64)float64 { return x * 0.1} funcmain() { // fmt.Println(Big) fmt.Println(Small)
package main import ( "fmt" ) funcmain() { // var sum int = 1 // var sum = 1 // var ( // sum int = 1 // ) sum := 0; for i:=1; i <= 10; i++ { sum += i fmt.Println(sum) } fmt.Println(sum) } // exchange package main import ( "fmt" ) funcmain() { // var sum = 0 // var sum int = 0 // var ( // sum int = 1 // ) sum := 1 for ;sum< 10; { sum += sum fmt.Println(sum) } }
for 是 Go 中的 “while”
此时你可以去掉分号,因为 C 的while在 Go 中叫做for。
1 2 3 4 5 6 7 8 9 10 11
package main import ( "fmt" ) funcmain() { sum := 1 for sum < 1000 { sum += sum } fmt.Println(sum) }
无限循环
如果省略循环条件,该循环就不会结束,因此无限循环可以写得很紧凑。
1 2 3 4 5 6 7 8
package main import ( "fmt" ) funcmain() { for { } }
if
Go 的 if 语句与 for 循环类似,表达式外无需小括号 ( ),而大括号 { } 则是必须的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
package main import ( "fmt" "math" ) funcsqrt(x float64)string { if x < 0 { // return string(math.Sqrt(-x)) + "i" error // return math.Sqrt(x) cannot convert float64 into string return sqrt(-x)+"i" } return fmt.Sprint(math.Sqrt(x)) // use Sprint to convert float64 into string } funcmain() { fmt.Println(sqrt(2), sqrt(-4)) }
if 的简短语句
同 for 一样, if 语句可以在条件表达式前执行一个简单的语句。 该语句声明的变量作用域仅在 if 之内。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
package main import ( "fmt" "math" ) funcpow(x, n, lim float64)float64 { if v := math.Pow(x, n); v < lim { return v } return lim }
package main import ( "fmt" ) type Vertex struct { X int Y int } funcmain() { v := Vertex{1, 2} p := &v // (*p).X = 4 // fmt.Println(*p) p.X = 3e3 fmt.Println(*p) }
// slices go package main import ( "fmt" ) funcprintSlices(s []int) { fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s); } funcmain() { s := []int{3, 4, 5,7, 8, 10}; printSlices(s); // Slices the slice to give it zero length s = s[:0]; printSlices(s); // extend th length s = s[:4]; printSlices(s); // drop its first two values s = s[:2]; printSlices(s); //len=2 cap=6 [3 4] s = s[:6]; printSlices(s); //len=6 cap=6 [3 4 5 7 8 10] s = s[3:4]; printSlices(s); //len=1 cap=3 [7] }
nil切片
切片的的零值是nil nil切片的长度和容量为0切没有底层数组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// nil slices package main import ( "fmt" ) funcmain() { // var s = []int //error []int is not expression // var s []int = []int{1, 2, 3} <==> s := []int{1, 2, 3} var s []int fmt.Println(s, len(s), cap(s)) if s == nil { fmt.Println("nil") } }
用 make 创建切片
切片可以用内建函数 make 来创建,这也是你创建动态数组的方式。 make 函数会分配一个元素为零值的数组并返回一个引用了它的切片: a := make([]int, 5) // len(a)=5 要指定它的容量,需向 make 传入第三个参数:
1 2 3
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
为切片追加新的元素是种常用的操作,为此 Go 提供了内建的 append 函数。内建函数的文档对此函数有详细的介绍。 func append(s []T, vs ...T) []T append 的第一个参数 s 是一个元素类型为 T 的切片,其余类型为 T 的值将会追加到该切片的末尾。 append 的结果是一个包含原切片所有元素加上新添加元素的切片。 当 s 的底层数组太小,不足以容纳所有给定的值时,它就会分配一个更大的数组。返回的切片会指向这个新分配的数组。
// append package main import ( "fmt" ) funcprintSlices(s []int) { fmt.Printf("len=%d cap=%d value=%v type=%T\n", len(s), cap(s), s, s); } funcmain() { var s []int; printSlices(s); // append works on nil slices s = append(s, 0); printSlices(s); // the slices grows as needed s = append(s, 1); printSlices(s); // we can add more than one element at time s = append(s, 2, 4, 5, 6); printSlices(s); // s1 := []int{7, 4, 2}; // 不是同一种类型 // s = append(s, s1); // printSlices(s); }
Range
for 循环的 range 形式可遍历切片或映射。 当使用 for 循环遍历切片时,每次迭代都会返回两个值。第一个值为当前元素的下标,第二个值为该下标所对应元素的一份副本。
1 2 3 4 5 6 7 8 9 10 11
// Range package main import ( "fmt" ) var pow = []int{1, 2, 4, 8, 16, 32, 64, 128} funcmain() { for i, v := range pow { fmt.Printf("2**%d = %d\n", i, v) } }
可以将下标或值赋予 _ 来忽略它。 若你只需要索引,去掉 , value 的部分即可。
1 2 3 4 5 6 7 8 9 10 11 12 13
package main import( "fmt" ) funcmain() { pow := make([]int, 10) for i := range pow { pow[i] = 1 << uint(i) // ==2**i } for _, value := range pow { fmt.Printf("%d\n", value) } }
练习:切片
实现 Pic。它应当返回一个长度为 dy 的切片,其中每个元素是一个长度为 dx,元素类型为 uint8 的切片。当你运行此程序时,它会将每个整数解释为灰度值(好吧,其实是蓝度值)并显示它所对应的图像。
package main import ( "fmt" ) type I interface { M(); } type T struct { S string; } // This method means type T implements the inteface I, // but we don't need to explicitly declare that it does so. func(t T)M() { fmt.Println(t.S); } funcmain() { var i I = T{"hellosdsfs"}; i.M(); }
package main import ( "fmt" ) // 1.声明一个接口I type I interface { M(); } // 2.声明具体类型T 结构体 type T struct { S string } // 3.具体类型结构体T(reciever) 实现接口的方法(隐式实现) func(t *T)M() { if t == nil { fmt.Println("<nil>"); return; } fmt.Println(t.S); } funcmain() { var i I; var t *T; i = t; describe(i); i.M(); i = &T{"Hellowqww"}; describe(i); i.M(); } funcdescribe(i I) { fmt.Printf("(%v %T)\n", i, i); }
package main import ( "fmt" ) funcmain() { var i interface{}; describe(i); i = 42; describe(i); i = "hello"; describe(i); } funcdescribe(i interface{}) { fmt.Printf("(%v %T)\n", i, i); }
类型断言
类型断言 提供了访问接口值底层具体值的方式。 t := i.(T) 该语句断言接口值 i 保存了具体类型 T,并将其底层类型为 T 的值赋予变量 t。 若 i 并未保存 T 类型的值,该语句就会触发一个panic。 为了 判断 一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。 t, ok := i.(T) 若 i 保存了一个 T,那么 t 将会是其底层值,而 ok 为 true。 否则,ok 将为 false 而 t 将为 T 类型的零值,程序并不会产生恐慌。 请注意这种语法和读取一个映射时的相同之处。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
package main import ( "fmt" ) funcmain() { // 声明一个接口, var i interface{} = "hello"; fmt.Printf("(%v %T)\n",i, i); s := i.(string); fmt.Println(s); s, ok := i.(string); fmt.Println(s, ok); f, ok := i.(float64); fmt.Println(f, ok); f = i.(float64); fmt.Println(f); }
类型选择
类型选择 是一种按顺序从几个类型断言中选择分支的结构。 类型选择与一般的 switch 语句相似,不过类型选择中的 case 为类型(而非值), 它们针对给定接口值所存储的值的类型进行比较。
1 2 3 4 5 6 7 8
switch v := i.(type) { case T: // v 的类型为 T case S: // v 的类型为 S default: // 没有匹配,v 与 i 的类型相同 }
类型选择中的声明与类型断言 i.(T) 的语法相同,只是具体类型 T 被替换成了关键字 type。 此选择语句判断接口值 i 保存的值类型是 T 还是 S。在 T 或 S 的情况下,变量 v 会分别按 T 或 S 类型保存 i 拥有的值。在默认(即没有匹配)的情况下,变量 v 与 i 的接口类型和值相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
package main import ( "fmt" ) funcdo(i interface{}) { switch v := i.(type) { caseint: fmt.Printf("Twice %v is %v\n", v, v*2); casestring: fmt.Printf("%q is %v bytes long\n", v, len(v)); default: fmt.Printf("I don't know about type %T!\n", v); } } funcmain() { do(11); do("hell9"); do(true); }
Stringer
fmt 包中定义的 Stringer 是最普遍的接口之一。
1 2 3
type Stringer interface { String() string }
Stringer 是一个可以用字符串描述自己的类型。fmt 包(还有很多包)都通过此接口来打印值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
package main import ( "fmt" ) // type Stringer interface { // String() string; // } type Person struct { Name string Age int } func(p Person)String()string { return fmt.Sprintf("%v (%v years)\n", p.Name, p.Age); } funcmain() { a := Person{"Arthur Dent", 43}; z := Person{"Zaphod Beelebrox", 9001}; fmt.Println(a, z); }
funcmain() { r := strings.NewReader("Hello, Reader!")
b := make([]byte, 8) for { n, err := r.Read(b) fmt.Printf("n = %v err = %v b = %v\n", n, err, b) fmt.Printf("b[:n] = %q\n", b[:n]) if err == io.EOF { break } } }
练习:Reader
实现一个 Reader 类型,它产生一个 ASCII字符 'A' 的无限流。
1 2 3 4 5 6 7 8 9 10 11
package main
import"golang.org/x/tour/reader"
type MyReader struct{}
// TODO: Add a Read([]byte) (int, error) method to MyReader.
At 应当返回一个颜色。上一个图片生成器的值 v 对应于此次的 color.RGBA{v, v, 255, 255}。
1 2 3 4 5 6 7 8 9 10
package main
import"golang.org/x/tour/pic"
type Image struct{}
funcmain() { m := Image{} pic.ShowImage(m) }
并发
goroutine
Go 程(goroutine)是由 Go 运行时管理的轻量级线程。 go f(x, y, z)会启动一个新的 goroutine并执行f(x, y, z)。 f, x, y 和 z 的求值发生在当前的 Go 程中,而f的执行发生在新的 Go 程中。 Go 程在相同的地址空间中运行,因此在访问共享的内存时必须进行同步。sync 包提供了这种能力,不过在 Go 中并不经常用到,因为还有其它的办法(见下一页)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
package main import ( "fmt" "time" ) funcsay(s string) { for i:= 1; i < 5; i++ { time.Sleep(100*time.Millisecond); fmt.Println(s); } } funcmain() { go say("hellod"); // fmt.Printf("address: %p", say); say("easonchen"); }
信道(Channels)
信道是带有类型的管道,你可以通过它用信道操作符 <- 来发送或者接收值。
1 2
ch <- v // 将 v 发送至信道 ch。 v := <-ch // 从 ch 接收值并赋予 v。
(“箭头”就是数据流的方向。) 和映射与切片一样,信道在使用前必须创建:ch := make(chan int) 默认情况下,发送和接收操作在另一端准备好之前都会阻塞。这使得 Go 程可以在没有显式的锁或竞态变量的情况下进行同步。 以下示例对切片中的数进行求和,将任务分配给两个 Go 程。一旦两个 Go 程完成了它们的计算,它就能算出最终的结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
package main import ( "fmt" ) funcsum(s []int, c chanint) { sum := 0; for _, v := range s { sum += v; } c <- sum;// 将和送入 c } funcmain() { s := []int{1, 2, 3, 5, 7, 9}; c := make(chanint); go sum(s[:len(s)/2], c); go sum(s[len(s)/2:], c); x, y := <-c, <-c; // 接受者 fmt.Println(x, y, x+y); }
发送者可通过 close 关闭一个信道来表示没有需要发送的值了。接收者可以通过为接收表达式分配第二个参数来测试信道是否被关闭:若没有值可以接收且信道已被关闭,那么在执行完 v, ok := <-ch 之后 ok 会被设置为 false。 循环 for i := range c 会不断从信道接收值,直到它被关闭。 Note: 只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引发程序恐慌(panic)。 Another Note: 信道与文件不同,通常情况下无需关闭它们。只有在必须告诉接收者不再有值需要发送的时候才有必要关闭,例如终止一个 range 循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
package main import ( "fmt" ) funcfibonacci(n int, c chanint) { x, y := 0, 1; for i:=0; i<n; i++ { c <- x; x, y = y, x+y; } close(c); } funcmain() { c := make(chanint, 10); go fibonacci(cap(c), c); for i := range c { fmt.Println(i); } }
select 语句
select 语句使一个 Go 程可以等待多个通信操作。 select 会阻塞到某个分支可以继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。
package main import"golang.org/x/tour/tree" // Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。 funcWalk(t *tree.Tree, ch chanint) // Same 检测树 t1 和 t2 是否含有相同的值。 funcSame(t1, t2 *tree.Tree)bool funcmain() { }
sync.Mutex
我们已经看到信道非常适合在各个 Go 程间进行通信。
但是如果我们并不需要通信呢?比如说,若我们只是想保证每次只有一个 Go 程能够访问一个共享的变量,从而避免冲突?