語(yǔ)言介紹
Go(又稱Golang)是Google開(kāi)發(fā)的一種靜態(tài)強(qiáng)類型、編譯型取逾、并發(fā)型,并具有垃圾回收功能的編程語(yǔ)言苹支。
羅伯特·格瑞史莫(Robert Griesemer)砾隅,羅勃·派克(Rob Pike)及肯·湯普遜(Ken Thompson)于2007年9月開(kāi)始設(shè)計(jì)Go,稍后Ian Lance Taylor债蜜、Russ Cox加入項(xiàng)目晴埂。Go是基于Inferno操作系統(tǒng)所開(kāi)發(fā)的。Go于2009年11月正式宣布推出寻定,成為開(kāi)放源代碼項(xiàng)目儒洛,并在Linux及Mac OS X平臺(tái)上進(jìn)行了實(shí)現(xiàn),后來(lái)追加了Windows系統(tǒng)下的實(shí)現(xiàn)狼速。在2016年琅锻,Go被軟件評(píng)價(jià)公司TIOBE 選為“TIOBE 2016 年最佳語(yǔ)言”
Robert Griesemer, Rob Pike 和 Ken Thompson。Robert在開(kāi)發(fā)Go之前是Google V8向胡、Chubby和HotSpot JVM的主要貢獻(xiàn)者恼蓬;Rob主要是Unix、UTF-8捷枯、plan9的作者滚秩;Ken主要是B語(yǔ)言、C語(yǔ)言的作者淮捆、Unix之父郁油。
為什么會(huì)設(shè)計(jì)go語(yǔ)言
設(shè)計(jì)Go語(yǔ)言是為了解決當(dāng)時(shí)Google開(kāi)發(fā)遇到的以下這些問(wèn)題:
大量的C++代碼本股,同時(shí)又引入了Java和Python
成千上萬(wàn)的工程師
數(shù)以萬(wàn)計(jì)行的代碼
分布式的編譯系統(tǒng)
數(shù)百萬(wàn)的服務(wù)器
其主要有以下幾個(gè)方面的痛點(diǎn):
編譯慢
失控的依賴
每個(gè)工程師只是用了一個(gè)語(yǔ)言里面的一部分
程序難以維護(hù)(可讀性差、文檔不清晰等)
更新的花費(fèi)越來(lái)越長(zhǎng)
交叉編譯困難
所以桐腌,他們當(dāng)時(shí)設(shè)計(jì)Go的目標(biāo)是為了消除各種緩慢和笨重拄显、改進(jìn)各種低效和擴(kuò)展性。Go是由那些開(kāi)發(fā)大型系統(tǒng)的人設(shè)計(jì)的案站,同時(shí)也是為了這些人服務(wù)的躬审;它是為了解決工程上的問(wèn)題,不是為了研究語(yǔ)言設(shè)計(jì)蟆盐;它還是為了讓我們的編程變得更舒適和方便
語(yǔ)法規(guī)則介紹
包管理
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10))
}
output:
My favorite number is 1
每個(gè) Go 程序都是由包組成的承边。
程序運(yùn)行的入口是包 main
。
這個(gè)程序使用并導(dǎo)入了包 "fmt" 和 "math/rand"
石挂。
按照慣例博助,包名與導(dǎo)入路徑的最后一個(gè)目錄一致。
例如痹愚,"math/rand"
包由 package rand 語(yǔ)句開(kāi)始富岳。
同一目錄下只能用同一個(gè)包名
- 導(dǎo)出名
在導(dǎo)入了一個(gè)包之后,就可以用其導(dǎo)出的名稱來(lái)調(diào)用它拯腮。
在 Go 中窖式,首字母大寫(xiě)的名稱是被導(dǎo)出的。
Foo 和 FOO 都是被導(dǎo)出的名稱动壤。名稱 foo 是不會(huì)被導(dǎo)出的萝喘。
foo-相當(dāng)于php的private , Foo 和 FOO 相當(dāng)于public
package exports
import "fmt"
func Foo(){
fmt.Println("it is Foo")
}
func FOO(){
fmt.Println("it is FOO")
foo()
}
func foo(){
fmt.Println("it is foo")
}
函數(shù)
package main
import "fmt"
func add(x int, y int) int {
return x + y
}
func add2(x, y int) int{
return x+y
}
func add3(x, y int) (z int){
z = x+y
return
}
func main() {
fmt.Println(add(42, 13))
fmt.Println(add2(42, 13))
fmt.Println(add3(42, 13))
}
output:
55
55
55
函數(shù)格式 func 函數(shù)名(無(wú)參/參數(shù)1, 參數(shù)2...)(返回結(jié)果|無(wú)){ 函數(shù)體}
執(zhí)行發(fā)現(xiàn)這三個(gè)函數(shù)返回結(jié)果相同狼电,只是格式不同
add-函數(shù)符合基本的函數(shù)格式
add2-對(duì)于同一類型參數(shù)蜒灰,可以在最后一個(gè)參數(shù)后面指明參數(shù)類型
add3-函數(shù)返回結(jié)果可以在返回結(jié)構(gòu)中聲明
- 多值返回
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world")
fmt.Println(a, b)
}
output:
world hello
函數(shù)可以返回任意數(shù)量的返回值。
swap 函數(shù)返回了兩個(gè)字符串肩碟。
變量
package main
import "fmt"
var (
a bool
b string
c int //有符號(hào)-等于cpu位數(shù)-- 如果是64位(-9223372036854775808 到 9223372036854775807)-`uname -m`
c1 int8 //有符號(hào)-占用8bit(-128 到 127),int16,int32,int64類推
d uint //無(wú)符號(hào)-等于cpu位數(shù)- 如果是64位(0 到 18446744073709551615)
d1 uint8 //無(wú)符號(hào)-占用8bit(0 到 255), uint16,uint32,uint64類推
e rune // int32 別名
f byte // uint8 別名
g float32 //1.401298464324817070923729583289916131280e-45 -- 3.402823466385288598117041834516925440e+38 (23位小數(shù)f强窖,8位偏置指數(shù)e,1位符號(hào)s)
h float64 //4.940656458412465441765687928682213723651e-324 -- 1.797693134862315708145274237317043567981e+308(52位小數(shù)f削祈,11位偏置指數(shù)e翅溺,1位符號(hào)s)
j complex64 //32 位實(shí)數(shù)和虛數(shù)
k complex128 //64 位實(shí)數(shù)和虛數(shù)
)
var aa, bb, ee bool
var ff, gg int = 5, 6
var hh, ii = 5, true
func main() {
var aaa, bbb bool = true, false
ccc, ddd := true, 18
fmt.Println(a, b, c, c1, d, d1, e, f, g, h, j, k)
fmt.Println(aa, bb, ee)
fmt.Println(ff, gg)
fmt.Println(hh, ii)
fmt.Println(aaa, bbb)
fmt.Println(ccc, ddd)
}
output:
false 0 0 0 0 0 0 0 0 (0+0i) (0+0i)
false false false
5 6
5 true
true false
true 18
go 基本的數(shù)據(jù)類型有bool類型,字符串類型髓抑,數(shù)字類型(復(fù)數(shù)類型)
默認(rèn)值分別為 false, "", 0(0+0i)
變量定義可以包含初始值咙崎,每個(gè)變量對(duì)應(yīng)一個(gè)。
如果省略類型吨拍;變量從初始值中獲得類型
在函數(shù)中褪猛,:=
簡(jiǎn)潔賦值語(yǔ)句在明確類型的地方,可以用于替代 var 定義羹饰。
函數(shù)外的每個(gè)語(yǔ)句都必須以關(guān)鍵字開(kāi)始(var
伊滋、func
碳却、等等),:=
結(jié)構(gòu)不能使用在函數(shù)外
- 數(shù)字類型轉(zhuǎn)換
package main
import (
"fmt"
"math"
"strconv"
)
//StringToInt 字符串轉(zhuǎn)整形
func StringToInt(valstr string) int {
val, err := strconv.Atoi(valstr)
if err != nil {
val = 0
}
return val
}
//IntToString 整形轉(zhuǎn)字符串
func IntToString(intval int) string {
return strconv.Itoa(intval)
}
func main() {
var x, y int = 3, 4
var f float64 = math.Sqrt(float64(x*x + y*y))
var z int = int(f)
fmt.Println(x, y, z)
a := IntToString(x) + "string"
b := "168"
c := StringToInt(b)
fmt.Println(a, b+"s", c)
}
output:
3 4 5
3string 168s 168
表達(dá)式 T(v) 將值 v 轉(zhuǎn)換為類型 T
數(shù)字類型一般可以直接顯式轉(zhuǎn)化笑旺,字符串和數(shù)字類型可以借助strconv包處理
- 類型推導(dǎo)
package main
import "fmt"
func main() {
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
var j bool
k := j
fmt.Printf("The types is %T, %T, %T, %T\n", i, f, g, k)
}
output:
The types is int, float64, complex128, bool
在定義一個(gè)變量但不指定其類型時(shí)(使用沒(méi)有類型的 var 或 := 語(yǔ)句)昼浦, 變量的類型由右值推導(dǎo)得出。當(dāng)右值定義了類型時(shí)筒主,新變量的類型與其相同
常量
package main
import "fmt"
const Pi = 3.14
const (
Big = 1 << 100
Small = Big >> 99
)
func needInt(x int) int { return x*10 + 1 }
func needFloat(x float64) float64 {
return x * 0.1
}
func main() {
const World = "世界"
fmt.Println("Hello", World)
fmt.Println("Happy", Pi, "Day")
const Truth = true
fmt.Println("Go rules?", Truth)
fmt.Println(needInt(Small))
fmt.Println(needFloat(Small))
fmt.Println(needFloat(Big))
}
output:
Hello 世界
Happy 3.14 Day
Go rules? true
21
0.2
1.2676506002282295e+29
常量的定義與變量類似关噪,只不過(guò)使用 const 關(guān)鍵字。
常量可以是字符乌妙、字符串使兔、布爾或數(shù)字類型的值。
常量不能使用 := 語(yǔ)法定義冠胯。
一個(gè)未指定類型的數(shù)值常量可以作為不同數(shù)字類型傳參
循環(huán)
package main
import "fmt"
func main() {
sum := 0
for i := 0; i < 10; i++ {
sum += i
}
fmt.Println(sum)
sum = 1
for sum < 1000 {
sum += sum
}
fmt.Println(sum)
}
output:
45
1024
Go 只有一種循環(huán)結(jié)構(gòu)——for
循環(huán)火诸。
基本的 for 循環(huán)除了沒(méi)有了 ( )
之外(甚至強(qiáng)制不能使用它們)锦针,看起來(lái)跟 C 或者 Java 中做的一樣荠察,而 { }
是必須的。
- 死循環(huán)
package main
import "fmt"
func main() {
for {
fmt.Println("hello world")
}
}
output:
hello world
hello world
....
不手動(dòng)停止的話奈搜,代碼會(huì)一直運(yùn)行下去
if 語(yǔ)句
package main
import (
"fmt"
"math"
)
func sqrt(x float64) string {
if x < 0 {
return sqrt(-x) + "i"
}
return fmt.Sprint(math.Sqrt(x))
}
func main() {
fmt.Println(sqrt(2), sqrt(-4))
}
output:
1.4142135623730951 2i
if 語(yǔ)句除了沒(méi)有了 ( )
之外(甚至強(qiáng)制不能使用它們)悉盆,看起來(lái)跟 C 或者 Java 中的一樣,而 { }
是必須的馋吗。
- if 的便捷語(yǔ)句
package main
import (
"fmt"
"math"
)
func pow(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
}
return lim
}
func pow2(x, n, lim float64) float64 {
if v := math.Pow(x, n); v < lim {
return v
} else {
fmt.Printf("%g >= %g\n", v, lim)
}
// 這里開(kāi)始就不能使用 v 了
return lim
}
func main() {
fmt.Println(
pow(3, 2, 10),
pow(3, 3, 20),
pow2(3, 2, 10),
pow2(3, 3, 20),
)
}
output:
27 >= 20
9 20 9 20
跟 for 一樣焕盟,if
語(yǔ)句可以在條件之前執(zhí)行一個(gè)簡(jiǎn)單的語(yǔ)句。
由這個(gè)語(yǔ)句定義的變量的作用域僅在 if 范圍之內(nèi)宏粤。
在 if 的便捷語(yǔ)句定義的變量同樣可以在任何對(duì)應(yīng)的 else 塊中使用脚翘。
switch 語(yǔ)句
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Print("Go runs on ")
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)
}
}
output:
Go runs on OS X
switch 結(jié)構(gòu)同其他語(yǔ)言一樣
switch 的條件從上到下的執(zhí)行,當(dāng)匹配成功的時(shí)候停止绍哎。
每條case除非以 fallthrough 語(yǔ)句結(jié)束来农,否則分支會(huì)自動(dòng)終止。
- 沒(méi)有條件的 switch
package main
import (
"fmt"
"time"
)
func main() {
t := time.Now()
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
}
output:
Good evening.
沒(méi)有條件的 switch 同 switch true
一樣崇堰。
這一構(gòu)造使得可以用更清晰的形式來(lái)編寫(xiě)長(zhǎng)的 if-then-else 鏈沃于。
defer 語(yǔ)句
package main
import "fmt"
func echo(str string) string {
defer fmt.Println("echo end")
return "hello " + str
}
func main() {
defer fmt.Println("world")
fmt.Println("hello")
echo("china")
}
output:
hello
echo end
world
defer 語(yǔ)句會(huì)延遲函數(shù)的執(zhí)行直到上層函數(shù)返回。
延遲調(diào)用的參數(shù)會(huì)立刻生成海诲,但是在上層函數(shù)返回前函數(shù)都不會(huì)被調(diào)用
- defer 棧
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 3; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
output:
counting
done
2
1
0
延遲的函數(shù)調(diào)用被壓入一個(gè)棧中繁莹。當(dāng)函數(shù)返回時(shí), 會(huì)按照后進(jìn)先出的順序調(diào)用被延遲的函數(shù)調(diào)用特幔。
派生類型
指針
package main
import "fmt"
func main() {
i, j := 42, 2701
p := &i // point to i
fmt.Println(*p) // read i through the pointer
*p = 21 // set i through the pointer
fmt.Println(i) // see the new value of i
p = &j // point to j
*p = *p / 37 // divide j through the pointer
fmt.Println(j) // see the new value of j
}
output:
42
21
73
Go 具有指針咨演。 指針保存了變量的內(nèi)存地址。
類型 *T 是指向類型 T 的值的指針蚯斯。其零值是nil
薄风。
var p int
& 符號(hào)會(huì)生成一個(gè)指向其作用對(duì)象的指針零院。
i := 42
p = &i
* 符號(hào)表示指針指向的底層的值。
fmt.Println(p) // 通過(guò)指針 p 讀取 i
*p = 21 // 通過(guò)指針 p 設(shè)置 i
這也就是通常所說(shuō)的“間接引用”或“非直接引用”村刨。
結(jié)構(gòu)體
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
fmt.Println(v)
v.X = 4
fmt.Println(v)
p := &v
p.X = 1.5e9
fmt.Println(v, v.X)
fmt.Println(p, p.X)
fmt.Println("------------------------")
v1 := Vertex{1, 2} // 類型為 Vertex
v2 := Vertex{X: 1} // Y:0 被省略
v3 := Vertex{} // X:0 和 Y:0
p1 := &Vertex{1, 2} // 類型為 *Vertex
fmt.Println(v1, v2, v3, p1)
fmt.Printf("values is %+v, %+v, %+v, %+v \n", v1, v2, v3, p1)
fmt.Printf("types is %T, %T, %T, %T \n", v1, v2, v3, p1)
fmt.Printf("values is %#v, %#v, %#v, %#v \n", v1, v2, v3, p1)
}
output:
{1 2}
{4 2}
{1500000000 2} 1500000000
&{1500000000 2} 1500000000
------------------------
{1 2} {1 0} {0 0} &{1 2}
values is {X:1 Y:2}, {X:1 Y:0}, {X:0 Y:0}, &{X:1 Y:2}
types is main.Vertex, main.Vertex, main.Vertex, *main.Vertex
values is main.Vertex{X:1, Y:2}, main.Vertex{X:1, Y:0}, main.Vertex{X:0, Y:0}, &main.Vertex{X:1, Y:2}
一個(gè)結(jié)構(gòu)體(struct
)就是一個(gè)字段的集合告抄。
結(jié)構(gòu)體字段使用點(diǎn)號(hào)來(lái)訪問(wèn).
結(jié)構(gòu)體字段可以通過(guò)結(jié)構(gòu)體指針來(lái)訪問(wèn)。
數(shù)組
package main
import "fmt"
func main() {
var a [2]string
a[0] = "Hello"
a[1] = "World"
fmt.Println(a[0], a[1])
fmt.Println(a)
}
類型 [n]T 是一個(gè)有 n 個(gè)類型為 T 的值的數(shù)組嵌牺。
表達(dá)式 var a [10]int
定義變量 a 是一個(gè)有十個(gè)整數(shù)的數(shù)組打洼。
數(shù)組的長(zhǎng)度是其類型的一部分,因此數(shù)組不能改變大小
這看起來(lái)是一個(gè)制約逆粹,但是請(qǐng)不要擔(dān)心募疮; Go 提供了更加便利的方式來(lái)使用數(shù)組。
slice
package main
import "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])
}
}
output:
p == [2 3 5 7 11 13]
p[0] == 2
p[1] == 3
p[2] == 5
p[3] == 7
p[4] == 11
p[5] == 13
一個(gè) slice 會(huì)指向一個(gè)序列的值僻弹,并且包含了長(zhǎng)度信息阿浓。
[]T
是一個(gè)元素類型為 T
的 slice。
- 對(duì) slice 切片
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])
// 省略下標(biāo)代表從 0 開(kāi)始
fmt.Println("p[:3] ==", p[:3])
// 省略上標(biāo)代表到 len(s) 結(jié)束
fmt.Println("p[4:] ==", p[4:])
}
slice 可以重新切片蹋绽,創(chuàng)建一個(gè)新的 slice 值指向相同的數(shù)組芭毙。
表達(dá)式 s[lo:hi]
表示從 lo 到 hi-1 的 slice 元素,含兩端卸耘。
因此s[lo:lo]
是空的退敦,而 s[lo:lo+1]
有一個(gè)元素。
- 構(gòu)造 slice
package main
import "fmt"
func main() {
a := make([]int, 5)
printSlice("a", a)
b := make([]int, 0, 5)
printSlice("b", b)
c := b[:2]
printSlice("c", c)
d := c[2:5]
printSlice("d", d)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
output:
a len=5 cap=5 [0 0 0 0 0]
b len=0 cap=5 []
c len=2 cap=5 [0 0]
d len=3 cap=3 [0 0 0]
slice 由函數(shù) make 創(chuàng)建蚣抗。這會(huì)分配一個(gè)零長(zhǎng)度的數(shù)組并且返回一個(gè) slice 指向這個(gè)數(shù)組:
a := make([]int, 5) // len(a)=5
為了指定容量侈百,可傳遞第三個(gè)參數(shù)到 make
:
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
- nil slice
package main
import "fmt"
func main() {
var z []int
fmt.Println(z, len(z), cap(z))
if z == nil {
fmt.Println("nil!")
}
}
output:
[] 0 0
nil!
slice 的零值是 nil
。
一個(gè) nil 的 slice 的長(zhǎng)度和容量是 0翰铡。
- 向 slice 添加元素
package main
import "fmt"
func main() {
var a []int
printSlice("a", a)
// append works on nil slices.
a = append(a, 0)
printSlice("a", a)
// the slice grows as needed.
a = append(a, 1)
printSlice("a", a)
// we can add more than one element at a time.
a = append(a, 2, 3, 4)
printSlice("a", a)
}
func printSlice(s string, x []int) {
fmt.Printf("%s len=%d cap=%d %v\n",
s, len(x), cap(x), x)
}
output:
a len=0 cap=0 []
a len=1 cap=1 [0]
a len=2 cap=2 [0 1]
a len=5 cap=6 [0 1 2 3 4]
向 slice 添加元素是一種常見(jiàn)的操作钝域,因此 Go 提供了一個(gè)內(nèi)建函數(shù) append
。 內(nèi)建函數(shù)的文檔對(duì) append
有詳細(xì)介紹锭魔。
func append(s []T, vs ...T) []T
append
的第一個(gè)參數(shù) s
是一個(gè)類型為 T
的數(shù)組例证,其余類型為 T
的值將會(huì)添加到 slice。
append
的結(jié)果是一個(gè)包含原 slice 所有元素加上新添加的元素的 slice赂毯。
如果 s
的底層數(shù)組太小战虏,而不能容納所有值時(shí),會(huì)分配一個(gè)更大的數(shù)組党涕。
返回的 slice 會(huì)指向這個(gè)新分配的數(shù)組烦感。
map
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"])
m := make(map[string]int)
m["Answer"] = 42
fmt.Println("The value:", m["Answer"])
m["Answer"] = 48
fmt.Println("The value:", m["Answer"])
delete(m, "Answer")
fmt.Println("The value:", m["Answer"])
v, ok := m["Answer"]
fmt.Println("The value:", v, "Present?", ok)
}
output:
{40.68433 -74.39967}
The value: 42
The value: 48
The value: 0
The value: 0 Present? false
map 映射鍵到值。
map 在使用之前必須用 make 而不是 new 來(lái)創(chuàng)建膛堤;值為 nil 的 map 是空的手趣,并且不能賦值
在 map m 中插入或修改一個(gè)元素:
m[key] = elem
獲得元素:
elem = m[key]
刪除元素:
delete(m, key)
通過(guò)雙賦值檢測(cè)某個(gè)鍵存在:
elem, ok = m[key]
如果 key 在 m 中,ok
為 true 。否則绿渣, ok 為 false
朝群,并且 elem 是 map 的元素類型的零值。
同樣的中符,當(dāng)從 map 中讀取某個(gè)不存在的鍵時(shí)姜胖,結(jié)果是 map 的元素類型的零值。
range
package main
import (
"fmt"
"sort"
)
var pow = []int{1, 2, 4, 8}
func main() {
for i, v := range pow {
fmt.Printf("2**%d = %d\n", i, v)
}
m := make(map[string]int)
m["Answer"] = 42
m["welcome"] = 48
var mp []string
for k, v := range m {
fmt.Printf("key:%s, value:%d \n", k, v)
mp = append(mp, k)
}
sort.Strings(mp)
for _, k := range mp {
fmt.Printf("key:%s, value:%d \n", k, m[k])
}
pow := make([]int, 3)
for i := range pow {
pow[i] = 1 << uint(i)
}
for _, value := range pow {
fmt.Printf("%d\n", value)
}
}
output:
2**0 = 1
2**1 = 2
2**2 = 4
2**3 = 8
key:Answer, value:42
key:welcome, value:48
key:Answer, value:42
key:welcome, value:48
1
2
4
for 循環(huán)的 range 格式可以對(duì) slice 或者 map 進(jìn)行迭代循環(huán)淀散。
可以通過(guò)賦值給 _
來(lái)忽略序號(hào)和值右莱。
如果只需要索引值,去掉“, value”的部分即可档插。
函數(shù)的閉包
package main
import "fmt"
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
pos, neg := adder(), adder()
for i := 0; i < 3; i++ {
fmt.Println(
pos(i),
neg(-2*i),
)
}
}
output:
0 0
1 -2
3 -6
Go 函數(shù)可以是閉包的慢蜓。閉包是一個(gè)函數(shù)值,它來(lái)自函數(shù)體的外部的變量引用。 函數(shù)可以對(duì)這個(gè)引用值進(jìn)行訪問(wèn)和賦值;換句話說(shuō)這個(gè)函數(shù)被“綁定”在這個(gè)變量上荐吵。
例如,函數(shù) adder 返回一個(gè)閉包翅萤。每個(gè)閉包都被綁定到其各自的 sum 變量上。
方法
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 (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func (v Vertex) Scale2(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
v := &Vertex{3, 4}
fmt.Println(v.Abs())
v.Scale(2)
fmt.Printf("%#v \n", v)
v.Scale2(2)
fmt.Printf("%#v \n", v)
v.Scale(2)
fmt.Printf("%#v \n", v)
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}
output:
5
&main.Vertex{X:6, Y:8}
&main.Vertex{X:6, Y:8}
&main.Vertex{X:12, Y:16}
1.4142135623730951
Go 沒(méi)有類。然而,仍然可以在結(jié)構(gòu)體類型上定義方法帆谍。
方法接收者 出現(xiàn)在 func 關(guān)鍵字和方法名之間的參數(shù)中。
你可以對(duì)包中的 任意 類型定義任意方法轴咱,而不僅僅是針對(duì)結(jié)構(gòu)體。
但是烈涮,不能對(duì)來(lái)自其他包的類型或基礎(chǔ)類型定義方法朴肺。
方法可以與命名類型或命名類型的指針關(guān)聯(lián)。
剛剛看到的兩個(gè) Abs 方法坚洽。一個(gè)是在 *Vertex 指針類型上戈稿,而另一個(gè)在 MyFloat 值類型上。
有兩個(gè)原因需要使用指針接收者讶舰。
- 首先避免在每個(gè)方法調(diào)用中拷貝值(如果值類型是大的結(jié)構(gòu)體的話會(huì)更有效率)鞍盗。
- 其次,方法可以修改接收者指向的值跳昼。
嘗試修改 Abs 的定義般甲,同時(shí) Scale 方法使用 Vertex 代替 *Vertex 作為接收者。
當(dāng) v 是 Vertex 的時(shí)候 Scale 方法沒(méi)有任何作用鹅颊。Scale
修改v
敷存。當(dāng) v 是一個(gè)值(非指針),方法看到的是 Vertex 的副本堪伍,并且無(wú)法修改原始值锚烦。Abs 的工作方式是一樣的觅闽。只不過(guò),僅僅讀取v
涮俄。所以讀取的是原始值(通過(guò)指針)還是那個(gè)值的副本并沒(méi)有關(guān)系蛉拙。
接口
package main
import (
"fmt"
"os"
)
type Reader interface {
Read(b []byte) (n int, err error)
}
type Writer interface {
Write(b []byte) (n int, err error)
}
type ReadWriter interface {
Reader
Writer
}
func main() {
var w Writer
// os.Stdout 實(shí)現(xiàn)了 Writer
w = os.Stdout
fmt.Fprintf(w, "hello, writer\n")
// os.Stdin 實(shí)現(xiàn)了 Writer, 但不是標(biāo)準(zhǔn)輸出
w = os.Stdin
fmt.Fprintf(w, "hello2, writer\n")
}
output:
hello, writer
接口類型是由一組方法定義的集合。
接口類型的值可以存放實(shí)現(xiàn)這些方法的任何值彻亲。
類型通過(guò)實(shí)現(xiàn)那些方法來(lái)實(shí)現(xiàn)接口刘离。 沒(méi)有顯式聲明的必要;所以也就沒(méi)有關(guān)鍵字“implements“睹栖。
隱式接口解藕了實(shí)現(xiàn)接口的包和定義接口的包:互不依賴硫惕。
因此,也就無(wú)需在每一個(gè)實(shí)現(xiàn)上增加新的接口名稱野来,這樣同時(shí)也鼓勵(lì)了明確的接口定義
- Stringers
package main
import "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)
fmt.Printf("%#v", a)
}
output:
Arthur Dent (42 years) Zaphod Beeblebrox (9001 years)
main.Person{Name:"Arthur Dent", Age:42}
一個(gè)普遍存在的接口是 fmt
包中定義的 Stringer
恼除。Stringer
是一個(gè)可以用字符串描述自己的類型。fmt
包 (還有許多其他包)使用這個(gè)來(lái)進(jìn)行輸出
錯(cuò)誤
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)
}
}
output:
at 2019-06-26 20:43:20.529809 +0800 CST m=+0.000310142, it didn't work
Go 程序使用 error 值來(lái)表示錯(cuò)誤狀態(tài)曼氛。
與 fmt.Stringer 類似豁辉,error
類型是一個(gè)內(nèi)建接口:
type error interface {
Error() string
}
(與 fmt.Stringer 類似,fmt
包在輸出時(shí)也會(huì)試圖匹配 error
舀患。)
通常函數(shù)會(huì)返回一個(gè) error 值徽级,調(diào)用的它的代碼應(yīng)當(dāng)判斷這個(gè)錯(cuò)誤是否等于 nil
, 來(lái)進(jìn)行錯(cuò)誤處理聊浅。
i, err := strconv.Atoi("42")
if err != nil {
fmt.Printf("couldn't convert number: %v\n", err)
}
fmt.Println("Converted integer:", i)
error 為 nil 時(shí)表示成功餐抢;非 nil 的 error 表示錯(cuò)誤。
Readers
package main
import (
"fmt"
"io"
"strings"
)
func main() {
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
}
}
}
output:
n = 8 err = <nil> b = [72 101 108 108 111 44 32 82]
b[:n] = "Hello, R"
n = 6 err = <nil> b = [101 97 100 101 114 33 32 82]
b[:n] = "eader!"
n = 0 err = EOF b = [101 97 100 101 114 33 32 82]
b[:n] = ""
io
包指定了 io.Reader
接口低匙, 它表示從數(shù)據(jù)流結(jié)尾讀取旷痕。
Go 標(biāo)準(zhǔn)庫(kù)包含了這個(gè)接口的許多實(shí)現(xiàn), 包括文件顽冶、網(wǎng)絡(luò)連接欺抗、壓縮、加密等等强重。
io.Reader
接口有一個(gè) Read
方法:
func (T) Read(b []byte) (n int, err error)
Read
用數(shù)據(jù)填充指定的字節(jié) slice绞呈,并且返回填充的字節(jié)數(shù)和錯(cuò)誤信息。 在遇到數(shù)據(jù)流結(jié)尾時(shí)间景,返回 io.EOF
錯(cuò)誤佃声。
例子代碼創(chuàng)建了一個(gè) strings.Reader
。 并且以每次 8 字節(jié)的速度讀取它的輸出拱燃。
Web 服務(wù)器
package main
import (
"fmt"
"log"
"net/http"
)
type Hello struct{}
func (h Hello) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello!")
}
func main() {
var h Hello
err := http.ListenAndServe("localhost:4000", h)
if err != nil {
log.Fatal(err)
}
}
包 http 通過(guò)任何實(shí)現(xiàn)了 http.Handler
的值來(lái)響應(yīng) HTTP 請(qǐng)求:
package http
type Handler interface {
ServeHTTP(w ResponseWriter, r *Request)
}
在這個(gè)例子中秉溉,類型 Hello
實(shí)現(xiàn)了 http.Handler
。
訪問(wèn) http://localhost:4000/ 會(huì)看到來(lái)自程序的問(wèn)候。
圖片
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())
}
output:
(0,0)-(100,100)
0 0 0 0
Package image 定義了 Image
接口:
package image
type Image interface {
ColorModel() color.Model
Bounds() Rectangle
At(x, y int) color.Color
}
注意:Bounds
方法的 Rectangle
返回值實(shí)際上是一個(gè) image.Rectangle
召嘶, 其定義在 image
包中父晶。
(參閱文檔了解全部信息。)
color.Color
和 color.Model
也是接口弄跌,但是通常因?yàn)橹苯邮褂妙A(yù)定義的實(shí)現(xiàn) image.RGBA
和 image.RGBAModel
而被忽視了甲喝。這些接口和類型由image/color 包定義。
多線程
goroutine
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 3; i++ {
time.Sleep(100 * time.Millisecond)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
output:
hello
world
world
hello
world
hello
goroutine 是由 Go 運(yùn)行時(shí)環(huán)境管理的輕量級(jí)線程铛只。
go f(x, y, z)
開(kāi)啟一個(gè)新的 goroutine 執(zhí)行
f(x, y, z)
f 埠胖, x , y
和 z
是當(dāng)前 goroutine 中定義的淳玩,但是在新的 goroutine 中運(yùn)行 f
直撤。
goroutine 在相同的地址空間中運(yùn)行,因此訪問(wèn)共享內(nèi)存必須進(jìn)行同步蜕着。sync
提供了這種可能谋竖,不過(guò)在 Go 中并不經(jīng)常用到,因?yàn)橛衅渌霓k法承匣。(在接下來(lái)的內(nèi)容中會(huì)涉及到蓖乘。)
channel
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)
}
output:
-5 17 12
channel 是有類型的管道,可以用 channel 操作符 <- 對(duì)其發(fā)送或者接收值韧骗。
ch <- v // 將 v 送入 channel ch嘉抒。
v := <-ch // 從 ch 接收,并且賦值給 v袍暴。
(“箭頭”就是數(shù)據(jù)流的方向些侍。)
和 map 與 slice 一樣,channel 使用前必須創(chuàng)建:
ch := make(chan int)
默認(rèn)情況下容诬,在另一端準(zhǔn)備好之前娩梨,發(fā)送和接收都會(huì)阻塞。這使得 goroutine 可以在沒(méi)有明確的鎖或競(jìng)態(tài)變量的情況下進(jìn)行同步览徒。
- 緩沖 channel
package main
import "fmt"
func main() {
c := make(chan int, 2)
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
}
output:
1
2
channel 可以是 帶緩沖的。為 make 提供第二個(gè)參數(shù)作為緩沖長(zhǎng)度來(lái)初始化一個(gè)緩沖 channel:
ch := make(chan int, 100)
向緩沖 channel 發(fā)送數(shù)據(jù)的時(shí)候颂龙,只有在緩沖區(qū)滿的時(shí)候才會(huì)阻塞习蓬。當(dāng)緩沖區(qū)清空的時(shí)候接受阻塞。
range 和 close
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, 4)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
output:
0
1
1
2
range 和 close
發(fā)送者可以 close 一個(gè) channel 來(lái)表示再?zèng)]有值會(huì)被發(fā)送了措嵌。
接收者可以通過(guò)賦值語(yǔ)句的第二參數(shù)來(lái)測(cè)試 channel 是否被關(guān)閉:當(dāng)沒(méi)有值可以接收并且 channel 已經(jīng)被關(guān)閉躲叼,那么經(jīng)過(guò)
v, ok := <-ch
之后 ok 會(huì)被設(shè)置為 false
。
循環(huán) for i := range c
會(huì)不斷從 channel 接收值企巢,直到它被關(guān)閉枫慷。
注意: 只有發(fā)送者才能關(guān)閉 channel,而不是接收者。向一個(gè)已經(jīng)關(guān)閉的 channel 發(fā)送數(shù)據(jù)會(huì)引起 panic或听。 還要注意: channel 與文件不同探孝;通常情況下無(wú)需關(guān)閉它們。只有在需要告訴接收者沒(méi)有更多的數(shù)據(jù)的時(shí)候才有必要進(jìn)行關(guān)閉誉裆,例如中斷一個(gè) range
顿颅。
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 < 5; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fibonacci(c, quit)
}
output:
0
1
1
2
3
quit
select 語(yǔ)句使得一個(gè) goroutine 在多個(gè)通訊操作上等待。
select 會(huì)阻塞足丢,直到條件分支中的某個(gè)可以繼續(xù)執(zhí)行粱腻,這時(shí)就會(huì)執(zhí)行那個(gè)條件分支。當(dāng)多個(gè)都準(zhǔn)備好的時(shí)候斩跌,會(huì)隨機(jī)選擇一個(gè)绍些。
- 默認(rèn)選擇
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)
}
}
}
output:
.
.
tick.
.
.
tick.
.
.
BOOM!
當(dāng) select
中的其他條件分支都沒(méi)有準(zhǔn)備好的時(shí)候,default
分支會(huì)被執(zhí)行耀鸦。
為了非阻塞的發(fā)送或者接收柬批,可使用 default
分支:
select {
case i := <-c:
// 使用 i
default:
// 從 c 讀取會(huì)阻塞
}