#杂项 := 这符号dephi里有,在go里 我看它的意义是 定义并赋值 define and assign。
if和switch都可以包含 初始化语句 (initialization statement)
if err := file.Chmod(0664); err != nil { //← nil is like C's NULL
fmt.Printf(err) //← Scope of err is limited to if's body
return err
}
请看if后面用来限定条件的括号不见了,分号前面的部分是初始化部分,分号后面的才是判断条件。
if err != nil
{ //←Must be on the same line as the if
return err
}
上面的代码是错误的,在Go里,左花括号必须与if、switch等在_同行_。
range关键字很强大,它能用在for循环里,It can loop over slices, arrays, strings, maps and channels。 遍历的对象不同,range返回的结果也不同,如果是slice, array,那第一个返回值是索引,第二个返回值是索引所在位置的数据。
list := []string{"a", "b", "c", "d", "e", "f"}
for k, v := range list {
//do what you want with k and v
}
switch要特别注意,请看下面的例子: switch i { case 0: // empty case body case 1: f() //f is not called when i==0! }
在C,java中,如果i为0,则会自动穿透而执行下面的语句,如果不想穿透则需要一个break关键字,而Go的思路是相反的!Go里默认是不穿透的,如果想穿透则必须使用关键字:fallthrough。
switch i {
case 0: fallthrough
case 1:
f() //f is called when i==0!
}
case语句后面可以接逗号分隔的列表。
func shouldEscape(c byte) bool {
switch c {
case ' ', '?', '&', '=', '#', '+':
return true
}
return false
}
-
首先从声明上可以看出来,数据在声明时必须有length;而slice则不需要。下面是go spec里关于array和slice的说明:
Array types
An array is a numbered sequence of elements of a single type, called the element type. The number of elements is called the length and is never negative.
ArrayType = "[" ArrayLength "]" ElementType . ArrayLength = Expression . ElementType = Type . The length is part of the array's type; it must evaluate to a non- negative constant representable by a value of type int. The length of array a can be discovered using the built-in function len. The elements can be addressed by integer indices 0 through len(a)-1. Array types are always one-dimensional but may be composed to form multi-dimensional types.
[32]byte [2*N] struct { x, y int32 } [1000]*float64 [3][5]int [2][2][2]float64 // same as 2 Slice types
A slice is a descriptor for a contiguous segment of an array and provides access to a numbered sequence of elements from that array. A slice type denotes the set of all slices of arrays of its element type. The value of an uninitialized slice is nil.
SliceType = "[" "]" ElementType . Like arrays, slices are indexable and have a length. The length of a slice s can be discovered by the built-in function len; unlike with arrays it may change during execution. The elements can be addressed by integer indices 0 through len(s)-1. The slice index of a given element may be less than the index of the same element in the underlying array.
A slice, once initialized, is always associated with an underlying array that holds its elements. A slice therefore shares storage with its array and with other slices of the same array; by contrast, distinct arrays always represent distinct storage.
The array underlying a slice may extend past the end of the slice. The capacity is a measure of that extent: it is the sum of the length of the slice and the length of the array beyond the slice; a slice of length up to that capacity can be created by slicing a new one from the original slice. The capacity of a slice a can be discovered using the built-in function cap(a).
A new, initialized slice value for a given element type T is made using the built-in function make, which takes a slice type and parameters specifying the length and optionally the capacity:
make([]T, length) make([]T, length, capacity) A call to make allocates a new, hidden array to which the returned slice value refers. That is, executing
make([]T, length, capacity) produces the same slice as allocating an array and slicing it, so these two examples result in the same slice:
make([]int, 50, 100) new([100]int)[0:50] Like arrays, slices are always one-dimensional but may be composed to construct higher-dimensional objects. With arrays of arrays, the inner arrays are, by construction, always the same length; however with slices of slices (or arrays of slices), the lengths may vary dynamically. Moreover, the inner slices must be allocated individually (with make).
根据spec说明,如果我们这样写:
foo := [5]string{"test1","test2"}
foo就是array;如果我们这样写:
foo := []string{"test1","test2"}
foo就是个slice。他们之间的区别就是方括号之间有没有那个length.
package main
import (
"fmt"
"reflect"
)
func main() {
foo := [2]string{"apple", "orange"}
foo2 := []string{"apple", "orange"}
fmt.Printf("Kind of foo : %s\n",reflect.ValueOf(foo).Kind().String())
fmt.Printf("Kind of foo2 : %s\n",reflect.ValueOf(foo2).Kind().String())
}
Output:
Kind of foo : array
Kind of foo2 : slice
- array是值类型,意味着如果把数组传给函数,实际上是传递了一个数组的copy。slice是引用类型,保持着对底层array的引用,如果把一个slice赋给另一个,那么它们两个都引用同一个数组了。
- array是固定长度的。slice是可以扩展的,这使得它可以做为一个set用,:)。
#【函数 function】 在java里,如果函数没有返回值,要用void来修饰,而在Go里是不用的,感觉很自然。
func subroutine( i int) {
return
}
Go的函数是可以返回多个返回值的,并且返回值可以是有名称的。当被命名后,在函数开始时,他们会被初始化为0值。 When named, they are initialized to the zero values for their types when the function begins.
下面的这样函数第二个返回值被命名为:nextPos。可以命名好处是参数自描述了。
func nextInt(b []byte, pos int) (value, nextPos int) { /* ... */ }
因为命名了的返回值被初始化,并绑定到未加修饰的return上,所以它们很简洁。可以像内部变量一样在函数内部使用。 Because named results are initialized and tied to an unadorned return, they can simplify as well as clarify. Here’s a version of io.ReadFull that uses them well:
func ReadFull(r Reader, buf []byte) (n int, err error) {
for len(buf) > 0 && err == nil {
var nr int
nr, err = r.Read(buf)
n += nr
buf = buf[nr:len(buf)]
}
return
}
##【Deferred code 延迟执行的代码 】 假设你写了一个函数,打开了一个文件,执行写操作,并且在这个函数中有多个return,那么你要在每个return前写上关闭文件的代码。像下面的代码一样。
func ReadWrite() bool {
file.Open("file")
// Do your thing
if failureX {
file.Close()
return false
}
if failureY {
file.Close() //←
return false
}
file.Close() //←
return true
}
写了好几个file.Close() ,麻烦吧?为了解决这个问题,Go语言引入了defer这个语句。在defer后面接一个函数,这个函数将在函数返回前执行。
func ReadWrite() bool {
file.Open("file")
defer file.Close()
// Do your thing
if failureX {
return false
}
if failureY {
return false
}
return true
}
这样的代码清爽多了吧? 你可以在函数里写多个defer,它们以FILO的顺序执行。
defer后面的函数甚至可以修改函数的返回值。
func f() (ret int) { //← ret is initialized with zero
defer func() {
ret++ //← Increment ret with 1
}()
return 0 //← 1 not 0 will be returned!
}
##【 变长参数 Variadic parameters 】
func myfunc(arg ...int) {}
这个很好理解,要注意的是,如果你不指定变参的类型,那它默认的类型是empty inteface: interface{}
变长参数的函数可以接受slice,我们要把数组传进来,只需要把数组转成slice.
func myfunc1 ( args …int) {}
a := […]int {1,2,3}
myfunc1(a…) //这就是调用方式。
##【 以函数为值 】 在go里函数跟其它东西是一样的,可以做为值传给变量。
func main() {
a := func() { //← define a nemeless function and assign to a
println("Hello world!")
} //← 注意这里没有括号()
a() //← 调用这个函数
}
##【 Callback 回调 】 函数可以做为值,那么回调就简单了。
func callback(y int, f func(int)) { //← f will hold the function
f(y) ← Call the callback f with y
}
#【Panic and recovering】 Go里没有像java一样的exception的处理机制,你无法抛出一个异常。代替它的是panic-recover这种机制。需要注意的是,你要把这做为最后的手段。
Panic panic 是一个内置的函数,它停止正常的执行流程,开始panicing.当函数F调用了panic,那么F的执行就会被停止,任何deferred的函数都会被执行,接着F就返回到它的调用者。对F的调用者来说,F这时就相当于是panic函数了,请仔细理解这句话,也就是对于F的调用者来说,当F中调用了panic,那么调用F就相当于调用了panic.然后这个处理过程就沿着堆栈一直往上走,直到当前goroutine中的函数都返回,这时程序会崩溃。
Recover也是一个内置函数,它会重新获得一个已经panic的goroutine的控制权。有点像java中的try catch。 注意: recover只在deferred 函数中才有用。 在正常的流程中(非panic)调用recover是没用的,它会返回一个nil。
#chapter 4 Package
一个包是一系列的函数和数值。一个包里可以包含多个文件。按惯例包名一般是小写的。
@是否要像java一样,把一个包入在包里的目录里?
我们来定义一个包:
package even
func Even(i int) bool { ← Exported function
return i % 2 == 0
}
func odd(i int) bool { ← Private function
return i % 2 == 1
}
以大写字母开头的函数是exported的,相当于public的。小写函数也就是private的了。
build一个包
% mkdir $GOPATH/src/even ← Create top-level directory
% cp even.go $GOPATH/src/even ← Copy the package file
% go build ← Build it
% go install ← Install it to ../pkg
这样我们就可以在程序里使用even这个包了。
package main
import (
"even"
"fmt"
)
func main() {
i := 5
fmt.Printf("Is %d even?",i,even.Even(i))
}
包名是默认的访问符,你可以在import时指定一个新的访问符。
import bar "bytes"
bar.Buffer
另外一个convention是,包名是它的源代码目录名。如 src/pkg/compress/gzip,你可这样来导入它
import "compress/gzip"
这时,它的访问符是gzip,不是compress_gzip,也不是compressGzip.
Finally, the convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multi-word names.
最后,Go一般使用混合大小写,而不是下划线的方式来表示一个多词的名字。
- 给包添加文档 每个包应该有一个包文档,一个注释块,放在package这个语句的前面。如果包里有多个文件,只要在一个文件加上这个注释就可以,任何一个包里的文件都可以。 /* The regexp package implements a simple library for regular expressions. The syntax of the regular expressions accepted is: regexp: concatenation '|' concatenation */ package regexp
每个包里的函数前面的注释被当作它的文档。 // Printf formats according to a format specifier and writes to standard // output. It returns the number of bytes written and any write error // encountered. func Printf(format string, a ...interface) (n int, err error)
- 为包写单元测试 TODO......
Chapter 5 Beyond the basics 基础之外 -指针 go 是有指针的,但是它没有C语言的指针那么复杂的用法。在Go里,当你在调用函数时,参数是传值的方式传递的。如果为了效率,或者,你有可能在函数内修改参数的值,我们有指针。Go里的指针定义和使用起来跟C差不多。 一个新创建的指针或是未指向任何东西的指针被赋以nil。
-内存分配
go有垃圾收集器,你不用担心内存的回收。
go里有两个关键字用来分配内存,new 和 make。它们做不同的工作,应用到不同的类型,这可能会让你迷惑,不过其实规则挺简单的。
Allocation with new 用new来分配
内置的指令new非常必要的,new(T),为T分配了一个0大小的内存,并返回地址。用go的术语来说是:它返回了一个指向零值T的一个指针。这一点很重要,要记住。
这意味着你可以通过new创建一个用户的数据结构,并马上开始使用它。例如bytes.Buffer的文档是这样说的,“零值的Buffer是一个即时可用空的Buffer” ready to use.
The zero-value-is-useful property works transitively. 零值可用这个属性是可传递的。
type SyncedBuffer struct {
lock sync.Mutex
buffer bytes.Buffer
}
在分配完或声明完以后,SyncedBuffer的值即时可用。下面的例子里p和v,不做任何处理就可以直接用了。
In this snippet, both p and v will work correctly without further arrangement.
p := new(SyncedBuffer) ← Type *SyncedBuffer, ready to use
var v SyncedBuffer ← Type SyncedBuffer, idem
- 用make来分配
内置函数make(T,args)与new(T)服务目的不一样。它只能创建一个slice,maps和channel.它返回一个已初始化(非零值)的T,而不是*T。这样区分的主要原因是用户的数据结构必须初始化才能用。
例如,make([]int,10,100)分配了一个100 int的数组,并创建了一个长度为10,容量为100,指向这个数组前10个元素的slice。作为对比,new([]int) 返回一个指向新分配的、零值的slice结构,也就是一个指向nilslice值的指针。
- Constructors and composite literals 构造函数和组合语义
有时零值是不够的,需要一个初始化的构造函器。下面的例子是从os这个包里摘出来的。
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f := new(File)
f.fd = fd
f.name = name
f.dirinfo = nil
f.nepipe = 0
return f
}
有许多模板可用,我们可以用复合语法来简化上面的例子。复合语法在每次求值时生成一个新的实例。
func NewFile(fd int, name string) *File {
if fd < 0 {
return nil
}
f = File{fd,name,nil,0} ← Create a new File
return &f ← Return the address of f
}
返回函数内的变量的地址是OK的,变量存储的内容在函数返回后依然有效。
我们也可以合并上面的两行,
return &File{fd,name,nil,0}
复合语法的项(或称字段)必须按顺序排列,并且要全部出现,一个也不能少。如果我们使用 字段:值 这样的方式来赋值,则字段可以以任意顺序出现。那些未赋值字段就会赋以零值。所以我们可以这样:
return &File{fd:fd,name:name} ← 这个跟ruby好像啊。
如果复合语法没有任何的字段,那么所有的字段会被赋以零值。这时&File{}和new(File)是一样的。
- Defining your own types 定义你自己的类型
你可以通过关键字 type 定义自己的类型
type foo int
这个感觉跟C的typedef效果是一样的。
go里也有struct,用它来定义结构。
type Person struct {
name string
age int
}
func main() {
a := new(Person)
a.name = "Harley";a.age = 35
fmt.Printf("%v\n",a)
}
运行的结果:
&{Pete 42}
真棒!go知道如何打印你的结构。
与C的struct不同的是,go里的struct可以包含函数,有点像class了吧。因为在go里函数也是值嘛。
struct {
x, y int
A *[]int
F func()
}
如果你省略了字段的名称,你就创建了一个匿名字段。
struct {
T1 ← Field name is T1
*T2 ← Field name is T2
P.T3 ← Field name is T3
x, y int ← Field names are x and y
}
首字母大写的字段是exported的,也就是其它的包能读写它,结构里的函数也是如此。
- method
如果你创建一个使用刚建的struct的函数,你有两种方式:
1. 创建一个用struct做为参数的函数。
func doSomething(in1 *Person, in2 int) { /* ... */ }
2.把struct做为函数的receiver。在第三章中说过,带receiver的函数被称为“方法”
func (in1 *NameAge) doSomething(in2 int) { /* ... */ }
var n *NameAge
n.doSomething(2)
到底使用哪种方式,完全取决于你,但是如果你要满足接口interface,你就必须使用method.
同时,要牢记下面的规则:
如果x是可取址的, 同时&x的方法中包含m,那么x.m(),是(&x).m()的缩写。
这可能很巧妙,也是下面的类型声明的主要区别
// A Mutex is a data type with two methods, Lock and Unlock. type Mutex struct { /* Mutex fields */ }
func (m *Mutex) Lock() { /* Lock implementation */ }
func (m *Mutex) Unlock() { /* Unlock implementation */ }
现在我们用两种不同的方式来创建两个类型。
type NewMutex Mutex
type PrintableMutex struct {Mutex}
NewMutex 等同于 Mutex,但是它不包含Mutex的任何方法。也就说它的方法集是空的。
但是 PrintableMutex 继承了Mutex的方法集。也就是说:
PrintableMutex的方法集包含了方法 Lock 和 Unlock,这两个方法绑定到PrintableMutex的匿名字段Mutex上。
chapter 6 Interface
在Go里,interface被重载成表示几个不同的东西。每种类型都有一个interface,即它定义的方法集。请看下面的例子。
Listing 6.1. Defining a struct and methods on it
type S struct { i int }
func (p *S) Get() int { return p.i }
func (p *S) Put(v int) { p.i = v }
你也可以定义一个接口,它就简单的方法集。
type I interface {
Get() int
Put(int)
}
类型S是接口I的一个合法实现,因为它定义了接口I所需的两个方法。尽管它没有显式地实现这个接口。
译注:在java里,要实现一个接口必须使用implements关键字。而在go里,你的类型只要满足接口所需的方法集,go就认为你实现了接口。真不错。:)
func f(p I) {
fmt.Println(p.Get())
p.Put(1)
}
上面的函数使用了接口I
因为S实现接口I,所以我们可以调用f,把S的指针传过去。
var s S; f(&s) 译注:这里是指针哟,是不是当type做为参数时,调用时都要传指针呢?
我们要传指针,而不是S的值,因为我们定义的方法是用S的指针做为receiver的,func (p *S) Put(v int) { p.i = v }
当然我们也可以用S的值做为receiver,把方法改为像下面的一样:
func (p S) Put(v int) { p.i = v }
但是因为p是传值的,所以p.i = v 这行代码就没有效果了。赋值不会成功的。所以一般方法声明的receiver都是指针了。如果你不希望传过来的S的值被修改,那你就以传值的方式定义一个方法就好了。
Which is what? 这是什么?
让我们定义另外一个类型,它也实现接口I:
type R struct { i int }
func (p *R) Get() int { return p.i }
func (p *R) Put(v int) { p.i = v }
现在函数f就可以接受R和S类型的参数了。假如你想知道传过来的参数的具体类型,你可以使用type switch.
func f(p I) {
switch t := p.(type) {
case *S : // 1
case *R : // 2
case S: // 3
case R: // 4
default: // 5
}
}
在switch外使用(type)是非法的。type switch不是运行时检测类型的唯一方法。你可以使用“comma,ok”这样的方式来检测一个类型是否实现了一个指定的接口。
if t,ok = something.(I); ok {
// something implements the interface I
// t is the type it has
}
- 空接口
因为所有的类型都实现了空接口:interface{}.我们可以创建一个以空接口为参数的通用方法:
func g(something interface{}) int {
return something.(I).Get()
}
这是方法的返回值有点小技巧。something的类型是空接口interface{},也就是它不保证有任何的方法:它可以包含任何类型。
.(I)是类型的断言,把something转成I类型。
s = new(S)
fmt.Println(g(s));
这个调用工作得很好,会打印出0.如果我们用一个没有实现I接口的值来调用g,就会出问题。
i := 5 ←Make i a ``lousy'' int
fmt.Println(g(i))
能编译,但是运行时会报错:
panic: interface conversion: int is not main.I: missing method Get
- Methods 方法
方法就是有receiver的函数。你可以为任何类型定义方法,除了非本地的类型(non-local type)包括内置类型:int不能有任何方法。但你可以建一个有自己方法的新integer类型。例如:
type Foo int
func (self Foo) Emit() {
fmt.Printf("%v\n",self)
}
type Emitter interface {
Emit()
}
- Methods on interface types 接口里的方法
接口定义了一系列方法。方法包含真正的代码。换句话说,接口是定义原型,方法实现。所以一个method的receiver不能是接口。如果这样做了,会导致invalid receiver type的编译错误。
- Pointers to interfaces 指向接口的指针
在go里创建一个指向接口的指针是没用的,实际上创建一个接口的指针是非法的。
- Introspection and reflection 自省和反射
下面的例子我们想要查找Person类里的“tag”(这里叫"namestr")。我们需要使用“反射”包来实现(没有其它的途径)。
type Person struct {
name string "namestr"
age int
}
p1 := make(Person)
ShowTag(p1)
func ShowTag(i interface{}) {
switch t := reflect.TypeOf(i); t.Kind() { ← Get type, switch on Kind()
case reflect.Ptr: ← Its a pointer, hence a reflect.Ptr
tag := t.Elem().Field(0).Tag
下面的文字摘自godoc reflect
// Elem returns a type’s element type.
// It panics if the type’s Kind is not Array, Chan, Map, Ptr, or Slice.
Elem() Type
所以在上面的代码里我们用Elem来获得指针所指向的值。然后用Field(0)来获得zeroth的字段。它返回一个StructField,它有Tag属性,它返回字符串tag-name
【Chapter 7 Concurrency 并发】
【Chapter 8 Communication 通信】