Hello,世界
這是Go語(yǔ)言官方的一個(gè)簡(jiǎn)明入門(mén)教程沿侈,可以幫助我們快速入門(mén)Go語(yǔ)言:Go語(yǔ)言之旅闯第,它提供了一個(gè)在線編輯、編譯缀拭、運(yùn)行咳短、格式化代碼的平臺(tái)。我們也可以通過(guò)
$ go get -u gitHub.com/Go-zh/tour tour
在自己的電腦上安裝并運(yùn)行此教程的程序蛛淋。
首先讓我們來(lái)看一下作為各種編程語(yǔ)言入門(mén)著名的hello world程序的Go語(yǔ)言版本
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
上面的代碼運(yùn)行結(jié)果輸出如下:
Hello, 世界
1 基礎(chǔ)
1.1 包
Go語(yǔ)言程序是由包構(gòu)成的咙好,main()函數(shù)作為執(zhí)行程序的入口,只能包含在main包中褐荷。當(dāng)需要使用外部包的時(shí)候勾效,使用import導(dǎo)入外部包。import可能還會(huì)包含導(dǎo)入路徑诚卸,包名與導(dǎo)入路徑的最后一個(gè)元素一致。當(dāng)需要導(dǎo)入多個(gè)包時(shí)绘迁,使用import加括號(hào)合溺,括號(hào)中包含所需要的所有包名。
示例程序:
package main
import (
"fmt"
"math/rand" //導(dǎo)入math/rand包用以調(diào)用它定義的Into()函數(shù)
)
func main() {
fmt.Println("My favorite number is", rand.Intn(10)) //返回10以?xún)?nèi)的隨機(jī)數(shù)(沒(méi)有定義隨機(jī)數(shù)種子缀台,所以每次都返回同一個(gè)數(shù)-偽隨機(jī)數(shù))
}
代碼運(yùn)行結(jié)果:
My favorite number is 1
1.2 導(dǎo)出名
在Go語(yǔ)言的包中棠赛,變量或者函數(shù)如果是以大寫(xiě)字母開(kāi)頭的表明它是可以被其他包訪問(wèn)的,如果不是則只能被包內(nèi)部訪問(wèn)
package main
import (
"fmt"
"math"
)
func main() {
//fmt.Println(math.pi) //這個(gè)例子其實(shí)并不合適math包里并沒(méi)有pi這個(gè)變量膛腐,運(yùn)行時(shí)只會(huì)報(bào)錯(cuò)undefined睛约。
fmt.Println(math.Pi)
}
1.3 函數(shù)
通常把實(shí)現(xiàn)某個(gè)需要被頻繁訪問(wèn)調(diào)用的代碼封裝成一個(gè)函數(shù),以使代碼變得更簡(jiǎn)潔高效哲身,Go語(yǔ)言定義函數(shù)的方式:
func <func-name> (parameters ) (return values) {
do something
}
下面的代碼實(shí)現(xiàn)一個(gè)加法函數(shù):
package main
import "fmt"
func add(x int, y int) int { //Go語(yǔ)言支持同類(lèi)型參數(shù)辩涝,省略類(lèi)型符號(hào),x int, y int等價(jià)于x, y int
return x + y
}
func main() {
fmt.Println(add(42, 13))
}
Go語(yǔ)言函數(shù)支持多值返回勘天。
package main
import "fmt"
func swap(x, y string) (string, string) {
return y, x
}
func main() {
a, b := swap("hello", "world") //swap 返回兩個(gè)值交換后的結(jié)果
fmt.Println(a, b)
}
代碼運(yùn)行結(jié)果:
world hello
1.4 變量
變量聲明格式:
var <var-name> <var-type>
變量初始化:
var <var-name> <var-type> = <var-value>
var <var-name1>, <var-name2> <var-type> = <var-value1>, <var-value2>
短變量聲明:Go語(yǔ)言支持不用顯式指出變量的類(lèi)型怔揩,它可以根據(jù)初始化值的類(lèi)型做類(lèi)型自動(dòng)判斷捉邢。但是它的使用有一定限制 - 只能在函數(shù)體內(nèi)部使用,如果在函數(shù)體外部商膊,必須要使用var關(guān)鍵字伏伐。
k := 3 //k會(huì)被自動(dòng)定義為Int類(lèi)型
當(dāng)類(lèi)型推導(dǎo)存在有歧義的可能時(shí),Go語(yǔ)言會(huì)根據(jù)類(lèi)型的初始化值的精度判斷具體類(lèi)型晕拆。
1.5 基本類(lèi)型
變量聲明也可以同導(dǎo)入語(yǔ)句一樣使用分組方式聲明(括號(hào))藐翎,沒(méi)有明確初始化值的變量會(huì)被賦予零值。
- bool - 零值:false
- string - 零值:""
- int(以及各種位數(shù)大小不同的整型派生類(lèi)型)
- byte(8位無(wú)符號(hào)整型-uint8)
- rune(32位有符號(hào)整型-int32)
- float32,float64
- complex64, complex128
數(shù)值類(lèi)型的零值都是0
Go語(yǔ)言中不存在隱式的類(lèi)型轉(zhuǎn)換实幕,在需要類(lèi)型轉(zhuǎn)換時(shí)必須要顯式地使用轉(zhuǎn)換函數(shù)()吝镣,否則會(huì)編譯報(bào)錯(cuò):
./prog.go:12:15: cannot use f (variable of type float64) as type uint in variable declaration
1.6 常量
常量使用const關(guān)鍵字定義,且不能使用短變量語(yǔ)法進(jìn)行聲明(很容易理解茬缩,不然的話(huà)根短變量會(huì)產(chǎn)生沖突)
2 流程控制語(yǔ)句
2.1 for循環(huán)
Go語(yǔ)言只有for循環(huán)赤惊,且不需要括號(hào)對(duì)循環(huán)的部分進(jìn)行包括
普通for循環(huán):
for i := 1; i < 10; i++ {
//do something
}
類(lèi)C語(yǔ)言while循環(huán):
for i < 100 {
i++
//do something
}
無(wú)限循環(huán):
for {
//do something
}
2.2 條件跳轉(zhuǎn)
if語(yǔ)句
if condition {
//do something
}
可以在條件表達(dá)式之前執(zhí)行一個(gè)表達(dá)式,需要用分號(hào)隔開(kāi)凰锡,表達(dá)式變量的作用域僅限if之內(nèi):
if v := math.Pow(x, n); v < lim {
return v
}
if-else語(yǔ)句
使用else為條件表達(dá)式的結(jié)果相反時(shí)進(jìn)行另一個(gè)操作未舟。
2.3 switch分支跳轉(zhuǎn)
switch相當(dāng)于一連串的if-else語(yǔ)句,它會(huì)從上到下匹配switch表達(dá)式的值掂为,匹配成功就執(zhí)行分支語(yǔ)句塊裕膀。也可以省略switch表達(dá)式,將匹配判斷放到case語(yǔ)句中去執(zhí)行
switch {
case t.Hour() < 12:
fmt.Println("Good morning!")
case t.Hour() < 17:
fmt.Println("Good afternoon.")
default:
fmt.Println("Good evening.")
}
2.4 defer語(yǔ)句
defer語(yǔ)句會(huì)將其后面的表達(dá)式推遲到外層函數(shù)返回之后才運(yùn)行
package main
import "fmt"
func main() {
defer fmt.Println("world") //這一段會(huì)等到main()函數(shù)返回之后才執(zhí)行
fmt.Println("hello")
}
推遲的函數(shù)調(diào)用會(huì)被壓入defer棧勇哗,根據(jù)棧的工作原理昼扛,被推遲的函數(shù)會(huì)按照先進(jìn)后出的順序返回。
package main
import "fmt"
func main() {
fmt.Println("counting")
for i := 0; i < 10; i++ {
defer fmt.Println(i)
}
fmt.Println("done")
}
程序運(yùn)行結(jié)果:
counting
done
9
8
7
6
5
4
3
2
1
0
3 指針
指針也是一種變量類(lèi)型欲诺,它存儲(chǔ)的是變量或者值的內(nèi)存地址抄谐。
var p *int
指針的零值是nil,&取址操作符扰法,*解引用操作符
i := 42
p = &i //p是一個(gè)指針保存了整型變量i的內(nèi)存地址
*p = 20 //修改了p指向的內(nèi)存地址出存儲(chǔ)的值
與C語(yǔ)言不同的是 Go沒(méi)有指針運(yùn)算蛹含。(指針自增、自減等)
4 結(jié)構(gòu)體
通過(guò)struct關(guān)鍵來(lái)定義一組字段創(chuàng)建結(jié)構(gòu)體
type Vertex struct {
x int
y int
}
結(jié)構(gòu)體成員使用.操作符來(lái)訪問(wèn)塞颁,通過(guò)結(jié)構(gòu)體指針來(lái)引用結(jié)構(gòu)體成員時(shí)可以通過(guò)*解引用
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
v := Vertex{1, 2}
p := &v
(*p).X = 1e9
// p.X = 1e9 為了簡(jiǎn)化表達(dá)浦箱,Go語(yǔ)言也支持這樣隱式表達(dá),不同于C語(yǔ)言p->X
fmt.Println(v)
}
5 數(shù)組
定義數(shù)組:
var a [10]int
數(shù)組的大小是固定的祠锣,不能更改酷窥。
6 切片
- 在類(lèi)似數(shù)組的定義時(shí),不限定它的大小伴网,就構(gòu)成了切片
var s []int
- 切片文法
[]bool{true, false, true}
切片的索引上界是它的長(zhǎng)度蓬推,下界是0,使用[n:m]表示子切片時(shí)澡腾,不包括索引值為m的元素拳氢。
- 切片長(zhǎng)度
len(s)
- 切片容量
cap(s)
- 切片的零值是nil
- 使用make創(chuàng)建切片
a := make ([]int, 5) //創(chuàng)建包含5個(gè)整型零值元素的切片募逞,切片的長(zhǎng)度是5
a := make([]int, 0, 5) //創(chuàng)建包含0個(gè)元素但是容量是5的切片
- 切片本身也可以做為切片的元素,也就是說(shuō)可以嵌套定義馋评。
- 可以通過(guò)內(nèi)建的append()函數(shù)向切片追加元素放接,如果切片的容量已滿(mǎn),會(huì)分配一個(gè)更大的數(shù)組留特,返回的切片會(huì)指向新分配的數(shù)組纠脾。但是追加的元素必須要和原切片的類(lèi)型相同。
7 Range
- range形式的for循環(huán)可以用來(lái)遍歷切片和循環(huán)
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)
}
}
- 可以將索引下標(biāo)或者值賦予_來(lái)忽略它們
for i, _ := range pow
for _, value := range pow
8 映射
- Go語(yǔ)言映射類(lèi)型就是類(lèi)似字典那樣的鍵值對(duì)蜕青,映射的零值是nil苟蹈,和切片類(lèi)似,可以使用make()方法初始化映射
type Vertex struct {
Lat, Long float64
}
m := make(map[string]Vertex)
- 訪問(wèn)映射時(shí)必須要提供key值
- 映射支持嵌套定義右核,也就是鍵值對(duì)里的值也可以是映射類(lèi)型
- 修改映射
m[key] = elem //如果key鍵存在慧脱,則更新對(duì)應(yīng)的值為elem,如果不存在則向映射m添加{key:elem}元素
elem = m[key] //獲取映射m中key鍵對(duì)應(yīng)的值賦給elem
delete(m, key) //刪除映射m中key鍵對(duì)應(yīng)的元素
elem, ok = m[key] //如果key鍵存在于映射m中贺喝,則ok=true菱鸥,elem被賦予對(duì)應(yīng)的值;否則ok=false躏鱼,elem賦予對(duì)應(yīng)類(lèi)型的零值
elem, ok := m[key] //如果elem,ok沒(méi)有被聲明則可以使用短變量聲明格式
9 函數(shù)值
Go語(yǔ)言中氮采,函數(shù)也可以作為一種值類(lèi)型,它可以用作函數(shù)的參數(shù)或者返回值
package main
import (
"fmt"
"math"
)
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
func main() {
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
} //hypot被定義為一個(gè)需要傳入兩個(gè)float64類(lèi)型參數(shù)染苛,返回一個(gè)float64類(lèi)型的函數(shù)值類(lèi)型鹊漠,它的定義由math.Sqrt()實(shí)現(xiàn)
fmt.Println(hypot(5, 12))
fmt.Println(compute(hypot)) //hypot作為函數(shù)值類(lèi)型也可以作為compute函數(shù)的參數(shù)
fmt.Println(compute(math.Pow))
}
10 函數(shù)的閉包
待補(bǔ)充
11 方法
- Go語(yǔ)言沒(méi)有類(lèi)的概念,可以為結(jié)構(gòu)體類(lèi)型定義方法茶行,方法就是帶有指定的接收者參數(shù)的函數(shù)
package main
import (
"fmt"
"math"
)
type Vertex struct { //定義結(jié)構(gòu)體Vertex
X, Y float64
}
func (v Vertex) Abs() float64 { //為結(jié)構(gòu)體Vertex定義方法-方法的接受者必須為Vertex類(lèi)型變量
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v := Vertex{3, 4}
fmt.Println(v.Abs())
}
- 并不是只能為結(jié)構(gòu)體類(lèi)型聲明方法躯概,也可以指定接受者類(lèi)型為內(nèi)建類(lèi)型,但是內(nèi)建類(lèi)型必須要用type關(guān)鍵字指定別名畔师。話(huà)說(shuō)回來(lái)娶靡,在Go語(yǔ)言中一旦使用type為內(nèi)建類(lèi)型指定別名,這個(gè)別名就不能認(rèn)為是和原內(nèi)建類(lèi)型一樣的去考慮茉唉。
package main
import (
"fmt"
"math"
)
type MyFloat float64 //只有為float64類(lèi)型指定別名MyFloat之后才能為其定義方法
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
f := MyFloat(-math.Sqrt2)
fmt.Println(f.Abs())
}
- 方法接受者的類(lèi)型也可以是指針類(lèi)型拉庵,這樣方法就可以讀取和修改原始值淡溯,而不是只在值副本上進(jìn)行操作
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 main() {
v := Vertex{3, 4} //用3,4初始化一個(gè)Vertex
v.Scale(10)//因?yàn)镾cale()方法的接受者是指針類(lèi)型籽孙,所以這里的v是指針奋渔,它可以修改原始值3*10塘雳,4*10揍鸟,
fmt.Println(v.Abs())//如果修改Scale()方法使方法接受者為Vertex類(lèi)型變量流强,則Scale修改的只是v的副本犀呼,在計(jì)算v.Abs()時(shí)蜡感,仍然用的是原始值3蹬蚁,4
}
- 在Go函數(shù)中恃泪,函數(shù)參數(shù)如果聲明為指針類(lèi)型必須要傳入指針值,否則編譯會(huì)報(bào)錯(cuò)
package main
import (
"fmt"
"math"
)
type Vertex struct {
X, Y float64
}
func Abs(v Vertex) float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func Scale(v *Vertex, f float64) { //如果在這里把*移除犀斋,編譯就會(huì)報(bào)錯(cuò)
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v := Vertex{3, 4}
Scale(&v, 10)
fmt.Println(Abs(v))
}
- 而在方法定義中贝乎,即使方法接受者被定義為一個(gè)指針,那么在調(diào)用方法的類(lèi)型即使是一個(gè)值也不會(huì)編譯出錯(cuò)
var v Vertex
v.Scale(5) // OK 因?yàn)镾cale()方法有一個(gè)指針接收者叽粹,Go會(huì)將v.Scale(5)解釋為(&v).Scale(5)
p := &v
p.Scale(10) // OK
這樣做的原因有二:
首先览效,方法能夠修改接受者指向的值
其次,避免在處理大型結(jié)構(gòu)時(shí)虫几,復(fù)制值造成的資源浪費(fèi)