- 语句结束没有分号
- import支持导入多个(中间没有逗号)
语法:import ("xx" "xx" "xx") - 导出(类似于public等可被访问):首字母大写的变量及方法是被导出的
- 声明变量:
语法: 变量名:=值 var 变量名,变量名,...... 类型 - 函数:
函数可以没有参数或接受多个参数,函数可以返回任意数量的返回值;当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略;
函数可以返回多个“结果参数”,而不仅仅是一个值。它们可以像变量那样命名和使用。
如果命名了返回值参数,一个没有参数的
语法:func 方法名(参数列表)(返回值列表){return
语句,会将当前的值作为返回值返回。
return
}
new 的用法不同:
语法:new(类等名称)
可以将函数赋予变量(与javascript相同)
t:=func........
(`:=` 结构不能使用在函数外,函数外的每个语法块都必须以关键字开始。) -
Go 的基本类型有Basic types
bool string
int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 uintptr
byte // uint8 的别名
rune // int32 的别名 // 代表一个Unicode码
float32 float64
complex64 complex128
-
常量的定义与变量类似,只不过使用
const
关键字。常量可以是字符、字符串、布尔或数字类型的值。
语法:const Pi = 3.14
一个未指定类型的常量由上下文来决定其类型。 - 循环
Go 只有一种循环结构——`for` 循环。
基本的
for
循环除了没有了 `( )` 之外(甚至强制不能使用它们),看起来跟 Java 中做的一样,而 `{ }` 是必须的。
基于此可以省略分号:java 的while
在 Go 中叫做 `for`
如果省略了循环条件,循环就不会结束,因此可以用更简洁地形式表达死循环 if
语句除了没有了 `( )` 之外(甚至强制不能使用它们),看起来跟 Java 中的一样,而 `{ }` 是必须的。
跟
for
一样,`if` 语句可以在条件之前执行一个简单的语句。由这个语句定义的变量的作用域仅在if
和else 范围之内例子:
package mainimport ( "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), )
}
- 结构:
type用于声明 struct为结构的关键字(与java类似)
例子:
type Vertex struct { X int Y int }
结构体字段使用点号来访问。 指针:
Go 有指针,但是没有指针运算。结构体字段可以通过&结构体指针来访问。通过指针间接的访问是透明的。
例子:
func main() {p := Vertex{1, 2}
q := &p
q.X = 1e9
fmt.Println(p)
}
- 结构体文法:
结构体文法表示通过结构体字段的值作为列表来新分配一个结构体。
使用
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)
}
- slice:
slice 指向一个数组,并且包含了长度信息。
例子:
package mainimport "fmt"
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\n",
i, p[i]) //输出指定格式字符串
}
}
对 slice 进行截取
slice 可以重新切片,创建一个新的 slice 值指向相同的数组。
表达式
s[lo:hi]
表示从
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 mainimport "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)
}
} - Map:
map 映射键到值。
map 在使用之前必须用
make
而不是new
来创建;值为nil
的 map 是空的,并且不能赋值。
与java区别:
声明方式:var声明 m变量名 map[stringkey的类型]Vertexvalue的类型 =make生成对象的方法(map[string]Vertex);
例子:
package mainimport "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 mainimport "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
可能的形式了。switch 的条件从上到下的执行,当匹配成功的时候停止。
除非使用
例子:fallthrough
语句作为结尾,否则 case 部分会自动终止。(加上fallthrough会执行之后的一个的内容)
package mainimport (
}
"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 链。
- 方法:
Go 没有类。然而,仍然可以在结构体类型上定义方法。
方法接收者 出现在
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 mainimport (
"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`。所以读取的是原始值(通过指针)还是那个值的副本并没有关系。 - 接口定义:
接口类型是由一组方法定义的集合。
接口类型的值可以存放实现这些方法的任何值。
示例:
package mainimport (
"fmt"
"math"
)
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
//v := Vertex{3, 4}
a = f // a MyFloat 实现了 Abser
//a = &v // a *Vertex 实现了 Abser
//a = v // a Vertex, 没有实现 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)
}
???隐式接口
类型通过实现那些方法来实现接口。
没有显式声明的必要。
隐式接口解藕了实现接口的包和定义接口的包:互不依赖。
因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。
包 io 定义了
Reader
和 `Writer`;其实不一定要这么做 - 错误:
type error interface { Error() string }
当用fmt
包的多种不同的打印函数输出一个error
时,会自动的调用该方法。
示例:
package mainimport ("fmt""time")type MyError struct {When time.TimeWhat 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)}} - Web 服务器:
包 http 通过任何实现了
http.Handler
的值来响应 HTTP 请求:package http
type Handler interface { ServeHTTP(w ResponseWriter, r *Request) }
在这个例子中,类型
Hello
实现了 `http.Handler`。访问 http://localhost:4000/ 会看到来自程序的问候。
示例:
package mainimport (
"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)
}
- 处理文件:
1、图片(1)
Package image 定义了
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 mainimport (
"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
-
goroutine 是由 Go 运行时环境管理的轻量级线程。
go f(x, y, z)
开启一个新的 goroutine 执行
f(x, y, z)
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
-
channel 是有类型的管道,可以用 channel 操作符
<-
对其发送或者接收值。ch <- v // 将 v 送入 channel ch。 v := <-ch // 从 ch 接收,并且赋值给 v。
(“箭头”就是数据流的方向。)
和 map 与 slice 一样,channel 使用前必须创建:
ch := make(chan int)
默认情况下,在另一端准备好之前,发送和接收都会阻塞。这使得 goroutine 可以在没有明确的锁或竞态变量的情况下进行同步。
package mainimport "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 mainimport "fmt"
func main() {
c := make(chan int, 2)
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
- 并发机制:
-
控制线程范围
1、range 和 close
发送者可以
close
一个 channel 来表示再没有值会被发送了。接收者可以通过赋值语句的第二参数来测试 channel 是否被关闭:当没有值可以接收并且 channel 已经被关闭,那么经过v, ok := <-ch
之后
ok
会被设置为 `false`。循环 `for i := range c` 会不断从 channel 接收值,直到它被关闭。
注意: 只有发送者才能关闭 channel,而不是接收者。向一个已经关闭的 channel 发送数据会引起 panic。 还要注意: channel 与文件不同;通常情况下无需关闭它们。只有在需要告诉接收者没有更多的数据的时候才有必要进行关闭,例如中断一个 `range`。
package mainimport (
"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 mainimport (
"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() {
}
有疑问加站长微信联系(非本文作者)