一、第一個go程序
package main import ( "fmt" ) func main(){ fmt.Println("hello world") }
對於代碼的解釋
如果是為了將代碼編譯成一個可執行程序,那么package必須是main
如果是為了將代碼編譯成庫,那么package則沒有限制
go中所有的代碼都應該隸屬一個包
fmt 是go的一個系統庫
fmt.println()則可以打印輸出
如果想要運行程序:go run 程序名
在一個可執行程序只有一個main函數
關於注釋
單行注釋://
多行注釋:/* */
二、go語言初識
關於定義一個變量
var 變量名 變量類型
變量名 = 值
這里需要注意:go語言中定義的變量必須被用到,否則會報錯
同時定義變量和賦值可以一步完成通過: 變量名 := 值
定義一個函數
如果我們要定義一個函數,我們先看下面求和的例子:
func add(a int,b int) int { var sum int sum = a + b return sum }
這里我們需要知道,下面這種格式是被嚴格要求的沒包括第一行的大括號不能放到下一行
func 函數名(參數1 參數1類型,參數2 參數2類型) 返回值類型 {
}
三、golang語言特性
垃圾回收
內存自動回收,不需要開發人員管理內存
開發人員專注業務實現
只需要new分配內存,不需要釋放
天然高並發
- 從語言曾元支持並發,非常簡單
- goroute,輕量級線程,創建成千上萬goroute成為可能
- 基於CSP模型實現
關於高並發的一個簡單演示:
package main import ( "fmt" "time" ) func test_print(a int){ fmt.Println(a) } func main(){ for i:= 0;i < 100; i ++ { go test_print(i) } time.Sleep(time.Second) }
在實現高並發的時候只需要在調用的函數前面加上go,就表示開啟了並發
如果在for循環的外面不加上time.Sleep(time.Second),就會發現會少打印了,這是因為當主程序運行完之后,並不會等待線程,所以程序直接終止
channel管道
類似linux中的pipe
多個goroute之間通過channel進行通信
支持任何類型
package main import ( "fmt" ) func test_pipe(){ pipe := make(chan int,3) pipe <- 1 pipe <- 2 pipe <- 3 fmt.Println(len(pipe)) } func main(){ test_pipe() }
上述代碼的解釋:
pipe := make(chan int,3) 這里是定義一個管道pipe,go是強類型語言,所以這里聲明一個管道變量pipe需要通過有三個參數,chan表示是管道,int表示管道類型,3表示管道容量
通過len(pipe)可以查看管道的的長度
如果想要從管道里獲取數據
t1 :=<- pipe 這個寫法等同於
var t1 int
t1 = <- pipe
fmt.Println(t1)
管道遵循的原則是先進先出,所以第一個獲取的值是1
小結:如果想要給管道放入值:定義的管道pipe < - 要存入的內容
如果想要從管道中獲取值:變量名 =<- 定義的管道pipe
這里強調一下go中package包的概念,一個包里的變量,在這個包里是都可以訪問,但是在包之外也是有權限限制是否可以訪問到,如果一個變量在一個包里是大寫的,在其他包里就可以訪問到,如果是小寫的其他包里則訪問不到。類似其他語言中的public和private
多返回值
一個函數可以返回多個值
package main import "fmt" func calc(a int,b int) (int,int ){ sum := a + b avg := sum / 2 return sum,avg } func main(){ sum,avg := calc(100,200) fmt.Println(sum,avg) }
關於需要傳入多個參數的時候是用括號括起來單個的情況下一般不用括號括起來,直接int,而這里是返回兩個則需要 (int,int)表示返回兩個整數類型值
如果有多個返回值,但是我只想返回一個值,是通過下划線方式實現,則上述代碼改為:
func main(){ sum,_ := calc(100,200) fmt.Println(sum) }
四、包的概念
- 和python一樣,把相同功能的代碼放到一個目錄,稱之為包
- 包可以被其他包引用
- main包是用來生成可執行文件,每個程序只有一個main包
- 包的主要用途是提高代碼的課復用性
關於main包中的main函數,go程序經過編譯之后,運行該程序,會將編譯好的二進制文件加載到內存中,會首先調用main函數,所以main函數是程序的入口函數,即必須有package main
關於包,是我們可以把一些常用的功能封裝到包中,這個時候包中的每個go文件的開頭則不需要package main,而是package 自定義名字 這個自定義名字是根據這個包的功能進行命名
go源碼按package進行組織,並且package要放到非注釋的第一行
一個程序只有一個main包,一個包中只能有一個main函數,不能重復定義
main函數是程序的執行入口
沒有被引用的變量,編譯的時候會報錯
go的目錄規范
這里舉一個簡單的例子:如果我們在建立一個go_project目錄,通常在這個目錄下我們會創建如下目錄
src 存放不同的項目代碼
bin 存放編譯后的可執行程序
vender 存放引用的第三方庫
pgk 存放靜態庫
我們的go環境變量中的GOPATH一般會設置為:
(我這里是路徑是/users/zhaofan/go_project)
export GOPATH=/users/zhaofan/go_project
go的編譯
如果我們寫好了go的代碼文件,我們如果測試運行可以通過:
go run 快速執行go文件
go build 編譯程序,生成二進制文件
go install 安裝可執行文件到bin目錄下
基本命令:
go test執行單元測試或壓力測試
go env 顯示go相關的環境變量
go fmt 格式化源代碼
我們通過下面例子理解這個編譯命令的使用:
例子一
我在/users/zhaofan/go_project/src/go_dev/day01/hello目錄下寫了一個hello程序
現在把這個hello程序進行編譯
我們在go_project目錄下執行的編譯命令,如果不指定編譯生成的文件會直接將編譯文件生成在當前目錄即go_project目錄下
這里需要解釋的是go build 后面的路徑go build go_dev/day01/hello
我們從目錄結構可以看出,go_dev的上一級目錄src目錄並沒有寫,這是因為go編譯的時候,會自動去GOPATH下的src目錄里去找,所以這里是不需要寫,同時編譯的路徑的最后我們只寫到hello目錄而不是hello.go文件
例子二
我們在/users/zhaofan/go_project/src/go_dev/day01/ 目錄下建立一個goroute目錄
並在goroute目錄下建立兩個go文件,main.go和goroute.go文件
main.go文件的代碼為:
package main import ( "time" ) func main(){ for i := 0; i < 100; i++ { go test_goroute(i) } time.Sleep(time.Second) }
goroute.go文件的代碼為:
package main import "fmt" func test_goroute(a int){ fmt.Println(a) }
這樣我們編譯的時候只需要在go_project目錄下執行:
go build go_dev/day01/goroute
這樣就會再go_project目錄下生成一個可執行文件goroute
例子3
還是在/users/zhaofan/go_project/src/go_dev/day01/下建立一個goroute_test目錄
在goroute_test目錄下建立calc目錄和main目錄
同時在calc下建立一個sum.go文件,在main目錄下建立一個main.go文件
sum.go文件代碼如下:
package calc func Add(a int,b int,c chan int){ sum := a + b c <- sum }
這里有個地方需要注意這里我們的sum.go是作為包寫的,所以我們開頭是:package calc,即package+sum.go的所在上級目錄,並且是sum中定義的函數名首字母要大些(這個是必須的)這里其實是因為
我們定義的包都是要被外部的其他包調用,即我們這里定義的sum.go是要被其他包調用,這個時候只有首字母大寫才能被其他包調用到
main.go文件代碼如下:
package main import ( "fmt" "go_dev/day01/goroute_test/calc" ) func main(){ pipe := make(chan int,1) calc.Add(100,200,pipe) res :=<- pipe fmt.Println(res) }
這次我們編譯的時候指定編譯文件生成的目錄路徑,命令如下:
go build -o bin/goroute_test go_dev/day01/goroute_test/main
關於單元測試例子:
單元測試的代碼文件的名字格式必須是:*_test.go
例如我要寫關於calc.go文件的單元測試
新建一個文件命名為:calc_test.go
這里需要知道的是開頭的calc的名字並不是強制的,但是為了方便測試哪個代碼文件,開頭就以那個文件開頭,下面是一個例子代碼:
1 package calc 2 3 import ( 4 "testing" 5 ) 6 func TestAdd(t *testing.T){ 7 var sum int 8 sum = Add(5,6) 9 if sum != 11{ 10 t.Fatalf("add is not right,sum:%v expected:11",sum) 11 } 12 t.Logf("add is Ok") 13 }
在代碼中我們定義函數時候函數的名字也需要以Test開頭
上述測試文件執行結果:
1 bogon:calc zhaofan$ go test 2 PASS 3 ok go_dev/01/calc 0.007s 4 bogon:calc zhaofan$ go test -v 5 === RUN TestAdd 6 --- PASS: TestAdd (0.00s) 7 calc_test.go:12: add is Ok 8 PASS 9 ok go_dev/01/calc 0.007s 10 bogon:calc zhaofan$
Go的結構開發規范
好的代碼規范是非常重要的,這樣當你看別人代碼或者別人看你的代碼的時候就能很清楚的明白,下面是結構規范:
// 當前程序的包名 package main //導入其他的包 import "fmt" //常量的定義 const PI=3.14 //全局變量的聲明和賦值 var name = "gopher" //一般類型聲明 type newType int //結構的聲明 type gopher struct{} //接口的聲明 type golang interface{} //由main函數作為程序入口點啟動 func main(){ fmt.Println("Hello world! 你好世界") }