如今咱們來創建一個完整的程序main.go
:程序員
// Golang程序入口的包名必須爲 main package main // import "golang" // 導入其餘地方的包,包經過 go mod 機制尋找 import ( "fmt" "golang/diy" ) // init函數在main函數以前執行 func init() { // 聲明並初始化三個值 var i, j, k = 1, 2, 3 // 使用格式化包打印 fmt.Println("init hello world") fmt.Println(i, j, k) } // 函數,兩個數相加 func sum(a, b int64) int64 { return a + b } // 程序入口必須爲 main 函數 func main() { // 未使用的變量,不容許聲明 //cannot := 6 fmt.Println("hello world") // 定義基本數據類型 p := true // bool a := 3 // int b := 6.0 // float64 c := "hi" // string d := [3]string{"1", "2", "3"} // array,基本不用到 e := []int64{1, 2, 3} // slice f := map[string]int64{"a": 3, "b": 4} // map fmt.Printf("type:%T:%v\n", p, p) fmt.Printf("type:%T:%v\n", a, a) fmt.Printf("type:%T:%v\n", b, b) fmt.Printf("type:%T:%v\n", c, c) fmt.Printf("type:%T:%v\n", d, d) fmt.Printf("type:%T:%v\n", e, e) fmt.Printf("type:%T:%v\n", f, f) // 切片放值 e[0] = 9 // 切片增長值 e = append(e, 3) // 增長map鍵值 f["f"] = 5 // 查找map鍵值 v, ok := f["f"] fmt.Println(v, ok) v, ok = f["ff"] fmt.Println(v, ok) // 判斷語句 if a > 0 { fmt.Println("a>0") } else { fmt.Println("a<=0") } // 死循環語句 a = 0 for { if a >= 10 { fmt.Println("out") // 退出循環 break } a = a + 1 if a > 5 { continue } else { fmt.Println(a) } } // 循環語句 for i := 9; i <= 10; i++ { fmt.Printf("i=%d\n", i) } // 循環切片 for k, v := range e { fmt.Println(k, v) } // 循環map for k, v := range f { fmt.Println(k, v) } // 定義 int64 變量 var h, i int64 = 4, 6 // 使用函數 sum := sum(h, i) fmt.Printf("sum(h+i),h=%v,i=%v,%v\n", h, i, sum) // 新建結構體,值 g := diy.Diy{ A: 2, //b: 4.0, // 小寫成員不能導出 } // 打印類型,值 fmt.Printf("type:%T:%v\n", g, g) // 小寫方法不能導出 //g.set(1,1) g.Set(1, 1) fmt.Printf("type:%T:%v\n", g, g) // 結構體值變化 g.Set2(3, 3) fmt.Printf("type:%T:%v\n", g, g) // 結構體值未變化 // 新建結構體,引用 k := &diy.Diy{ A: 2, } fmt.Printf("type:%T:%v\n", k, k) k.Set(1, 1) fmt.Printf("type:%T:%v\n", k, k) // 結構體值變化 k.Set2(3, 3) fmt.Printf("type:%T:%v\n", k, k) // 結構體值未變化 // 新建結構體,引用 m := new(diy.Diy) m.A = 2 fmt.Printf("type:%T:%v\n", m, m) s := make([]int64, 5) s1 := make([]int64, 0, 5) m1 := make(map[string]int64, 5) m2 := make(map[string]int64) fmt.Printf("%#v,cap:%#v,len:%#v\n", s, cap(s), len(s)) fmt.Printf("%#v,cap:%#v,len:%#v\n", s1, cap(s1), len(s1)) fmt.Printf("%#v,len:%#v\n", m1, len(m1)) fmt.Printf("%#v,len:%#v\n", m2, len(m2)) var ll []int64 fmt.Printf("%#v\n", ll) ll = append(ll, 1) fmt.Printf("%#v\n", ll) ll = append(ll, 2, 3, 4, 5, 6) fmt.Printf("%#v\n", ll) ll = append(ll, []int64{7, 8, 9}...) fmt.Printf("%#v\n", ll) fmt.Println(ll[0:2]) fmt.Println(ll[:2]) fmt.Println(ll[0:]) fmt.Println(ll[:]) }
在相同目錄下新建diy
文件夾,文件下新建一個diy.go
文件(名字任取):golang
// 包名 package diy // 結構體 type Diy struct { A int64 // 大寫導出成員 b float64 // 小寫不能夠導出 } // 引用結構體的方法,引用傳遞,會改變原有結構體的值 func (diy *Diy) Set(a int64, b float64) { diy.A = a diy.b = b return } // 值結構體的方法,值傳遞,不會改變原有結構體的值 func (diy Diy) Set2(a int64, b float64) { diy.A = a diy.b = b return } // 小寫方法,不能導出 func (diy Diy) set(a int64, b float64) { diy.A = a diy.b = b return } // 小寫函數,不能導出,只能在同一包下使用 func sum(a, b int64) int64 { return a + b }
進入文件所在目錄,打開命令行終端,執行:算法
go mod init go run main.go
會顯示一些打印結果:編程
init hello world 1 2 3 hello world type:bool:true type:int:3 type:float64:6 type:string:hi type:[3]string:[1 2 3] type:[]int64:[1 2 3] type:map[string]int64:map[a:3 b:4] 5 true 0 false a>0 1 2 3 4 5 out i=9 i=10 0 9 1 2 2 3 3 3 a 3 b 4 f 5 sum(h+i),h=4,i=6,10 type:diy.Diy:{2 0} type:diy.Diy:{1 1} type:diy.Diy:{1 1} type:*diy.Diy:&{2 0} type:*diy.Diy:&{1 1} type:*diy.Diy:&{1 1} type:*diy.Diy:&{2 0} []int64{0, 0, 0, 0, 0},cap:5,len:5 []int64{},cap:5,len:0 map[string]int64{},len:0 map[string]int64{},len:0 []int64(nil) []int64{1} []int64{1, 2, 3, 4, 5, 6} []int64{1, 2, 3, 4, 5, 6, 7, 8, 9} [1 2] [1 2] [1 2 3 4 5 6 7 8 9] [1 2 3 4 5 6 7 8 9]
咱們看到Golang
語言只有小括號和大括號,不須要使用逗號來分隔代碼,只有一種循環for
。segmentfault
接下來咱們會分析這個例子。數組
每個大型的軟件工程項目,都須要進行工程管理。工程管理的一個環節就是代碼層次的管理。數據結構
包,也稱爲庫,如代碼的一個包,代碼的一個庫,英文:Library
或者Package
。好比,咱們經常聽到某程序員說:嘿,X哥,我知道Github
上有一個更好用的數據加密庫,幾千顆星呢。併發
在高級編程語言層次,也就是代碼自己,各類語言發明了包(package
)機制來更好的管理代碼,將代碼按功能分類歸屬於不一樣的包。app
Golang
語言目前的包管理新機制叫go mod
。數據結構和算法
咱們的項目結構是:
├── diy │ └── diy.go └── main.go
每個*.go
源碼文件,必須屬於一個包,假設包名叫diy
,在代碼最頂端必須有package diy
,在此以前不能有其餘代碼片斷,如diy/diy.go
文件中:
// 包名 package diy // 結構體 type Diy struct { A int64 // 大寫導出成員 b float64 // 小寫不能夠導出 }
做爲執行入口的源碼,則強制包名必須爲main
,入口函數爲func main()
,如main.go
文件中:
// Golang程序入口的包名必須爲 main package main // import "golang" // 導入其餘地方的包,包經過 go mod 機制尋找 import ( "fmt" "golang/diy" )
在入口文件main.go
文件夾下執行如下命令:
go mod int
該命令會解析main.go
文件的第一行package main // import "golang"
,注意註釋//
後面的import "golang"
,會生成go.mod
文件:
module golang go 1.13
Golang
編譯器會將這個項目認爲是包golang
,這是整個項目最上層的包,而底下的文件夾diy
做爲package diy
,包名全路徑就是golang/diy
。
接着,main.go
爲了導入包,使用import ()
:
// 導入其餘地方的包,包經過 go mod 機制尋找 import ( "fmt" "golang/diy" )
能夠看到導入了官方的包fmt
和咱們自已定義的包golang/diy
,官方的包會自動尋找到,不須要任何額外處理,而本身的包會在當前項目往下找。
在包golang/diy
中,咱們定義了一個結構體和函數:
// 結構體 type Diy struct { A int64 // 大寫導出成員 b float64 // 小寫不能夠導出 } // 小寫函數,不能導出,只能在同一包下使用 func sum(a, b int64) int64 { return a + b }
對於包中小寫的函數或者結構體中小寫的字段,不能導出,其餘包不能使用它,Golang
用它實現了私有或公有控制,畢竟有些包的內容咱們不想在其餘包中被使用,相似Java
的private
關鍵字。
結構體和函數會在後面的章節介紹,如今只需知道只有大寫字母開頭的結構體或函數,才能在其餘包被人引用。
最後,Golang
的程序入口統一在包main
中的main
函數,執行程序時是從這裏開始的:
package main import "fmt" // init函數在main函數以前執行 func init() { // 聲明並初始化三個值 var i, j, k = 1, 2, 3 // 使用格式化包打印 fmt.Println("init hello world") fmt.Println(i, j, k) } // 程序入口必須爲 main 函數 func main() { }
有個必須注意的事情是函數init()
會在每一個包被導入以前執行,若是導入了多個包,那麼會根據包導入的順序前後執行init()
,再回到執行函數main()
。
Golang語言能夠先聲明變量,再賦值,也能夠直接建立一個帶值的變量。如:
// 聲明並初始化三個值 var i, j, k = 1, 2, 3 // 聲明後再賦值 var i int64 i = 3 // 直接賦值,建立一個新的變量 j := 5
能夠看到var i int64
,數據類型是在變量的後面而不是前面,這是Golang
語言與其餘語言最大的區別之一。
同時,做爲一門靜態語言,Golang
在編譯前還會檢查哪些變量和包未被引用,強制禁止遊離的變量和包,從而避免某些人類低級錯誤。如:
package main func main(){ a := 2 }
若是執行將會報錯:
go run main.go ./main.go:26:2: cannot declared and not used
提示聲明變量未使用,這是Golang
語言與其餘語言最大的區別之一。
變量定義後,若是沒有賦值,那麼存在默認值。咱們也能夠定義常量,只需加關鍵字const
,如:
const s = 2
常量一旦定義就不能修改。
咱們再來看看基本的數據類型有那些:
// 定義基本數據類型 p := true // bool a := 3 // int b := 6.0 // float64 c := "hi" // string d := [3]string{"1", "2", "3"} // array,基本不用到 e := []int64{1, 2, 3} // slice f := map[string]int64{"a": 3, "b": 4} // map fmt.Printf("type:%T:%v\n", p, p) fmt.Printf("type:%T:%v\n", a, a) fmt.Printf("type:%T:%v\n", b, b) fmt.Printf("type:%T:%v\n", c, c) fmt.Printf("type:%T:%v\n", d, d) fmt.Printf("type:%T:%v\n", e, e) fmt.Printf("type:%T:%v\n", f, f)
輸出:
type:bool:true type:int:3 type:float64:6 type:string:hi type:[3]string:[1 2 3] type:[]int64:[1 2 3] type:map[string]int64:map[a:3 b:4]
數據類型基本有整數,浮點數,字符串,布爾值,數組,切片(slice) 和 字典(map) 。
bool
。int
(默認類型,通常視操做系統位數=int32或int64),int32
,int64
。float32
,float64
(默認類型,更大的精度)string
。沒聲明具體變量類型的時候,會自動識別類型,把整數認爲是int
類型,把帶小數點的認爲是float64
類型,如:
a := 3 // int b := 6.0 // float64
因此當你須要使用確切的int64
或float32
類型時,你須要這麼作:
var a int64 = 3 var b float32 = 6.0
Golang
有數組類型的提供,可是通常不使用,由於數組不可變長,當你把數組大小定義好了,就再也沒法變動大小。因此Golang
語言造出了可變長數組:切片(slice
),將數組的容量大小去掉就變成了切片。切片,能夠像切東西同樣。自動調整大小,能夠切一部分,或者把兩部分拼起來。
d := [3]string{"1", "2", "3"} // array,基本不用到 e := []int64{1, 2, 3} // slice
切片能夠像數組同樣按下標取值,放值,也能夠追加值:
// 切片放值 e[0] = 9 // 切片增長值 e = append(e, 3)
切片追加一個值3
進去須要使用append
關鍵字,而後將結果再賦給本身自己,這是Golang
語言與其餘語言最大的區別之一,實際切片底層有個固定大小的數組,當數組容量不夠時會生成一個新的更大的數組。
同時,由於平常開發中,咱們常常將兩個數據進行映射,相似於查字典同樣,先查字母,再翻頁。因此字典map
開發使用頻率極高,因此Golang
自動提供了這一數據類型,這是Golang
語言與其餘語言最大的區別之一。
字典存儲了一對對的鍵值:
// 增長map鍵值 f["f"] = 5 // 查找map鍵值 v, ok := f["f"] fmt.Println(v, ok) v, ok = f["ff"] fmt.Println(v, ok)
結構如map[string]int64
表示鍵爲字符串string
,值爲整數int64
,而後你能夠將f = 5
這種關係進行綁定,須要時能夠拿出鍵f
對應的值。
鍵值結構字典:map
使用前必須初始化,如:
m := map[string]int64{} m1 = make(map[string]int64)
若是不對字典進行初始化,做爲引用類型,它是一個nil
空引用,你使用空引用,往字典裏添加鍵值對,將會報錯。
而切片結構slice
不須要初始化,由於添加值時是使用append
操做,內部會自動初始化,如:
var ll []int64 fmt.Printf("%#v\n", ll) ll = append(ll, 1) fmt.Printf("%#v\n", ll)
打印:
[]int64(nil) []int64{1}
同時切片有如下特徵:
ll = append(ll, 2, 3, 4, 5, 6) fmt.Printf("%#v\n", ll) ll = append(ll, []int64{7, 8, 9}...) fmt.Printf("%#v\n", ll) fmt.Println(ll[0:2]) fmt.Println(ll[:2]) fmt.Println(ll[0:]) fmt.Println(ll[:])
內置語法append
能夠傳入多個值,將多個值追加進切片。而且能夠將另一個切片,如[]int64{7, 8, 9}...
,用三個點表示遍歷出裏面的值,把一個切片中的值追加進另一個切片。
在切片後面加三個點...
表示虛擬的建立若干變量,將切片裏面的值賦予這些變量,再將變量傳入函數。
咱們取切片的值,除了能夠經過下標取一個值,也能夠取範圍:[下標起始:下標截止(不包括取該下標的值)]
,如[0:2]
,表示取出下標爲0和1
的值,總共有兩個值,再好比[0:4]
,表示取出下標爲0,1,2,3
的值。若是下標取值,下標超出實際容量,將會報錯。
若是下標起始等於下標0
,那麼能夠省略,如[:2]
,若是下標截止省略,如[2:]
表示從下標2
開始,取後面全部的值。這個表示[:]
自己沒有做用,它就表示切片自己。
咱們能夠把常用的代碼片斷封裝成一個函數,方便複用:
// 函數,兩個數相加 func sum(a, b int64) int64 { return a + b }
Golang
定義函數使用的關鍵字是func
,後面帶着函數名sum(a, b int64) int64
,表示函數sum
傳入兩個int64
整數a
和b
,輸出值也是一個int64
整數。
使用時:
// 定義 int64 變量 var h, i int64 = 4, 6 // 使用函數 sum := sum(h, i) fmt.Printf("sum(h+i),h=%v,i=%v,%v\n", h, i, sum)
輸出:
sum(h+i),h=4,i=6,10
將函數外的變量h
,i
傳入函數sum
做爲參數,是一個值拷貝的過程,會拷貝h
和i
的數據到參數a
和b
,這兩個變量是函數sum
內的局部變量,兩個變量相加後返回求和結果。
就算函數裏面改了局部變量的值,函數外的變量仍是不變的,如:
package main import "fmt" func changeTwo(a, b int) { a = 6 b = 8 } func main() { a, b := 1, 2 fmt.Println(a, b) changeTwo(a, b) fmt.Println(a, b) }
輸出:
1 2 1 2
變量是有做用域的,做用域主要被約束在各級大括號{}
裏面,因此函數裏面的變量和函數體外的變量是沒有關係的,互相獨立。
咱們還能夠實現匿名的函數如:
input := 2 output := func(num int) int { num = num * 2 return num }(input) fmt.Println(output)
打印出:
4
原本函數在外部是這樣的:
func A(num int) int { num = num * 2 return num }
如今省略了函數名,定義後直接使用:
output := func(num int) int { num = num * 2 return num }(input)
input
是匿名函數的輸入參數,匿名函數返回的值會賦予output
。
我是陳星星,歡迎閱讀我親自寫的 數據結構和算法(Golang實現),文章首發於 閱讀更友好的GitBook。