Go 语言简介(上)— 语法
周末天氣不好,只能宅在家里,于是就順便看了一下Go語言,覺得比較有意思,所以寫篇文章介紹一下。我想寫一篇你可以在乘坐地鐵或公交車上下班時就可以初步了解一門語言的文章。所以,下面的文章主要是以代碼和注釋為主。只需要你對C語言,Unix,Python有一點基礎(chǔ),我相信你會在30分鐘左右讀完并對Go語言有一些初步了解的。
Hello World
| 1 2 3 4 5 6 7 | package main //聲明本文件的package名 import "fmt" //import語言的fmt庫——用于輸出 func main() { ????fmt.Println("hello world") } |
運行
你可以有兩種運行方式,
| 1 2 | $go run hello.go hello world |
| 1 2 3 4 5 6 7 | $go build hello.go $ls hello hello.go $./hello hello world |
自己的package
你可以使用GOPATH環(huán)境變量,或是使用相對路徑來import你自己的package。
Go的規(guī)約是這樣的:
1)在import中,你可以使用相對路徑,如 ./或 ../ 來引用你的package
2)如果沒有使用相對路徑,那么,go會去找$GOPATH/src/目錄。
| 1 | import "./haoel"? //import當(dāng)前目錄里haoel子目錄里的所有的go文件 |
| 1 | import "haoel"? //import 環(huán)境變量 $GOPATH/src/haoel子目錄里的所有的go文件 |
fmt輸出格式
fmt包和libc里的那堆使用printf, scanf,fprintf,fscanf 很相似。下面的東西對于C程序員不會陌生。
注意:Println不支持,Printf才支持%式的輸出:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package main import "fmt" import "math" func main() { ????fmt.Println("hello world") ????fmt.Printf("%t\n", 1==2) ????fmt.Printf("二進制:%b\n", 255) ????fmt.Printf("八進制:%o\n", 255) ????fmt.Printf("十六進制:%X\n", 255) ????fmt.Printf("十進制:%d\n", 255) ????fmt.Printf("浮點數(shù):%f\n", math.Pi) ????fmt.Printf("字符串:%s\n", "hello world") } |
當(dāng)然,也可以使用如\n\t\r這樣的和C語言一樣的控制字符
變量和常量
變量的聲明很像 javascript,使用 var關(guān)鍵字。注意:go是靜態(tài)類型的語言,下面是代碼:
| 1 2 3 4 5 6 7 8 | //聲明初始化一個變量 var? x int = 100 var str string = "hello world"</pre> //聲明初始化多個變量 var? i, j, k int = 1, 2, 3 //不用指明類型,通過初始化值來推導(dǎo) var b = true //bool型 |
還有一種定義變量的方式(這讓我想到了Pascal語言,但完全不一樣)
| 1 | x := 100 //等價于 var x int = 100; |
常量很簡單,使用const關(guān)鍵字:
| 1 2 | const s string = "hello world" const pi float32 = 3.1415926 |
數(shù)組
直接看代碼(注意其中的for語句,和C很相似吧,就是沒有括號了)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | func main() { ????var a [5]int ????fmt.Println("array a:", a) ????a[1] = 10 ????a[3] = 30 ????fmt.Println("assign:", a) ????fmt.Println("len:", len(a)) ????b := [5]int{1, 2, 3, 4, 5} ????fmt.Println("init:", b) ????var c [2][3]int ????for i := 0; i < 2; i++ { ????????for j := 0; j < 3; j++ { ????????????c[i][j] = i + j ????????} ????} ????fmt.Println("2d: ", c) } |
運行結(jié)果:
| 1 2 3 4 5 | array a: [0 0 0 0 0] assign: [0 10 0 30 0] len: 5 init: [1 2 3 4 5] 2d:? [[0 1 2] [1 2 3]] |
數(shù)組的切片操作
這個很Python了。
| 1 2 3 4 5 6 7 8 9 10 | a := [5]int{1, 2, 3, 4, 5} b := a[2:4] // a[2] 和 a[3],但不包括a[4] fmt.Println(b) b = a[:4] // 從 a[0]到a[4],但不包括a[4] fmt.Println(b) b = a[2:] // 從 a[2]到a[4],且包括a[2] fmt.Println(b) |
分支循環(huán)語句
if語句
注意:if 語句沒有圓括號,而必需要有花括號
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //if 語句 if x % 2 == 0 { ????//... } //if - else if x % 2 == 0 { ????//偶數(shù)... } else { ????//奇數(shù)... } //多分支 if num < 0 { ????//負數(shù) } else if num == 0 { ????//零 } else { ????//正數(shù) } |
switch 語句
注意:switch語句沒有break,還可以使用逗號case多個值
| 1 2 3 4 5 6 7 8 9 10 11 12 | switch i { ????case 1: ????????fmt.Println("one") ????case 2: ????????fmt.Println("two") ????case 3: ????????fmt.Println("three") ????case 4,5,6: ????????fmt.Println("four, five, six") ????default: ????????fmt.Println("invalid value!") } |
for 語句
前面你已見過了,下面再來看看for的三種形式:(注意:Go語言中沒有while)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //經(jīng)典的for語句 init; condition; post for i := 0; i<10; i++{ ?????fmt.Println(i) } //精簡的for語句 condition i := 1 for i<10 { ????fmt.Println(i) ????i++ } //死循環(huán)的for語句 相當(dāng)于for(;;) i :=1 for { ????if i>10 { ????????break ????} ????i++ } |
關(guān)于分號
從上面的代碼我們可以看到代碼里沒有分號。其實,和C一樣,Go的正式的語法使用分號來終止語句。和C不同的是,這些分號由詞法分析器在掃描源代碼過程中使用簡單的規(guī)則自動插入分號,因此輸入源代碼多數(shù)時候就不需要分號了。
規(guī)則是這樣的:如果在一個新行前方的最后一個標(biāo)記是一個標(biāo)識符(包括像int和float64這樣的單詞)、一個基本的如數(shù)值這樣的文字、或以下標(biāo)記中的一個時,會自動插入分號:
break continue fallthrough return ++ -- ) }通常Go程序僅在for循環(huán)語句中使用分號,以此來分開初始化器、條件和增量單元。如果你在一行中寫多個語句,也需要用分號分開。
注意:無論任何時候,你都不應(yīng)該將一個控制結(jié)構(gòu)((if、for、switch或select)的左大括號放在下一行。如果這樣做,將會在大括號的前方插入一個分號,這可能導(dǎo)致出現(xiàn)不想要的結(jié)果。
map
map在別的語言里可能叫哈希表或叫dict,下面是和map的相關(guān)操作的代碼,代碼很容易懂
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | func main(){ ????m := make(map[string]int) //使用make創(chuàng)建一個空的map ????m["one"] = 1 ????m["two"] = 2 ????m["three"] = 3 ????fmt.Println(m) //輸出 map[three:3 two:2 one:1] (順序在運行時可能不一樣) ????fmt.Println(len(m)) //輸出 3 ????v := m["two"] //從map里取值 ????fmt.Println(v) // 輸出 2 ????delete(m, "two") ????fmt.Println(m) //輸出 map[three:3 one:1] ????m1 := map[string]int{"one": 1, "two": 2, "three": 3} ????fmt.Println(m1) //輸出 map[two:2 three:3 one:1] (順序在運行時可能不一樣) ????for key, val := range m1{ ????????fmt.Printf("%s => %d \n", key, val) ????????/*輸出:(順序在運行時可能不一樣) ????????????three => 3 ????????????one => 1 ????????????two => 2*/ ????} } |
指針
Go語言一樣有指針,看代碼
| 1 2 3 4 5 6 7 8 9 10 11 12 | var i int = 1 var pInt *int = &i //輸出:i=1???? pInt=0xf8400371b0?????? *pInt=1 fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt) *pInt = 2 //輸出:i=2???? pInt=0xf8400371b0?????? *pInt=2 fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt) i = 3 //輸出:i=3???? pInt=0xf8400371b0?????? *pInt=3 fmt.Printf("i=%d\tpInt=%p\t*pInt=%d\n", i, pInt, *pInt) |
Go具有兩個分配內(nèi)存的機制,分別是內(nèi)建的函數(shù)new和make。他們所做的事不同,所應(yīng)用到的類型也不同,這可能引起混淆,但規(guī)則卻很簡單。
內(nèi)存分配
new?是一個分配內(nèi)存的內(nèi)建函數(shù),但不同于其他語言中同名的new所作的工作,它只是將內(nèi)存清零,而不是初始化內(nèi)存。new(T)為一個類型為T的新項目分配了值為零的存儲空間并返回其地址,也就是一個類型為*T的值。用Go的術(shù)語來說,就是它返回了一個指向新分配的類型為T的零值的指針。
make(T,?args)函數(shù)的目的與new(T)不同。它僅用于創(chuàng)建切片、map和chan(消息管道),并返回類型T(不是*T)的一個被初始化了的(不是零)實例。這種差別的出現(xiàn)是由于這三種類型實質(zhì)上是對在使用前必須進行初始化的數(shù)據(jù)結(jié)構(gòu)的引用。例如,切片是一個具有三項內(nèi)容的描述符,包括指向數(shù)據(jù)(在一個數(shù)組內(nèi)部)的指針、長度以及容量,在這三項內(nèi)容被初始化之前,切片值為nil。對于切片、映射和信道,make初始化了其內(nèi)部的數(shù)據(jù)結(jié)構(gòu)并準(zhǔn)備了將要使用的值。如:
下面的代碼分配了一個整型數(shù)組,長度為10,容量為100,并返回前10個數(shù)組的切片
| 1 | make([]int, 10, 100) |
以下示例說明了new和make的不同。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | var p *[]int = new([]int)?? // 為切片結(jié)構(gòu)分配內(nèi)存;*p == nil;很少使用 var v? []int = make([]int, 10) // 切片v現(xiàn)在是對一個新的有10個整數(shù)的數(shù)組的引用 // 不必要地使問題復(fù)雜化: var p *[]int = new([]int) fmt.Println(p) //輸出:&[] *p = make([]int, 10, 10) fmt.Println(p) //輸出:&[0 0 0 0 0 0 0 0 0 0] fmt.Println((*p)[2]) //輸出: 0 // 習(xí)慣用法: v := make([]int, 10) fmt.Println(v) //輸出:[0 0 0 0 0 0 0 0 0 0] |
函數(shù)
老實說,我對Go語言這種反過來聲明變量類型和函數(shù)返回值的做法有點不滿(保持和C一樣的不可以嗎? 呵呵)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package main import "fmt" func max(a int, b int) int { //注意參數(shù)和返回值是怎么聲明的 ????if a > b { ????????return a ????} ????return b } func main(){ ????fmt.Println(max(4, 5)) } |
函數(shù)返回多個值
Go中很多Package 都會返回兩個值,一個是正常值,一個是錯誤,如下所示:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | package main import "fmt" func main(){ ????v, e := multi_ret("one") ????fmt.Println(v,e) //輸出 1 true ????v, e = multi_ret("four") ????fmt.Println(v,e) //輸出 0 false ????//通常的用法(注意分號后有e) ????if v, e = multi_ret("four"); e { ????????// 正常返回 ????}else{ ????????// 出錯返回 ????} } func multi_ret(key string) (int, bool){ ????m := map[string]int{"one": 1, "two": 2, "three": 3} ????var err bool ????var val int ????val, err = m[key] ????return val, err } |
函數(shù)不定參數(shù)
例子很清楚了,我就不多說了
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | func sum(nums ...int) { ????fmt.Print(nums, " ")? //輸出如 [1, 2, 3] 之類的數(shù)組 ????total := 0 ????for _, num := range nums { //要的是值而不是下標(biāo) ????????total += num ????} ????fmt.Println(total) } func main() { ????sum(1, 2) ????sum(1, 2, 3) ????//傳數(shù)組 ????nums := []int{1, 2, 3, 4} ????sum(nums...) } |
函數(shù)閉包
nextNum這個函數(shù)返回了一個匿名函數(shù),這個匿名函數(shù)記住了nextNum中i+j的值,并改變了i,j的值,于是形成了一個閉包的用法
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | func nextNum() func() int { ????i,j := 1,1 ????return func() int { ????????var tmp = i+j ????????i, j = j, tmp ????????return tmp ????} } //main函數(shù)中是對nextNum的調(diào)用,其主要是打出下一個斐波拉契數(shù) func main(){ ????nextNumFunc := nextNum() ????for i:=0; i<10; i++ { ????????fmt.Println(nextNumFunc()) ????} } |
函數(shù)的遞歸
和c基本是一樣的
| 1 2 3 4 5 6 7 8 9 10 | func fact(n int) int { ????if n == 0 { ????????return 1 ????} ????return n * fact(n-1) } func main() { ????fmt.Println(fact(7)) } |
結(jié)構(gòu)體
Go的結(jié)構(gòu)體和C的基本上一樣,不過在初始化時有些不一樣,Go支持帶名字的初始化。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | type Person struct { ????name string ????age? int ????email string } func main() { ????//初始化 ????person := Person{"Tom", 30, "tom@gmail.com"} ????person = Person{name:"Tom", age: 30, email:"tom@gmail.com"} ????fmt.Println(person) //輸出 {Tom 30 tom@gmail.com} ????pPerson := &person ????fmt.Println(pPerson) //輸出 &{Tom 30 tom@gmail.com} ????pPerson.age = 40 ????person.name = "Jerry" ????fmt.Println(person) //輸出 {Jerry 40 tom@gmail.com} } |
結(jié)構(gòu)體方法
不多說了,看代碼吧。
注意:Go語言中沒有public, protected, private的關(guān)鍵字,所以,如果你想讓一個方法可以被別的包訪問的話,你需要把這個方法的第一個字母大寫。這是一種約定。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | type rect struct { ????width, height int } func (r *rect) area() int { //求面積 ????return r.width * r.height } func (r *rect) perimeter() int{ //求周長 ????return 2*(r.width + r.height) } func main() { ????r := rect{width: 10, height: 15} ????fmt.Println("面積: ", r.area()) ????fmt.Println("周長: ", r.perimeter()) ????rp := &r ????fmt.Println("面積: ", rp.area()) ????fmt.Println("周長: ", rp.perimeter()) } |
接口和多態(tài)
接口意味著多態(tài),下面是一個經(jīng)典的例子,不用多說了,自己看代碼吧。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | //---------- 接 口 --------// type shape interface { ????area() float64 //計算面積 ????perimeter() float64 //計算周長 } //--------- 長方形 ----------// type rect struct { ????width, height float64 } func (r *rect) area() float64 { //面積 ????return r.width * r.height } func (r *rect) perimeter() float64 { //周長 ????return 2*(r.width + r.height) } //----------- 圓? 形 ----------// type circle struct { ????radius float64 } func (c *circle) area() float64 { //面積 ????return math.Pi * c.radius * c.radius } func (c *circle) perimeter() float64 { //周長 ????return 2 * math.Pi * c.radius } // ----------- 接口的使用 -----------// func interface_test() { ????r := rect {width:2.9, height:4.8} ????c := circle {radius:4.3} ????s := []shape{&r, &c} //通過指針實現(xiàn) ????for _, sh := range s { ????????fmt.Println(sh) ????????fmt.Println(sh.area()) ????????fmt.Println(sh.perimeter()) ????} } |
錯誤處理 – Error接口
函數(shù)錯誤返回可能是C/C++時最讓人糾結(jié)的東西的,Go的多值返回可以讓我們更容易的返回錯誤,其可以在返回一個常規(guī)的返回值之外,還能輕易地返回一個詳細的錯誤描述。通常情況下,錯誤的類型是error,它有一個內(nèi)建的接口。
| 1 2 3 | type error interface { ????Error() string } |
還是看個示例吧:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | package main import "fmt" import "errors" //自定義的出錯結(jié)構(gòu) type myError struct { ????arg? int ????errMsg string } //實現(xiàn)Error接口 func (e *myError) Error() string { ????return fmt.Sprintf("%d - %s", e.arg, e.errMsg) } //兩種出錯 func error_test(arg int) (int, error) { ????if arg < 0? { ?????????return -1, errors.New("Bad Arguments - negtive!") ?????}else if arg >256 { ????????return -1, &myError{arg, "Bad Arguments - too large!"} ????} ????return arg*arg, nil } //相關(guān)的測試 func main() { ????for _, i := range []int{-1, 4, 1000} { ????????if r, e := error_test(i); e != nil { ????????????fmt.Println("failed:", e) ????????} else { ????????????fmt.Println("success:", r) ????????} ????} } |
程序運行后輸出:
| 1 2 3 | failed: Bad Arguments - negtive! success: 16 failed: 1000 - Bad Arguments - too large! |
錯誤處理 – Defer
下面的程序?qū)τ诿恳粋€熟悉C語言的人來說都不陌生(有資源泄露的問題),C++使用RAII來解決這種問題。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | func CopyFile(dstName, srcName string) (written int64, err error) { ????src, err := os.Open(srcName) ????if err != nil { ????????return ????} ????dst, err := os.Create(dstName) ????if err != nil { ????????return ????} ????written, err = io.Copy(dst, src) ????dst.Close() ????src.Close() ????return } |
Go語言引入了Defer來確保那些被打開的文件能被關(guān)閉。如下所示:(這種解決方式還是比較優(yōu)雅的)
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | func CopyFile(dstName, srcName string) (written int64, err error) { ????src, err := os.Open(srcName) ????if err != nil { ????????return ????} ????defer src.Close() ????dst, err := os.Create(dstName) ????if err != nil { ????????return ????} ????defer dst.Close() ????return io.Copy(dst, src) } |
Go的defer語句預(yù)設(shè)一個函數(shù)調(diào)用(延期的函數(shù)),該調(diào)用在函數(shù)執(zhí)行defer返回時立刻運行。該方法顯得不同常規(guī),但卻是處理上述情況很有效,無論函數(shù)怎樣返回,都必須進行資源釋放。
我們再來看一個defer函數(shù)的示例:
| 1 2 3 | for i := 0; i < 5; i++ { ????defer fmt.Printf("%d ", i) } |
被延期的函數(shù)以后進先出(LIFO)的順行執(zhí)行,因此以上代碼在返回時將打印4 3 2 1 0。
總之,我個人覺得defer的函數(shù)行為有點怪異,我現(xiàn)在還沒有完全搞清楚。
錯誤處理 – Panic/Recover
對于不可恢復(fù)的錯誤,Go提供了一個內(nèi)建的panic函數(shù),它將創(chuàng)建一個運行時錯誤并使程序停止(相當(dāng)暴力)。該函數(shù)接收一個任意類型(往往是字符串)作為程序死亡時要打印的東西。當(dāng)編譯器在函數(shù)的結(jié)尾處檢查到一個panic時,就會停止進行常規(guī)的return語句檢查。
下面的僅僅是一個示例。實際的庫函數(shù)應(yīng)避免panic。如果問題可以容忍,最好是讓事情繼續(xù)下去而不是終止整個程序。
| 1 2 3 4 5 6 7 | var user = os.Getenv("USER") func init() { ????if user == "" { ????????panic("no value for $USER") ????} } |
當(dāng)panic被調(diào)用時,它將立即停止當(dāng)前函數(shù)的執(zhí)行并開始逐級解開函數(shù)堆棧,同時運行所有被defer的函數(shù)。如果這種解開達到堆棧的頂端,程序就死亡了。但是,也可以使用內(nèi)建的recover函數(shù)來重新獲得Go程的控制權(quán)并恢復(fù)正常的執(zhí)行。 對recover的調(diào)用會通知解開堆棧并返回傳遞到panic的參量。由于僅在解開期間運行的代碼處在被defer的函數(shù)之內(nèi),recover僅在被延期的函數(shù)內(nèi)部才是有用的。
你可以簡單地理解為recover就是用來捕捉Painc的,防止程序一下子就掛掉了。
下面是一個例程,很簡單了,不解釋了
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | func g(i int) { ????if i>1 { ????????fmt.Println("Panic!") ????????panic(fmt.Sprintf("%v", i)) ????} } func f() { ????defer func() { ????????if r := recover(); r != nil { ????????????fmt.Println("Recovered in f", r) ????????} ????}() ????for i := 0; i < 4; i++ { ????????fmt.Println("Calling g with ", i) ????????g(i) ????????fmt.Println("Returned normally from g.") ?????} } func main() { ????f() ????fmt.Println("Returned normally from f.") } |
運行結(jié)果如下:(我們可以看到Painc后的for循環(huán)就沒有往下執(zhí)行了,但是main的程序還在往下走)
| 1 2 3 4 5 6 7 8 | Calling g with? 0 Returned normally from g. Calling g with? 1 Returned normally from g. Calling g with? 2 Panic! Recovered in f 2 Returned normally from f. |
你習(xí)慣這種編程方式嗎?我覺得有點詭異。呵呵。
好了,上面是是一Go語言相關(guān)的編程語法的介紹,我沒有事無巨細,只是讓你了解一下Go語言是長什么樣的。當(dāng)然,這還沒完,請期待下篇——Go語言的特性。
from:?http://coolshell.cn/articles/8460.html
總結(jié)
以上是生活随笔為你收集整理的Go 语言简介(上)— 语法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C++11(及现代C++风格)和快速迭代
- 下一篇: Go 语言简介(下)— 特性