變量定義
變量定義文法
- 使用var關鍵字,可放在函數内,也可放在包内
// var + 變量名 + 資料類型(有預設值)
var a int
var b string = "string"
// 通過指派自動判斷類型
var c = true
// 集中定義
var (
x = 1
y = 2
)
- 使用:=定義變量,隻能在函數内使用
// := 用于賦初值
a, b := 1, 2
内建變量類型(builtin)
- bool, string
- (u)int, (u)int8, (u)int16, (u)int32, (u)int64, uintptr
- byte, rune
- float32, float64, complex64, complex128
強制類型轉換
Golang要求強制類型轉換,無隐式轉換
a, b := 3, 4
var c int
c = int(math.Sqrt(float64(a * a + b * b)))
常量
使用const關鍵字定義常量,const數值可作為各種類型使用。
// 不确定類型
const a = 3
var b float64
b = a
// 合并定義
const (
c = 1
d = 2
)
枚舉
const (
spring = 0
summer = 1
autumn = 2
winter = 3
)
// iota用在取值為0的const變量,後續變量依次加1
const (
spring = iota
summer
autumn
winter
)
變量定義要點
- 變量類型寫在變量名之後
- 編譯器可以推測變量類型
- Golang沒有char,使用rune
- 原生支援複數類型
分支
if
const filename = "abc.txt"
contents, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(contents)
}
// 可以在if條件中指派,指派變量的作用域在if語句中
if contents, err := ioutil.ReadFile(filename); err != nil {
fmt.Println(err)
} else {
fmt.Println(contents)
}
switch
switch後可以沒有表達式,case結束後自動break,通過fallthrough不使用自動break。
func grade(score int) {
grade := ""
switch {
case score < 0:
panic(fmt.Sprintf("Wrong score: %d\n", score))
case score < 60:
grade = "C"
case score < 80:
grade = "B"
case score < 90:
grade = "A"
case score < 100:
// 自動break,除非出現fallthrough
fallthrough
case score >= 100:
grade = "S"
}
fmt.Println(grade)
}
循環
for條件沒有括号,沒有while。
func convertToBin(n int) string {
result := ""
for ; n > 0; n /= 2 {
lsb := n % 2
result = strconv.Itoa(lsb) + result
}
return result
}
func printFile(filename string) {
file, err := os.Open(filename)
if err != nil {
panic(err)
}
scanner := bufio.NewScanner(file)
for scanner.Scan() {
fmt.Println(scanner.Text())
}
}
func forever() {
// while true
for {
fmt.Println("forever")
}
}
函數
- 函數聲明的順序為:關鍵字,函數名,參數,傳回類型,函數體。
- 函數允許有多個傳回值,并能在函數體内拿到傳回值。
- 函數可以作為另一個函數的參數
- 函數支援可變參數清單
func div(a, b int) (q, r int) {
// q = a / b
// r = a % b
// return
return a / b, a % b
}
// 使用_抛棄傳回值
a, _ := div(12, 7)
// 使用另一個函數作為參數
func apply(operation func(a, b int), a, b int) {
operation(a, b)
}
// 使用匿名函數
apply(func(a, b int) {
fmt.Println(a, b)
}, 1, 2)
// 可變參數清單
func sum(numbers ...int) int {
s := 0
for i := range numbers {
s += numbers[i]
}
return s
}
指針
Golang隻有值傳遞一種方式。
// 使用指針操作
func change(pa *int) {
*pa++
}
a := 5
change(&a)
func swap(x, y *int) {
*x, *y = *y, *x
}
數組
數組長度寫在類型之前,[10]int和[20]int是不同的類型。數組是值複制。
var arr1 [5]int
arr2 := [3]int{1, 3, 5}
arr3 := [...]int{2, 4, 6}
var arr4 [4][5]int
周遊數組
for i := 0; i < len(arr3); i++ {
fmt.Println(arr3[i])
}
for i := range arr3 {
fmt.Println(arr3[i])
}
// 按下标和值周遊
for i, v := range arr3 {
fmt.Println(i, v)
}
切片
數組切片相當于資料的一個視圖,可以通過切片改變數組的值。
func updateSlice(s []int) {
s[0] = 100
}
func main() {
array := [...]int{0, 1, 2, 3, 4, 5, 6, 7}
s1 := array[2:6]
s2 := array[2:]
s3 := array[:6]
s4 := array[:]
fmt.Println(s1, s2, s3, s4) // [2 3 4 5] [2 3 4 5 6 7] [0 1 2 3 4 5] [0 1 2 3 4 5 6 7]
updateSlice(s2)
fmt.Println(s2) // [100 3 4 5 6 7]
updateSlice(s3)
fmt.Println(s3) // [100 1 100 3 4 5]
// 切片擴充
s5 := s3[6:8]
fmt.Println(s5)
// 添加元素
s6 := append(s5, 8)
fmt.Println(s6, array)
fmt.Println(array[:8])
// 通過make建立切片
// 長度為10的切片
x := make([]int, 10)
// 長度為10,容量為16的切片
y := make([]int, 10, 16)
// 拷貝數組元素
copy(x, y)
// 截取數組元素
z := append(x[:2], x[3:]...)
}
切片底層維護了一個數組,ptr指向切片的首個元素,len表示切片的長度,cap表示底層數組的從ptr到最末的元素數量。切片可以向後擴充,不可向前擴充。切片添加元素如果會超過底層數組的cap,Golang會配置設定更大的底層數組(原容量*2進行擴充),由于值傳遞的關系,必須接收append方法的傳回值。
map
通過map[K]v的形式定義map。除了slice、map、function的内建類型和struct類型都可以作為map的key。
m := map[string]string{
"name": "wch",
"age": "23",
}
m2 := make(map[string]int)
// 無序周遊map
for k, v := range m {
fmt.Println(k, v)
}
// map讀值
name := m["name"]
// 判斷key是否存在
grade, exist := m["grade"]
// 删除
delete(m, "name")
rune
Golang的rune相當于java的char,中文字元在Golang中占3個位元組,使用 utf8.RuneCountInString 擷取字元數,用 len 擷取位元組長度,使用 []byte 擷取位元組。
s := "學習Golang!"
fmt.Println(s)
// ch是rune類型,中文字元占3個位元組
for i, ch := range s {
fmt.Printf("(%d %X) ", i, ch)
}
fmt.Println()
fmt.Println("rune count in string:", utf8.RuneCountInString(s))
bytes := []byte(s)
for len(bytes) > 0 {
ch, size := utf8.DecodeRune(bytes)
bytes = bytes[size:]
fmt.Printf("size= %d, rune=%c\n", size, ch)
}
for i, ch := range []rune(s) {
fmt.Printf("(%d %c)", i, ch)
}
fmt.Println()
面向對象
Golang僅支援封裝,不支援繼承和多态,沒有class,隻有struct,通過struct來定義對象。
type treeNode struct {
value int
left, right *treeNode
}
func createNode(value int) *treeNode {
return &treeNode{value: value}
}
func main() {
// 通過多種方式聲明對象
var root treeNode
root = treeNode{value: 0}
root.left = &treeNode{}
root.right = &treeNode{1, nil, nil}
root.right.left = new(treeNode)
root.right.right = createNode(3)
}
Golang支援顯式定義方法接收者,值/指針接收者均可接收值/指針。當需要改變内容或結構過大時需要使用指針接收者,值接收者是Golang特有的
// 指定方法接收者
func (node treeNode) print() {
fmt.Println(node.value)
}
func (node *treeNode) setValue(value int) {
node.value = value
}
func main() {
node := treeNode{}
node.setValue(100)
node.print()
pNode := &node
pNode.setValue(101)
pNode.print()
}
封裝
Golang的可見性(針對包)使用首字母來控制,首字母大寫表示public,首字母小寫表示private。每個目錄都是一個包,main包包含可執行入口。為結構定義的方法必須放在同一個包内,可以是不同的檔案。Golang提供了兩種擴充系統類型和已封裝類型的方法:
- 定義别名:通過type為已有類型定義一個别名,針對新的結構體進行擴充。
type Queue []int
func (q *Queue) Push(v int) {
*q = append(*q, v)
}
func (q *Queue) Pop() int {
head := (*q)[0]
*q = (*q)[1:]
return head
}
func (q *Queue) IsEmpty() bool {
return len(*q) == 0
}
- 使用組合:定義新的結構體,其中一個成員是需要擴充的類型的指針。
type EnhanceNode struct {
node *Node
}
func (enhanceNode *EnhanceNode) PostOrder() {
if nil == enhanceNode || nil == enhanceNode.node {
return
}
left := EnhanceNode{enhanceNode.node.Left}
right := EnhanceNode{enhanceNode.node.Right}
left.PostOrder()
right.PostOrder()
enhanceNode.PostOrder()
}
GOROOT、GOPATH、go get
GOROOT一般為下載下傳的sdk路徑,GOPATH為使用者可定義的路徑,使用者源碼和通過go get指令下載下傳的第三方庫在GOPATH的src目錄下。
gopm是go get的鏡像工具,通過 go get -g -v github.com/gpmgo/gopm 下載下傳gopm。
// 下載下傳
gopm get -g -v Golang.org/x/tools/cmd/goimports
// 更新
gopm get -g -v -u Golang.org/x/tools/cmd/goimports
// 安裝到GOPATH的bin目錄
go install Golang.org/x/tools/cmd/goimports
接口
接口的實作是隐式的,隻要實作接口的方法。
duck typing
“像鴨子走路,像鴨子叫(長得像鴨子),那麼就是鴨子”,意為描述事物的外部行為而非内部結構。嚴格說go屬于結構化類型系統,類似duck typing。
接口變量裡有什麼
- 接口自帶指針
- 接口變量同樣采用值傳遞,幾乎不需要使用接口的指針
- 指針接收者隻能以指針的方式使用,值接收者都可以
- interface{}可以代表任何類型
- 定義接口
type Retriever interface {
Get(url string) string
}
func download(r Retriever) string {
return r.Get("http://www.baidu.com")
}
- 實作接口
type Retriever struct {
UserAgent string
Timeout time.Duration
}
func (r *Retriever) Get(url string) string {
resp, err := http.Get(url)
if nil != err {
panic(err)
}
result, err := httputil.DumpResponse(resp, true)
resp.Body.Close()
if nil != err {
panic(err)
}
return string(result)
}
- 調用接口
var r Retriever
r = &real.Retriever{
UserAgent: "Mozilla/5.0",
Timeout: time.Minute,
}
fmt.Println(download(r))
// Type Assertion
if retriever, ok := r.(*real.Retriever); ok {
fmt.Printf("%T %v\n", retriever, retriever)
}
- 接口組合
type HttpExecute interface {
Retriever
Poster
}
函數式程式設計
- 函數是一等公民:參數,變量,傳回值都可以是函數。
- 高階函數:函數的參數可以是函數
- 函數->閉包
func adder() func(int) int {
sum := 0
return func(i int) int {
sum += i
return sum
}
}
func main() {
// f中不僅是一個函數,還有對變量sum的引用
f := adder()
for i := 0; i < 10; i++ {
fmt.Println(f(i))
}
}
錯誤處理和資源管理
defer
用于指定在函數結束之前執行,可以用于關閉檔案、釋放鎖等。
func tryDefer() {
// 在函數借結束之前執行
defer fmt.Println("execute last...")
defer fmt.Println("execute before last...")
fmt.Println("execute")
}
panic
- 停止目前函數執行
- 一直向上傳回,執行每一層的defer
- 如果沒有遇見recover,程式退出
recover
- 僅在defer調用中使用
- 擷取panic的值
- 如果無法處理,重新panic
func tryRecover() {
defer func() {
r := recover()
if err, ok := r.(error); ok {
fmt.Println("error:", err)
} else {
panic(fmt.Sprintf("unknown error: %v", r))
}
}()
panic(errors.New("this is an error"))
}
測試
測試格式
- 測試檔案與待測試代碼放在同一目錄下
- 測試檔案名以 _test.go 結尾
- 測試函數名為 TestXxx 或 Test_xxx 的格式,使用其它類型測試,如性能測試則函數名為BenchmarkXxx 或 ```Benchmark_xxx`` 的格式
傳統測試
- 傳統測試資料與邏輯混在一起
- 傳統測試的出錯資訊不明确
- 傳統測試一旦出錯測試即全部結束
表格驅動測試
- 測試代碼
func calcTriangle(a, b int) int {
return int(math.Sqrt(float64(a*a + b*b)))
}
-
表格驅動測試
分離了測試資料和測試邏輯,明确了出錯資訊,可以部分失敗。
func TestCalcTriangle(t *testing.T) {
tests := []struct{ a, b, c int }{
{3, 4, 5},
{5, 12, 13},
{8, 15, 18},
}
for _, tt := range tests {
if actual := calcTriangle(tt.a, tt.b); actual != tt.c {
t.Errorf("calcTriangle(%d %d); got %d; expected %d", tt.a, tt.b, actual, tt.c)
}
}
}
性能測試
執行b.N次,由Golang決定具體次數,測試完成後在控制台列印執行次數和平均執行時間。
func BenchmarkTriangle(b *testing.B) {
x, y, z := 8, 15, 17
for i := 0; i < b.N; i++ {
actual := calcTriangle(x, y)
if actual != z {
b.Errorf("calcTriangle(%d %d); got %d; expected %d", x, y, actual, z)
}
}
}
測試指令行工具
# 測試指令
go test
# 代碼覆寫率測試
go test -cover
# 生成代碼測試覆寫率檔案
go test -coverprofile c.out
# 代碼覆寫率測試html
go tool cover -html=c.out
# 性能測試
go test -bench .
# 生成性能報告
go test -bench . -cpuprofile cpu.out
文檔
Golang提供 go doc 指令檢視由注釋組成的文檔,通過 godoc -http :6060 指令在本地6060端口生成html文檔。在test檔案中建立名為 ExampleXxx_xxx 的函數,通過特定格式生成代碼執行個體文檔。
func ExampleQueue_Pop() {
queue := Queue{}
queue.Push(1)
fmt.Println(queue.Pop())
// Output:
// 1
}
go routine
協程Coroutine
- 輕量級“線程”
- 非搶占式多任務處理,由協程主動交出控制權
- 編譯器/解釋器/虛拟機層面的多任務
- 多個協程可能在一個或多個線程上運作
goroutine定義
- 調用任何函數前隻需加上 go 關鍵字就可以交給排程器執行
- 不需要在定義時區分是否為異步函數
- 排程器會在合适的點進行切換(I/O、select、channel、等待鎖、函數調用等),或手動調用 runtime.Gosched() 進行切換
- 使用race來檢測資料通路沖突, go run -race xxx.go
channel
相對于通過共享記憶體來通信,channel通過通信來共享記憶體。
- chanel可以作為參數傳遞
- 可以設定channel的buffer
- 發送方負責關閉channel,接收方通過可以通過 range 判斷channel是否傳遞完畢
使用select進行排程
select可以用來監聽IO操作,可以同時監聽多個channel的消息狀态。
連結:https://www.jianshu.com/p/ee509e86dc56