“Go是一个开源的编程语言,它很容易用于构建简单、可靠和高效的软件。”(摘自Go语言官 方网站:http://golang.org )
Go语言不须要在语句或者声明的末尾添加分号,除非一行上有多条语句。实际上,编译器会主动把特定符号后的换行符转换为分号, 所以换行符添加的位置会影响Go代码的正确解析html
c类语言中i++为表达式,而go中,i++是语句而非表达式,且++i非法c++
*p++ //增长p指针指向的变量的值,而不改变p自身,与c/c++不一样
不一样类型间不能进行直接赋值操做程序员
数组长度是数组类型的一部分,[3]int
和[4]int
属于不一样类型golang
p := [3]int{1,2,3} p = [4]int{1,2,3,4} //error
函数的全部形参包括数组在内都是采用副本的形式传入express
func test(a [4]int)[4]int{ for i,j := range a{ a[i] = j+1 } return a } func main() { var a = [4]int{1,2,3,4} fmt.Println(a) //1,2,3,4 var b = test(a) fmt.Println(a) //1,2,3,4 fmt.Println(b) //2,3,4,5 }
切片声明时不须要指定大小,系统自动生成编程
s := []int{1,2,3,4} //与数组不一样,不须要指定大小
值为nil
的slice没有底层数组,与nil相等的slice长度为0,当长度为0的slice不必定是nil数组
var s []int // len(s) == 0, s == nil s = nil // len(s) == 0, s == nil s = []int(nil) // len(s) == 0, s == nil s = []int{} // len(s) == 0, s != nil
内置的make函数建立一个指定元素类型、长度和容量的slice。容量部分能够省略,在这种状况下,容量将等于长度。缓存
make([]T, len) make([]T, len, cap) // same as make([]T, cap)[:len]
内置的append函数用于向slice追加元素:服务器
var runes []rune for _, r := range "Hello, 世界" { runes = append(runes, r) } fmt.Printf("%q\n", runes) // "['H' 'e' 'l' 'l' 'o' ',' ' ' '世' '界']"
一个结构体可能同时含有导出成员和未导出成员多线程
点操做符也能够和指向结构体的指针一块儿工做:
var employeeOfTheMonth *Employee = &dilbert employeeOfTheMonth.Position += " (proactive team player)"
至关于下面语句
(*employeeOfTheMonth).Position += " (proactive team player)"
若结构的全部成员都是可比较的,则结构体是可比较的当结构体全部成员都相等时,结构体变量相等
结构体能够嵌入到另外一个结构体中
匿名成员的数据类型必须是命名的类型或指向一个命名的类型的指针
对于匿名嵌入,能够直接访问叶子属性而不须要给出完整的路径
type Point struct { X, Y int } type Circle struct { Point Radius int } type Wheel struct { Circle Spokes int } var w Wheel w.X = 8 // equivalent to w.Circle.Point.X = 8 w.Y = 8 // equivalent to w.Circle.Point.Y = 8 w.Radius = 5 // equivalent to w.Circle.Radius = 5 w.Spokes = 20
如下4中声明所表明的含义相同
func add(x int, y int) int {return x + y} func sub(x, y int) (z int) { z = x - y; return} func first(x int, _ int) int { return x } func zero(int, int) int { return 0 } fmt.Printf("%T\n", add) // "func(int, int) int" fmt.Printf("%T\n", sub) // "func(int, int) int" fmt.Printf("%T\n", first) // "func(int, int) int" fmt.Printf("%T\n", zero) // "func(int, int) int"
go支持多个返回值
若是一个函数将全部的返回值都显示的变量名,那么该函数的return语句能够省略操做数。这
称之为bare return。
// CountWordsAndImages does an HTTP GET request for the HTML // document url and returns the number of words and images in it. func CountWordsAndImages(url string) (words, images int, err error) { resp, err := http.Get(url) if err != nil { return } doc, err := html.Parse(resp.Body) resp.Body.Close() if err != nil { err = fmt.Errorf("parsing HTML: %s", err) return } words, images = countWordsAndImages(doc) return } func countWordsAndImages(n *html.Node) (words, images int) { /* ... */ }
在GO中,函数被看做第一类值:函数拥有类型,能够赋值给其余变量,传递给函数,从函数返回
可是函数值之间是不可比较的,也不能用函数值做为map的key。
拥有函数名的函数只能在包级语法块中被声明,经过函数字面量(function literal),咱们可绕过这一限制,在任何表达式中表示一个函数值。函数字面量的语法和函数声明类似,区别在于func关键字后没有函数名。函数值字面量是一种表达式,它的值被称为匿名函数(anonymous function),也称闭包。
,经过这种方式定义的函数能够访问完整的词法环境(lexical environment),
这意味着在函数中定义的内部函数能够引用该函数的变量,以下例所示:
// squares返回一个匿名函数。 // 该匿名函数每次被调用时都会返回下一个数的平方。 func squares() func() int { var x int return func() int { x++ return x * x } } func main() { f := squares() fmt.Println(f()) // "1" fmt.Println(f()) // "4" fmt.Println(f()) // "9" fmt.Println(f()) // "16" }
函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体。
func name(parameter-list)(result-list){ body }
拥有函数名的函数只能在包级语法块中被声明,经过函数字面量(function literal),咱们可绕过这一限制,在任何表达式中表示一个函数值。函数字面量的语法和函数声明类似,区别 在于func关键字后没有函数名。函数值字面量是一种表达式,它的值被称为匿名函数(anonymous function)。
经过这种方式定义的函数能够访问完整的词法环境(lexical environment), 这意味着在函数中定义的内部函数能够引用该函数的变量,以下例所示:
// squares返回一个匿名函数。 // 该匿名函数每次被调用时都会返回下一个数的平方。 func squares() func() int { var x int return func() int { x++ return x * x } } func main() { f := squares() fmt.Println(f()) // "1" fmt.Println(f()) // "4" fmt.Println(f()) // "9" fmt.Println(f()) // "16" }
函数squares返回另外一个类型为 func() int 的函数。对squares的一次调用会生成一个局部变量x并返回一个匿名函数。每次调用时匿名函数时,该函数都会先使x的值加1,再返回x的平方。第二次调用squares时,会生成第二个x变量,并返回一个新的匿名函数。新匿名函数操做的是第二个x变量。
注意捕获迭代变量
var rmdirs []func() for _, d := range tempDirs() { dir := d // NOTE: necessary! os.MkdirAll(dir, 0755) // creates parent directories too rmdirs = append(rmdirs, func() { os.RemoveAll(dir) }) } // ...do some work… for _, rmdir := range rmdirs { rmdir() // clean up }
在上面的程序中,for循环语句引入了新的词法块,循环 变量dir在这个词法块中被声明。在该循环中生成的全部函数值都共享相同的循环变量。须要注意,函数值中记录的是循环变量的内存地址,而不是循环变量某一时刻的值。以dir为例, 后续的迭代会不断更新dir的值,当删除操做执行时,for循环已完成,dir中存储的值等于最后一次迭代的值。这意味着,每次对os.RemoveAll的调用删除的都是相同的目录。这不是go或defer自己致使的,而是由于它们都会等待循环结束后,再执行函数值。
一般,为了解决这个问题,咱们会引入一个与循环变量同名的局部变量,做为循环变量的副 本。好比下面的变量dir,虽然这看起来很奇怪,但却颇有用。
for _, dir := range tempDirs() { dir := dir // declares inner dir, initialized to outer dir // ... }
参数数量可变的函数称为为可变参数函数。典型的例子就是fmt.Printf和相似函数。Printf首先接收一个的必备参数,以后接收任意个数的后续参数。在声明可变参数函数时,须要在参数列表的最后一个参数类型以前加上省略符号“...”,这表示
该函数会接收任意数量的该类型参数。
func sum(vals...int) int { total := 0 for _, val := range vals { total += val } return total }
sum函数返回任意个int型参数的和。在函数体中,vals被看做是类型为[] int的切片。sum能够接收任意数量的int型参数:
fmt.Println(sum()) // "0" fmt.Println(sum(3)) // "3" fmt.Println(sum(1, 2, 3, 4)) // "10"
在上面的代码中,调用者隐式的建立一个数组,并将原始参数复制到数组中,再把数组的一个切片做为参数传给被调函数。若是原始参数已是切片类型,咱们该如何传递给sum?只需在最后一个参数后加上省略符。下面的代码功能与上个例子中最后一条语句相同。
values := []int{1, 2, 3, 4} fmt.Println(sum(values...)) // "10"
虽然在可变参数函数内部,...int 型参数的行为看起来很像切片类型,但实际上,可变参数函数和以切片做为参数的函数是不一样的。
func f(...int) {} func g([]int) {} fmt.Printf("%T\n", f) // "func(...int)" fmt.Printf("%T\n", g) // "func([]int)"
当defer语句被执行时,跟在defer后面的函数会被延迟执行。直到包含该defer语句的函数执行完毕时,defer后的函数才会被执行,不论包含defer语句的函数是经过return正常结束,仍是因为panic致使的异常结束。你能够在一个函数中执行多条defer语句,它们的执行顺序与声明顺序相反。
defer语句常常被用于处理成对的操做,如打开、关闭、链接、断开链接、加锁、释放锁。经过defer机制,不论函数逻辑多复杂,都能保证在任何执行路径下,资源被释放。释放资源的defer应该直接跟在请求资源的语句后。
Go的类型系统会在编译时捕获不少错误,但有些错误只能在运行时检查,如数组访问越界、空指针引用等。这些运行时错误会引发painc异常。
通常而言,当panic异常发生时,程序会中断运行,并当即执行在该goroutine中被延迟的函数(defer 机制)。随后,程序崩溃并输出日志信息。日志信息包括panic value和函数调用的堆栈跟踪信息。panic value一般是某种错误信息。对于每一个goroutine,日志信息中都会有与之相对的,发生panic时的函数调用堆栈跟踪信息。一般,咱们不须要再次运行程序去定位问题,日志信息已经提供了足够的诊断依据。所以,在咱们填写问题报告时,通常会将panic异常和日志信息一并记录。
虽然Go的panic机制相似于其余语言的异常,但panic的适用场景有一些不一样。因为panic会引发程序的崩溃,所以panic通常用于严重错误,如程序内部的逻辑不一致。
若是在deferred函数中调用了内置函数recover,而且定义该defer语句的函数发生了panic异常,recover会使程序从panic中恢复,并返回panic value。致使panic异常的函数不会继续运行,但能正常返回。在未发生panic时调用recoverrecover会返回nil。
func Parse(input string) (s *Syntax, err error) { defer func() { if p := recover(); p != nil { err = fmt.Errorf("internal error: %v" , p) } }() // ...parser... }
deferred函数帮助Parse从panic中恢复。在deferred函数内部,panic value被附加到错误信息中;并用err变量接收错误信息,返回给调用者。咱们也能够经过调用runtime.Stack往错误信息中添加完整的堆栈调用信息。
GO不支持类,但支持方法,能够为结构体或其余类型定义方法,方法就是一类带特殊的 接收者 参数的函数。方法接收者在它本身的参数列表内,位于 func
关键字和方法名之间。
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()) //v.Abs() 调用方法 }
其形式相似于将函数声明中的形参放到函数名以前
只能为同一个包的类型接收者声明方法,不能为其余包内定义的类型声明方法
能够为其余类型定义方法,但不能为内置类型(如:int
)定义方法
type MyFloat float64 func (f MyFloat) Abs() float64 { //ture if f < 0 { return float64(-f) } return float64(f) } //cannot define new methods on non-local type float64 //func (f float64) Abs() float64 { // if f < 0 { // return float64(-f) // } // return float64(f) //} func main() { f := MyFloat(-math.Sqrt2) fmt.Println(f.Abs()) }
使用指针接收者能够改变接收者自身的值
type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func (v *Vertex) Scale(f float64) { //指针接收者 v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} v.Scale(10) //v = {30,40} fmt.Println(v.Abs()) }
调用函数时,指针类型的形参必须接受一个指针;调用方法时接收者为变量时能够是指针也能够为值,编译器会自动解引用或取地址。
var v Vertex ScaleFunc(v, 5) // 编译错误! ScaleFunc(&v, 5) // OK var v Vertex v.Scale(5) // OK p := &v p.Scale(10) // OK
在现实的程序里,通常会约定若是Point这个类有一个指针做为接收器的方法,那么全部Point 的方法都必须有一个指针接收器,即便是那些并不须要这个指针接收器的函数。
只有类型(Point)和指向他们的指针(*Point),才是可能会出如今接收器声明里的两种接收器。 此外,为了不歧义,在声明方法时,若是一个类型名自己是一个指针的话,是不容许其出如今接收器中的,好比下面这个例子:
type P *int func (P) f() { /* ... */ } // compile error: invalid receiver type
使用嵌入结构体时,被嵌入结构体能够直接调用嵌入结构体的方法,
import "image/color" type Point struct{ X, Y float64 } type ColoredPoint struct { Point Color color.RGBA } red := color.RGBA{255, 0, 0, 255} blue := color.RGBA{0, 0, 255, 255} var p = ColoredPoint{Point{1, 1}, red} var q = ColoredPoint{Point{5, 4}, blue} fmt.Println(p.Distance(q.Point)) // "5" Distance是point类型的方法,p的类型为ColoredPoint,但能够直接调用Distance p.ScaleBy(2) q.ScaleBy(2) fmt.Println(p.Distance(q.Point)) // "10" 但参数类型为Point时,必须显示调用point字段
能够将特定变量的方法调用赋值给变量,经过变量调用方法,其形式相似于函数变量的赋值:
p := Point{1, 2} q := Point{4, 6} distanceFromP := p.Distance // method value,选择器返回一个方法值 fmt.Println(distanceFromP(q)) // "5" scaleP := p.ScaleBy // method value,选择器返回一个方法值 scaleP(2) // p becomes (2, 4)
p.distance
,p.ScaleBy称为
选择器,选择器返回一个方法值。
在一个包的API须要一个函数值、且调用方但愿操做的是某一个绑定了对象的方法的话,方
法"值"会很是实用。举例来讲,下面例子中的time.AfterFunc
这个函数的功能是在指定的延迟时间以后来执行一个(译注:另外的)函数。且这个函数操做的是一个Rocket对象r
type Rocket struct { /* ... */ } func (r Rocket) Launch() { / ... */ } r := new(Rocket) time.AfterFunc(10 * time.Second, func() { r.Launch() }) //这里至关于将r.Launch封装为一个函数传入
直接用方法"值"传入AfterFunc
的话能够更为简短:
time.AfterFunc(10 * time.Second, r.Launch)
和方法"值"相关的还有方法表达式。当调用一个方法时,与调用一个普通的函数相比,咱们必需要用选择器(p.Distance)语法来指定方法的接收器。当T是一个类型时,方法表达式可能会写做T.f或者(*T).f,会返回一个函数"值",这种函数会将
其第一个参数用做接收器,因此能够用一般(译注:不写选择器)的方式来对其进行调用:
p := Point{1, 2} q := Point{4, 6} //这里Point是类型名,其拥有一个方法func (p Point) Distance(), distance := Point.Distance // method expression fmt.Println(distance(p, q)) // "5" fmt.Printf("%T\n", distance) // "func(Point, Point) float64" scale := (*Point).ScaleBy scale(&p, 2) fmt.Println(p) // "{2 4}" fmt.Printf("%T\n" , scale) // "func(*Point, float64)"
以上的内容至关于将类型的一个方法转化为一个函数,该函数相较于方法多了第一个参数,该参数代表接收器,如上述将方法func (p Point) Distance()float64
转化为func(Point, Point) float64
在Go语言中还存在着另一种类型:接口类型。接口类型是一种抽象的类型。它不会暴露出它所表明的对象的内部值的结构和这个对象支持的基础操做的集合;它们只会展现出它们本身的方法。也就是说当你有看到一个接口类型的值时,你不知道它是什么,惟一知道的就是能够经过它的方法来作什么。
nil 接口值既不保存值也不保存具体类型。
也就是说,若是一个类型声明了某个接口给出的全部方法,则认为该类型继承了该接口,而无需显式说明。
Stringer
是一个能够用字符串描述本身的类型。fmt
包(还有不少包)都经过此接口来打印值
type Person struct { Name string Age int } func (p Person) String() string { return fmt.Sprintf("%v (%v years)", p.Name, p.Age) } func main() { a := Person{"Arthur Dent", 42} z := Person{"Zaphod Beeblebrox", 9001} fmt.Println(a, z) //Arthur Dent (42 years) Zaphod Beeblebrox (9001 years) }
Go 程序使用 error
值来表示错误状态。与 fmt.Stringer
相似,error
类型是一个内建接口:
type error interface { Error() string }
(与 fmt.Stringer
相似,fmt
包在打印值时也会知足 error
。)
package http type Handler interface { ServeHTTP(w ResponseWriter, r *Request) } func ListenAndServe(address string, h Handler) error
ListenAndServe函数须要一个例如“localhost:8000”的服务器地址,和一个全部请求均可以分派的Handler接口实例。它会一直运行,直到这个服务由于一个错误而失败(或者启动失败),它的返回值必定是一个非空的错误。
类型断言 提供了访问接口值底层具体值的方式。
t := i.(T)
该语句断言接口值 i
保存了具体类型 T
,并将其底层类型为 T
的值赋予变量 t
。若 i
并未保存 T
类型的值,该语句就会触发一个panic。
这里有两种可能。第一种,若是断言的类型T是一个具体类型,而后类型断言检查x的动态类型是否和T相同。若是这个检查成功了,类型断言的结果是x的动态值,固然它的类型是T。换句话说,具体类型的类型断言从它的操做对象中得到具体的值。若是检查失败,接下来这个操做会抛出panic。例如:
var w io.Writer w = os.Stdout f := w.(*os.File) // success: f == os.Stdout c := w.(*bytes.Buffer) // panic: interface holds *os.File, not *bytes.Buffer
上述中,w为os.Stdout
,其类型为*os.File
,因此w.(*os.File)
断言成功,返回w的动态值,即os.Stdout
。由于w的类型与*bytes.Buffer
不符,因此c :=w.(*bytes.Buffer)
运行时返回panic
若是断言类型T是接口类型,则类型断言检查x的动态类型是否知足T.若是此检查成功,在下面代码中,w.(io.ReadWriter)
检查的是w的动态类型(即os.Stdout
的动态类型io.ReadWriter
),与w的接口io.Writer
无关。
var w io.Writer w = os.Stdout rw := w.(io.ReadWriter) // success: *os.File has both Read and Write w.Write([]byte("w write ")) // w.Read([]byte("w read")) //w.Read undefined (type io.Writer has no field or method Read) rw.Read([]byte("rw read")) rw.Write([]byte("rw write"))
在上面的第一个类型断言后,w和rw都持有os.Stdout
所以它们每一个有一个动态类型*os.File
,可是变量w是一个io.Write
, r类型只对外公开出文件的Write方法,然而rw变量同时公开它的Read和write方法。
若是断言操做的对象是一个nil接口值,那么不论被断言的类型是什么这个类型断言都会失败。
为了 判断 一个接口值是否保存了一个特定的类型,类型断言可返回两个值:其底层值以及一个报告断言是否成功的布尔值。
t, ok := i.(T)
func main() { var i interface{} = "hello" s := i.(string) //i保存的值的具体类型为string fmt.Println(s) s, ok := i.(string) fmt.Println(s, ok) f, ok := i.(float64) fmt.Println(f, ok) f = i.(float64) // 报错(panic) fmt.Println(f) }
类型选择 是一种按顺序从几个类型断言中选择分支的结构。
类型选择与通常的 switch 语句类似,不过类型选择中的 case 为类型(而非值), 它们针对给定接口值所存储的值的类型进行比较。
func do(i interface{}) { switch v := i.(type) { case int: fmt.Printf("Twice %v is %v\n", v, v*2) case string: fmt.Printf("%q is %v bytes long\n", v, len(v)) default: fmt.Printf("I don't know about type %T!\n", v) } } func main() { do(21) do("hello") do(true) }
Go 程(goroutine)是由 Go 运行时管理的轻量级线程。
go f(x, y, z)
会启动一个新的 Go 程并执行
信道是带有类型的管道,你能够经过它用信道操做符 <-
来发送或者接收值。
ch <- v // 将 v 发送至信道 ch。 v := <-ch // 从 ch 接收值并赋予 v。
(“箭头”就是数据流的方向。)
和映射与切片同样,信道在使用前必须建立:
ch := make(chan int) ch := make(chan int, 100) //带缓冲信道
默认状况下,发送和接收操做在另外一端准备好以前都会阻塞。这使得 Go 程能够在没有显式的锁或竞态变量的状况下进行同步。
发送者可经过 close(ch)
关闭一个信道来表示没有须要发送的值了。接收者能够经过为接收表达式分配第二个参数来测试信道是否被关闭:若没有值能够接收且信道已被关闭,那么在执行完
v, ok := <-ch
以后 ok
会被设置为 false
值。循环 for i := range c
会不断从信道接收值,直到它被关闭。
注意: 只有发送者才能关闭信道,而接收者不能。向一个已经关闭的信道发送数据会引起程序恐慌(panic)。
当一个被关闭的channel中已经发送的数据都被成功接收后,后续的接收操做将再也不阻塞,它们会当即返回一个零
还要注意: 信道与文件不一样,一般状况下无需关闭它们。只有在必须告诉接收者再也不有须要发送的值时才有必要关闭,例如终止一个 range
循环。
Go语言的类型系统提供了单方向的channel类型,分别用于只发送或只接收的channel
。类型 chan<- int
表示一个只发送int的channel,只能发送不能接收。相反,类型<-chan int
表示一个只接收int的channel,只能接收不能发送。(箭头 <- 和关键字chan的相对位置代表了channel的方向。)这种限制将在编译期检测。
带缓存的Channel内部持有一个元素队列。队列的最大容量是在调用make函数建立channel时经过第二个参数指定的。下面的语句建立了一个能够持有三个字符串元素的带缓存Channel。
ch = make(chan string, 3)
使用内置函数cap能够获取channel缓存大小, 函数len能够获取channel有效数据个数
fmt.Println(cap(ch)) //缓冲区大小 fmt.Println(len(ch)) //channel中的有效数据个数
make(chan int)
和make(chan int,1)
的区别select
语句使一个 Go 程能够等待多个通讯操做。
select
会阻塞到某个分支能够继续执行为止,这时就会执行该分支。当多个分支都准备好时会随机选择一个执行。
当 select
中的其它分支都没有准备好时,default
分支就会执行。为了在尝试发送或者接收时不发生阻塞,可以使用 default
分支:
select { case i := <-c: // 使用 i default: // 从 c 中接收会阻塞时执行 }
每个OS线程都有一个固定大小的内存块(通常会是2MB)来作栈,这个栈会用来存储当前正在被调用或挂起(指在调用其它函数时)的函数的内部变量。
相反,一个goroutine会以一个很小的栈开始其生命周期,通常只须要2KB。一个goroutine的栈,和操做系统线程同样,会保存其活跃或挂起的函数调用的本地变量,可是和OS线程不太同样的是一个goroutine的栈大小并非固定的;栈的大小会根据须要动态地伸缩。
OS线程由操做系统内核调用调用时会产生上下文切换,消耗资源较多
GO自身包含调度器,其调度消耗小于线程切换
在大多数支持多线程的操做系统和程序语言中,当前的线程都有一个独特的身份(id),而且这个身份信息能够以一个普通值的形式被被很容易地获取到,典型的能够是一个integer或者指针值。goroutine没有能够被程序员获取到的身份(id)的概念。