return
语句,会将当前的值作为返回值返回。
语法:func 方法名(参数列表)(返回值列表){
return
}
new 的用法不同:
语法:new(类等名称)
可以将函数赋予变量(与javascript相同)
t:=func........
(`:=` 结构不能使用在函数外,函数外的每个语法块都必须以关键字开始。)const
关键字。
常量可以是字符、字符串、布尔或数字类型的值。
语法:const Pi = 3.14
一个未指定类型的常量由上下文来决定其类型。
for
循环除了没有了 `( )` 之外(甚至强制不能使用它们),看起来跟 Java 中做的一样,而 `{ }` 是必须的。
基于此可以省略分号:java 的 while
在 Go 中叫做 `for`
如果省略了循环条件,循环就不会结束,因此可以用更简洁地形式表达死循环
if 语句除了没有了 `( )` 之外(甚至强制不能使用它们),看起来跟 Java 中的一样,而 `{ }` 是必须的。
跟 for
一样,`if` 语句可以在条件之前执行一个简单的语句。由这个语句定义的变量的作用域仅在 if
和else 范围之内
例子:
package main
import ( "fmt" "math" )
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else{
// v可使用
} return lim
}
func main() {
fmt.Println( pow(3, 2, 10), pow(3, 3, 20), )
}
Name:
语法可以仅列出部分字段。(字段名的顺序无关。)
特殊的前缀 &
构造了指向结构体的指针。
例子:
type Vertex struct {
X, Y int
}
var (
p = Vertex{1, 2} // 类型为 Vertex
q = &Vertex{1, 2} // 类型为 *Vertex
r = Vertex{X: 1} // Y:0 被省略
s = Vertex{} // X:0 和 Y:0
)
func main() {
fmt.Println(p, q, r, s)
}
lo
到 hi-1
的 slice 元素,含两端。因此
s[lo:lo]
是空的,而
s[lo:lo+1]
有一个元素。
例子:
package main
import "fmt"
func main() {
p := []int{2, 3, 5, 7, 11, 13}
fmt.Println("p ==", p)
fmt.Println("p[1:4] ==", p[1:4])
// 省略下标代表从 0 开始
fmt.Println("p[:3] ==", p[:3])
// 省略上标代表到 len(s) 结束
fmt.Println("p[4:] ==", p[4:])
}
slice 的零值是 `nil`。
一个 nil 的 slice 的长度和容量是 0。
for
循环的 range
格式可以对 slice 或者 map 进行迭代循环。
可以将值赋值给 _
来忽略序号和值。
如果只需要索引值,去掉“, value”的部分即可。
例子:
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 {
fmt.Print(i)
}
}
make
而不是 new
来创建;值为 nil
的 map 是空的,并且不能赋值。
与java区别:
声明方式:var声明 m变量名 map[stringkey的类型]Vertexvalue的类型 =make生成对象的方法(map[string]Vertex);
例子:
package main import "fmt"
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 的文法跟结构体文法相似,不过必须有键名。
如果顶级的类型只有类型名的话,可以在文法的元素中省略键名?应该是vale的类名。
例子:
package main
import "fmt"
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
在 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 的元素类型的零值
switch
可能的形式了。
switch 的条件从上到下的执行,当匹配成功的时候停止。
除非使用 fallthrough
语句作为结尾,否则 case 部分会自动终止。(加上fallthrough会执行之后的一个的内容)
例子:
package main import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
//runtime.GOOS获取当前运行的系统
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 链。
func
关键字和方法名之间的参数中。
示例:
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 main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
}
事实上,可以对包中的 任意 类型定义任意方法,而不仅仅是针对结构体。
不能对来自其他包的类型或基础类型定义方法。
方法可以与命名类型或命名类型的指针关联。
刚刚看到的两个 Abs
方法。一个是在 *Vertex
指针类型上,而另一个在MyFloat
值类型上。 有两个原因需要使用指针接收者。首先避免在每个方法调用中拷贝值(如果值类型是大的结构体的话会更有效率)。其次,方法可以修改接收者指向的值。
示例:
package main
import (
"fmt"
"math"
)
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())
}
尝试修改 Abs
的定义,同时 Scale
方法使用 Vertex
代替 *Vertex
作为接收者。(这时是复制了
Vertex
的值,并不会改变原有的 Vertex
值 )
当 v
是 Vertex
的时候 Scale
方法没有任何作用。`Scale` 修改 `v`。当 v
是一个值(非指针),方法看到的是 Vertex
的副本,并且无法修改原始值。
Abs
的工作方式是一样的。只不过,仅仅读取 `v`。所以读取的是原始值(通过指针)还是那个值的副本并没有关系。
Reader
和 `Writer`;其实不一定要这么做
fmt
包的多种不同的打印函数输出一个 error
时,会自动的调用该方法。
示例:
package main
import (
"fmt"
"time"
)
type MyError struct {
When time.Time
What string
}
func (e *MyError) Error() string {
return fmt.Sprintf("at %v, %s",
e.When, e.What)
}
func run() error {
return &MyError{
time.Now(),
"it didn't work",
}
}
func main() {
if err := run(); err != nil {
fmt.Println(err)
}
}
http.Handler
的值来响应 HTTP 请求:
package http
type Handler interface { ServeHTTP(w ResponseWriter, r *Request) }
在这个例子中,类型 Hello
实现了 `http.Handler`。
访问 http://localhost:4000/ 会看到来自程序的问候。
示例:
package main
import (
"fmt"
"net/http"
)
type Hello struct{}
func (h Hello) ServeHTTP(
w http.ResponseWriter,
r *http.Request) {
fmt.Fprint(w, "Hello!")
}
func main() {
var h Hello
http.ListenAndServe("localhost:4000", h)
}
Image
接口:
package image
type Image interface {
ColorModel() color.Model Bounds() Rectangle At(x, y int) color.Color
}
(参阅文档了解全部信息。)
同样,`color.Color` 和 color.Model
也是接口,但是通常因为直接使用预定义的实现 image.RGBAColor
和 image.RGBAColorModel
而被忽视了。
示例:
package main
import (
"fmt"
"image"
)
func main() {
m := image.NewRGBA(image.Rect(0, 0, 100, 100))
fmt.Println(m.Bounds())
fmt.Println(m.At(0, 0).RGBA())
}
线程 1、goroutine
f , x , y
和 z
是当前 goroutine 中定义的,但是在新的 goroutine 中运行 `f`。
goroutine 在相同的地址空间中运行,因此访问共享内存必须进行同步。sync
提供了这种可能,不过在 Go 中并不经常用到,因为有其他的办法。(在接下来的内容中会涉及到。)
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
2、channel
<-
对其发送或者接收值。
ch <- v // 将 v 送入 channel ch。 v := <-ch // 从 ch 接收,并且赋值给 v。
(“箭头”就是数据流的方向。)
和 map 与 slice 一样,channel 使用前必须创建:
ch := make(chan int)
默认情况下,在另一端准备好之前,发送和接收都会阻塞。这使得 goroutine 可以在没有明确的锁或竞态变量的情况下进行同步。
package main
import "fmt"
func sum(a []int, c chan int) {
sum := 0
for _, v := range a {
sum += v
}
c <- sum // 将和送入 c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // 从 c 中获取
fmt.Println(x, y, x+y)
}
3、缓冲 channel
channel 可以是 _带缓冲的_。为 make
提供第二个参数作为缓冲长度来初始化一个缓冲 channel:
ch := make(chan int, 100)
向缓冲 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。当缓冲区清空的时候接受阻塞。
修改例子使得缓冲区被填满,然后看看会发生什么。
package main
import "fmt"
func main() {
c := make(chan int, 2)
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
close
一个 channel 来表示再没有值会被发送了。接收者可以通过赋值语句的第二参数来测试 channel 是否被关闭:当没有值可以接收并且 channel 已经被关闭,那么经过
v, ok := <-ch
之后 ok
会被设置为 `false`。
循环 `for i := range c` 会不断从 channel 接收值,直到它被关闭。
注意: 只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic。 还要注意: channel 与文件不同;通常情况下无需关闭它们。只有在需要告诉接收者没有更多的数据的时候才有必要进行关闭,例如中断一个 `range`。
package main
import (
"fmt"
)
func fibonacci(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
2、select
select
语句使得一个 goroutine 在多个通讯操作上等待。
select
会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支。当多个都准备好的时候,会随机选择一个。
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <-quit:
fmt.Println("quit")
return
}
}
}
func main() {
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
3、默认select
当 select
中的其他条件分支都没有准备好的时候,`default` 分支会被执行。
为了非阻塞的发送或者接收,可使用 default
分支:
select { case i := <-c: // 使用 i default:
// 从 c 读取会阻塞
}
package main import ( "fmt" "time" ) func main() { tick := time.Tick(100 * time.Millisecond) boom := time.After(500 * time.Millisecond) for { select { case <-tick: fmt.Println("tick.") case <-boom: fmt.Println("BOOM!") return default: fmt.Println(" .") time.Sleep(50 * time.Millisecond) } } } 使用并发 (1/2) 1、练习:Web 爬虫 在这个练习中,将会使用 Go 的并发特性来并行执行 web 爬虫。 修改
Crawl
函数来并行的抓取 URLs,并且保证不重复。 package main import ( "fmt" ) type Fetcher interface { // Fetch 返回 URL 的 body 内容,并且将在这个页面上找到的 URL 放到一个 slice 中。 Fetch(url string) (body string, urls []string, err error) } // Crawl 使用 fetcher 从某个 URL 开始递归的爬取页面,直到达到最大深度。 func Crawl(url string, depth int, fetcher Fetcher) { // TODO: 并行的抓取 URL。 // TODO: 不重复抓取页面。 // 下面并没有实现上面两种情况: if depth <= 0 { return } body, urls, err := fetcher.Fetch(url) if err != nil { fmt.Println(err) return } fmt.Printf("found: %s %q\n", url, body) for _, u := range urls { Crawl(u, depth-1, fetcher) } return } func main() { Crawl("http://golang.org/", 4, fetcher) } // fakeFetcher 是返回若干结果的 Fetcher。 type fakeFetcher map[string]*fakeResult type fakeResult struct { body string urls []string } func (f *fakeFetcher) Fetch(url string) (string, []string, error) { if res, ok := (*f)[url]; ok { return res.body, res.urls, nil } return "", nil, fmt.Errorf("not found: %s", url) } // fetcher 是填充后的 fakeFetcher。 var fetcher = &fakeFetcher{ "http://golang.org/": &fakeResult{ "The Go Programming Language", []string{ "http://golang.org/pkg/", "http://golang.org/cmd/", }, }, "http://golang.org/pkg/": &fakeResult{ "Packages", []string{ "http://golang.org/", "http://golang.org/cmd/", "http://golang.org/pkg/fmt/", "http://golang.org/pkg/os/", }, }, "http://golang.org/pkg/fmt/": &fakeResult{ "Package fmt", []string{ "http://golang.org/", "http://golang.org/pkg/", }, }, "http://golang.org/pkg/os/": &fakeResult{ "Package os", []string{ "http://golang.org/", "http://golang.org/pkg/", }, }, } 2、练习:等价二叉树 1. 实现Walk
函数。 2. 测试Walk
函数。 函数tree.New(k)
构造了一个随机结构的二叉树,保存了值 `k`,`2k`,`3k`,...,`10k`。 创建一个新的 channelch
并且对其进行步进: go Walk(tree.New(1), ch) 然后从 channel 中读取并且打印 10 个值。应当是值 1,2,3,...,10。 3. 用Walk
实现Same
函数来检测是否t1
和t2
存储了相同的值。 4. 测试Same
函数。 `Same(tree.New(1), tree.New(1))` 应当返回 true,而 `Same(tree.New(1), tree.New(2))` 应当返回 false。 package main import "code.google.com/p/go-tour/tree" // Walk 步进 tree t 将所有的值从 tree 发送到 channel ch。 func Walk(t *tree.Tree, ch chan int) // Same 检测树 t1 和 t2 是否含有相同的值。 func Same(t1, t2 *tree.Tree) bool func main() { }