一文帶你入門 Golang

小白要生髮發表於2020-11-29

go 語言特點

指令碼化的語法,容易上手。

靜態型別+編譯性,開發、執行效率都有保證

函式式 & 物件導向 兩種程式設計正規化,

原生支援併發程式設計支援,降低開發成本,維護成本,以及更好的相容性,效率。

劣勢:語法糖沒有 Python 和 Ruby 多。執行效率不及C,但已趕超C++,Java。第三方庫不多,就是輪子少(喜歡造輪子的可以加入golang輪子大軍)。

安裝

官方: golang.org/

官方映象站: golang.google.cn/

國內官方站點: go-zh.org/

Linux

golang.org/dl/ 下載最新Go語言二進位制包

wget https://dl.google.com/go/go1.13.15.linux-amd64.tar.gz

tar -C /usr/local -xzf go1.13.15.linux-amd64.tar.gz

export PATH=$PATH:/usr/local/go/bin

go version

Mac

$ brew install go

## 安裝制定版本
$ brew install go@1.13

Windows

訪問 官方映象站 下載 msi 安裝檔案。

環境變數

安裝好 go 後。執行go env就可以得到當前 go runtime 相關的環境變數了,這些變數可以透過

go env -w GO_ENV_NAME='new Value'

或是

$ env GO_ENV_NAME='new Value'

都能影響 go env

目錄結構

這裡講一下 $GOPATH 也就是 ~/go 下的目錄結果:

$  ls ~/go/
bin     #   go 可執行檔案目錄
pkg     #   go 歸檔檔案目錄
src     #   go 下載的程式碼包原始碼檔案,存放目錄

go命令

go run

go 自帶了 runtime,透過 go run file.go 就能執行程式程式碼了。

引數:

-a : 強制編譯相關程式碼,不論他們的原始碼是否有改變,編譯結果是否最新。
-n : 列印編譯過程中所需要的執行命令,但不真正執行它們
-p n : 並行編譯,n為並行數量
-v : 列出被編譯的程式碼包的名稱
-work : 顯示編譯時建立的的臨時目錄,並且不刪除。
-x : 列印編譯過程中所需執行的命令

go build

編譯出目標檔案。

類比 nodejs 就是, npm build, 類比 Java

go build main.go 編譯製定的 main.go 程式碼檔案

go build 執行命令就是將當前程式碼包作為程式碼包並編譯

引數:

-a 所有涉及、被引入的程式碼都會被重新編譯。

go install

編譯並安裝程式碼包或原始檔。

golang 安裝程式碼包動作就是將編譯結果移動到 pkg/平臺號編號/歸檔檔案。

如果是可執行檔案,則移動到當前工作去的 bin 目錄,或者 $GOBIN 目錄.

go get

從遠端程式碼倉庫上下載、安裝程式碼包。

遠端倉科可以是:gitmercurial(HG)svnbazaar

程式碼下載後會放到 $GOPATH/src 目錄中.

-d : 只執行下載動作,不執行安裝動作
-fix : 先執行`修正`動作,再進行編譯和安裝程式碼包。
-x : 展示命令執行過程
-u : 下載並更新程式碼包

修正: 指語法向上相容。

go 程式碼關鍵字

break         //退出迴圈
default     //選擇結構預設項(switch、select)
func         //定義函式
interface    //定義介面
select        //channel
case         //選擇結構標籤
chan         //定義channel
const         //常量
continue     //跳過本次迴圈
defer         //延遲執行內容(收尾工作)
go         //併發執行
map         //map型別
struct        //定義結構體
else         //選擇結構
goto         //跳轉語句
package     //包
switch        //選擇結構
fallthrough     //??
if         //選擇結構
range         //從slice、map等結構中取元素
type        //定義型別
for         //迴圈
import         //匯入包
return         //返回
var        //定義變數

標示符

append bool byte cap close complex complex64 complex128 uint16
copy false float32 float64 imag int int8 int16 uint32
int32 int64 iota len make new nil panic uint64
print println real recover string true uint uint8 uintptr

語言特色

不要求縮排,不要求末尾加分號——;,同一行程式碼中有多個表示式,需要用 分號 分割。沒有使用的變數,包,會導致報錯。

每個go原始檔開頭必須是package開頭,定義自己的包

一個目錄下,只能有一個包名

一個可執行的檔案必須要有 main() 函式

import 引入包

兩種引入風格

import "package1"
import "package2"
import (
    "package1"
    pa2 "package2"      // 包別名,別名為 pa2
    . "fmt"
    _ "mysql"
)

. "fmt" 方式引入包的化,使用fmt裡面的函式就可直接使用,不用帶 fmt 字首了

如果引入的包不使用,會報錯, 或者加個字首 _ 即可,這樣的下劃線會把引入的包的init函式執行一下。定義的變數不用,也會報錯。

包內初始化函式

定義 包內 初始化函式

func init() {

}

只匯入這個包部分,並執行init函式,由於匯入不全,所以在程式碼中就不能使用這個包了。

import _ "MyPackage" 

資料型別

序號 型別和描述
1 布林型 布林型的值只可以是常量 true 或者 false。一個簡單的例子:var b bool = true。
2 數字型別 整型 int 和浮點型 float32、float64,Go 語言支援整型和浮點型數字,並且支援複數,其中位的運算採用補碼。
3 字串型別: 字串就是一串固定長度的字元連線起來的字元序列。Go 的字串是由單個位元組連線起來的。Go 語言的字串的位元組使用 UTF-8 編碼標識 Unicode 文字。
4 派生型別: 包括: (a) 指標型別(Pointer) (b) 陣列型別 (c) 結構化型別(struct) (d) Channel 型別 (e) 函式型別 (f) 切片型別 (g) 介面型別(interface) (h) Map 型別

使用 int 時,根據當前作業系統來的,64位系統對應 int64, 32位作業系統,對應int32.

變數宣告

  • 變數宣告: var <變數名> [變數型別]
  • 變數賦值: <變數名> = <值,表示式,函式返回>
  • 變數宣告賦值:var <變數名> [變數型別] = <值,表示式,函式返回>
  • 變數宣告,型別推斷,並賦值 <變數名> := <值,表示式,函式返回>
分組宣告

var (
    i int
    foo float32
    name string
)

分組批次宣告、賦值
var a,b,c,d int = 1,2,3,4
a,b := 1,2

特殊變數 _

變數作用域

  • 函式內定義的變數稱為區域性變數
  • 函式外定義的變數稱為全域性變數
  • 全域性變數必須使用 var 宣告,區域性變數可省略

作用域可以分為以下四個型別:

  • 內建作用域:不需要自己宣告,所有的關鍵字和內建型別、函式都擁有全域性作用域
  • 包級作用域:必須函式外宣告,在該包內的所有檔案都可以訪問
  • 檔案級作用域:不需要宣告,匯入即可。一個檔案中透過import匯入的包名,只在該檔案內可用
  • 區域性作用域:在自己的語句塊內宣告,包括函式,for、if 等語句塊,或自定義的 {} 語句塊形成的作用域,只在自己的區域性作用域內可用

語句塊

語句塊是由花括弧({})所包含的一系列語句。

在 Go 中還有很多的隱式語句塊:

  • 主語句塊:包括所有原始碼,對應內建作用域
  • 包語句塊:包括該包中所有的原始碼(一個包可能會包括一個目錄下的多個檔案),對應包級作用域
  • 檔案語句塊:包括該檔案中的所有原始碼,對應檔案級作用域
  • for 、if、switch等語句本身也在它自身的隱式語句塊中,對應區域性作用域

型別轉換

  • 不存在隱式轉換,必須是顯示
  • 型別轉換必須是在兩種相容的型別之間
  • <變數名稱> [:]= <目標型別>( <需要轉換的變數名> )

型別轉換精度丟失

型別斷言

斷言,顧名思義就是果斷的去猜測一個未知的事物。在 go 語言中,interface{} 就是這個神秘的未知型別,其斷言操作就是用來判斷 interface{} 的型別。

    var foo interface{} = 22

    f, ok := foo.(int)
    if !ok {
        t.Log("Guess wrong ...")
    }
    t.Logf("The type is : %T", f)   

常量

  • 顯示 const idenfity [type] = value
  • 隱式 const identify = value () (無型別常量)

變數型別支援: bool, int, float, string

特殊常量 iota

運算

算術運算

運算子 描述 例項
+ 相加 A + B 輸出結果 30
- 相減 A - B 輸出結果 -10
* 相乘 A * B 輸出結果 200
/ 相除 B / A 輸出結果 2
% 求餘 B % A 輸出結果 0
++ 自增 A++ 輸出結果 11
自減 A– 輸出結果 9

關係運算

運算子 描述 例項
== 檢查兩個值是否相等,如果相等返回 True 否則返回 False。 (A == B) 為 False
!= 檢查兩個值是否不相等,如果不相等返回 True 否則返回 False。 (A != B) 為 True
> 檢查左邊值是否大於右邊值,如果是返回 True 否則返回 False。 (A > B) 為 False
< 檢查左邊值是否小於右邊值,如果是返回 True 否則返回 False。 (A < B) 為 True
>= 檢查左邊值是否大於等於右邊值,如果是返回 True 否則返回 False。 (A >= B) 為 False
<= 檢查左邊值是否小於等於右邊值,如果是返回 True 否則返回 False。 (A <= B) 為 True

邏輯運算

運算子 描述 例項
&& 邏輯 AND 運算子。 如果兩邊的運算元都是 True,則條件 True,否則為 False。 (A && B) 為 False
|| 邏輯 OR 運算子。 如果兩邊的運算元有一個 True,則條件 True,否則為 False。 (A || B) 為 True
! 邏輯 NOT 運算子。 如果條件為 True,則邏輯 NOT 條件 False,否則為 True。 !(A && B) 為 True

位運算

運算子 描述 例項
& 按位與運算子”&”是雙目運算子。 其功能是參與運算的兩數各對應的二進位相與。 (A & B) 結果為 12, 二進位制為 0000 1100
| 按位或運算子”|”是雙目運算子。 其功能是參與運算的兩數各對應的二進位相或 (A | B) 結果為 61, 二進位制為 0011 1101
^ 按位異或運算子”^”是雙目運算子。 其功能是參與運算的兩數各對應的二進位相異或,當兩對應的二進位相異時,結果為1。 (A ^ B) 結果為 49, 二進位制為 0011 0001
<< 左移運算子”<<”是雙目運算子。左移n位就是乘以2的n次方。 其功能把”<<”左邊的運算數的各二進位全部左移若干位,由”<<”右邊的數指定移動的位數,高位丟棄,低位補0。 A << 2 結果為 240 ,二進位制為 1111 0000
>> 右移運算子”>>”是雙目運算子。右移n位就是除以2的n次方。 其功能是把”>>”左邊的運算數的各二進位全部右移若干位,”>>”右邊的數指定移動的位數。 A >> 2 結果為 15 ,二進位制為 0000 1111

賦值運算

運算子 描述 例項
= 簡單的賦值運算子,將一個表示式的值賦給一個左值 C = A + B 將 A + B 表示式結果賦值給 C
+= 相加後再賦值 C += A 等於 C = C + A
-= 相減後再賦值 C -= A 等於 C = C - A
*= 相乘後再賦值 C *= A 等於 C = C * A
/= 相除後再賦值 C /= A 等於 C = C / A
%= 求餘後再賦值 C %= A 等於 C = C % A
<<= 左移後賦值 C <<= 2 等於 C = C << 2
>>= 右移後賦值 C >>= 2 等於 C = C >> 2
&= 按位與後賦值 C &= 2 等於 C = C & 2
^= 按位異或後賦值 C ^= 2 等於 C = C ^ 2
|= 按位或後賦值 C |= 2 等於 C = C | 2

優先順序

優先順序 運算子 功能
9 () [] -> . 字尾運算
8 ! *(指標) & ++ – +(正號) -(負號) 單目運算
7 * / % + - 算術運算,加減乘除
6 << >> 位運算
5 == != < <= > >= 邏輯運算、不等、等
4 & | ^ 按位 邏輯與、或
3 || && 邏輯或、與
2 = += -= *= 等等 賦值運算
1 , 逗號

一元賦值 這兩大運算子是 從右到左 關聯,其他都是 從左到右 關聯。

注意:優先順序 值越大則優先順序越高。為了方便理解、記憶,我對沒有嚴格按照優先順序製表,只是做了個大概!!
更詳細的

程式碼控制語句

if, else, else if

    var number int = 37
    if number += 4; 10 > number {
        fmt.Print("less than 10:", number)
    } else if 10 < number {
        number -= 2
        fmt.Print("greater 10:", number)
    } else {

    }

switch, select

package main

import (
    "fmt"
    "math/rand"
)

func main() {
    ia := []interface{}{byte(6), 'a', uint(10), int32(-4), "CC"}
    v := ia[rand.Intn(4)]

    // 值 switch
    switch v {
        case 'a' :
            fmt.Println("char: ", v)
        case 10 :
            fmt.Println("uint: ", v)
        case -4 :
            fmt.Println("int: ", v)
        case 0.1 :
            fallthrough
        caes "0.1"
            fmt.Println("float: ", v)
        default :
            fmt.Println("byte: ", v)
    }

    // 變數型別 switch
    switch interface{}(v).(type) {
    case string :
        fmt.Printf("Case A.")
    case byte :
        fmt.Printf("Case B.")
    case int :
        fmt.Printf("Case B.")
    default:
        fmt.Println("Unknown!")
    }
}

注意,go語言和其他語言不同的時,每個case程式碼末尾會自動加上break 操作, 如果你需要使用 fallthrough 來抵消預設的 break

select 用於管道

for

是的 golangforforforeachfor inwhile於一體。

do while 表示:golang你這麼繞,不優雅

package main

import (
    "fmt"
    "time"
)

func main() {
    map1 := map[int]string{1: "Golang", 2: "Java", 3: "Python", 4: "C"}

    n := 1
    for {   // 省略則預設是true
        if n > 3 {
            break;
        }
        fmt.Println("for true map item: ", map1[n])
        time.Sleep(1)
        n++
    }

    for i := 1; i < 4; i++ {
        fmt.Println("for i map item: ", map1[i])
    }

    for k,v := range map1 {
        fmt.Print(k, ":", v)
    }
}

goto, break, continue

goto 是跳過程式碼塊

package main

import (
    "fmt"
    "time"
)

func main() {
    code:
        fmt.Println("do some thing~")
        time.Sleep(1)

    goto code
}

break 跳出並結束迴圈

continue 跳過當前迴圈

雖然不能和PHP那樣 break 2 跳出多層, 單隻要有goto就能幹很多事了。

golang給 迴圈 就分配了一個 for,語句跳轉語句卻整了那麼多花樣

複合資料

內建方法 make & new

內建方法就是不需要引入包就能用的

make 可以建立 slice、map、chan,返回指標型別

  • slice 是可變長的陣列
  • map 是key-map 資料陣列
  • chan 是go獨有的 管道

一股c程式設計風格撲面而來, char *ptr = (char *)malloc(sizeof(char) * 5);

內建方法 new

記憶體置0,返回傳入型別的指標地址

package main
import fmt
import reflect

func main() {
    mSlice := make([]string, 3)
    mSlice[0] = "dog"
    mSlice[1] = "cat"
    mSlice[2] = "pig"
    fmt.Println("animals: ", mSlice)

    mMap := make(map[int]string)
    mMap[10] = "dog"
    mMap['2'] = "cat"
    fmt.Println(reflect.TypeOf(mMap))
    fmt.Println("animals :: ", mMap)


    nMap := new(map[int]string)
    fmt.Println(reflect.TypeOf(nMap))
}

append copy delete

slice可以使用copy,append 函式

delete 是專門用來刪除 map

  • append(src, ele) 追加元素
  • copy(dst, src) 把src元素賦值到dst上,
  • delete() 刪除元素

例子:

package main
import "fmt"

func main() {
    mSlice := make([]string, 3)
    mSlice[0] = "dog"
    mSlice[1] = "cat"
    mSlice[2] = "pig"
    fmt.Println("animals: ", mSlice)

    // append(mSlice, "id-3")   // 這樣寫會導致報錯: append(mSlice, "id-3") evaluated but not used
    mSlice = append(mSlice, "id-3")
    fmt.Println("animals update:", mSlice)
    fmt.Println("animals len :", len(mSlice))
    fmt.Println("animals cap:", cap(mSlice))

    // newSlice := make([]string)      // 這樣寫導致報錯:missing len argument to make([]string)
    // newSlice := make([]string, 2)       // 這樣寫會導致資料丟失2個,不會自動擴容
    newSlice := make([]string, 3)       // 不要多次定義初始化:no new variables on left side of :=
    copy(mSlice, newSlice)          // 這樣反向copy,會導致前面的幾個陣列元素被置為空
    // copy(newSlice, mSlice)
    fmt.Println("animals dst:", mSlice)
    fmt.Println("animals copy:", newSlice)

    delete(mMap, 50)
    fmt.Println(mMap)
}

panic & recover

異常處理

panic() 丟擲異常

recover() 獲取異常

報錯會導致程式程式碼中斷,不會再執行後續操作

例子:

package main

import "fmt"
import "errors"

func panicFunc() {
    defer func() {
        // recover()
        message := recover()    // 宣告瞭message 變數就需要使用哦,不然報錯
        fmt.Println("panice msg: ", message)

        switch message.(type) {
            case string:
            case error:
                fmt.Println("panice error msg: ", message)
            default:
        }
    }()
    // panic("報錯啦")
    panic(errors.New("I am error."))
}

func main() {

    panicFunc()
}

len & cap & close

len可以計算 string, array, slice, map, chan
cap 可以計算 slice, map, chan

  • len() 獲取陣列長度
  • cap() 獲取佔用空間分配
  • close() 用於關閉管道——chan

當宣告一個陣列時,go會預先分配一部分空間給當前陣列,獲取實際空間佔用大小,使用cap()

不用像PHP那樣,strlen(), count(), length 傻傻分不清楚了。

例子:

package main

import "fmt"

func main() {

    mSlice := make([]string, 3)
    mSlice[0] = "dog"
    mSlice[1] = "cat"
    mSlice[2] = "pig"
    fmt.Println("animals: ", mSlice)

    fmt.Println("animals update:", mSlice)
    fmt.Println("animals len :", len(mSlice))
    fmt.Println("animals cap:", cap(mSlice))

    mChan := make(chan int, 1)
    close(mChan)
    mChan <- 1      // 會導致報錯: panic: send on closed channel
}

defer

定一個當前方法關閉時,執行的程式碼, 壓棧設計,先宣告的後執行。

結構體

package main

import "fmt"

type Dog struct {
    ID int
    Name string
    Age int32
}

func main() {

    var dog Dog
    dog.ID = 1
    dog.Name = "haha"
    dog.Age = 3
    fmt.Println("print Dog Struct", dog)

    dog2 := Dog{ID:2, Name:"san", Age:4}
    fmt.Println("print Dog 2 Struct", dog2)

    dog3 := new(Dog)
    dog3.ID = 3
    dog3.Name = "Tom"
    dog3.Age = 5
    fmt.Println("print Dog 3 Struct", dog)
}

輸出

print Dog Struct {1 haha 3}
print Dog 2 Struct {2 san 4}
print Dog 3 Struct &{3 Tom 5}

屬性 & 函式

介面

/* define an interface */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   ...
   method_namen [return_type]
}

/* define a struct */
type struct_name struct {
   /* variables */
}

/* implement interface methods*/
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* method implementation */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* method implementation */
}

併發

指標

json

需要引入包 encoding/json, 兩個函式分別是 json.Marshal(), json.Unmarshal().

注意,最後一個是英文字母小寫的L,不是1

json 序列化

package main

import "fmt"
import "encoding/json"

type ServerInfo struct {
    SerName string
    SerIp   string
    SerPort uint16
}

func main() {
    server := new(ServerInfo)
    server.SerName = "http-nginx"
    server.SerIp = "127.0.0.1"
    server.SerPort = 8080
    re,err := json.Marshal(server)
    if nil != err {
        fmt.Println("error: ", err.Error())
    } else {
        fmt.Println("struct json bytes: ", re)
        fmt.Println("struct json string: ", string(re))
    }

    mServer := make(map[string]interface{})
    mServer["serverName"] = "apache2-http"
    mServer["serIp"] = "192.168.30.133"
    mServer["serPort"] = "3033"
    mRe,err := json.Marshal(mServer)
    if nil != err {
        fmt.Println("error: ", err.Error())
    } else {
        fmt.Println("map json string: ", string(mRe))
    }
}

輸出

struct json bytes:  [123 34 83 101 114 78 97 109 101 34 58 34 104 116 116 112 45 110 103 105 110 120 34 44 34 83 101 114 73 112 34 58 34 49 48 46 49 48 48 46 49 55 46 50 55 58 51 48 48 48 49 34 44 34 83 101 114 80 111 114 116 34 58 56 48 56 48 125]
struct json string:  {"SerName":"http-nginx","SerIp":"10.100.17.27:30001","SerPort":8080}
map json string:  {"serIp":"192.168.30.133","serPort":"3033","serverName":"apache2-http"}

ps: 我也不知道 10.100.17.27:30001 是怎麼回事

json 反序列化

可以使用 tag 來做 mapping,

package main

import "fmt"
import "encoding/json"

type ServerInfo struct {
    SerName string  `json:"name"`
    SerIp   string  `json:"ip"`
    SerPort uint16  `json:"port"`
}

func main() {
    // jsonStr := "{\"SerName\":\"http-nginx\",\"SerIp\":\"10.100.17.27:30001\",\"SerPort\":8080}"   \\ 雙引號注意轉義
    jsonStr := "{\"name\":\"http-nginx\",\"ip\":\"10.100.17.27:30001\",\"port\":8080}"

    sServer := new(ServerInfo)
    jsonBytes := []byte(jsonStr)
    uerr := json.Unmarshal(jsonBytes, &sServer)
    if nil != uerr {
        fmt.Println("error: ", err.Error())
    } else {
        fmt.Println("uns struct: ", sServer)
    }

    jsonStr3 := `{"serIp":"192.168.30.133","serPort":"3033","serverName":"apache2-http"}`   \\ 使用鍵盤1旁邊的 ` 符號包裹雙引號就不用轉義了

    uSer := make(map[string]interface{})
    uErr := json.Unmarshal([]byte(jsonStr3), &uSer)
    if nil != uErr {
        fmt.Println("error: ", uErr.Error())
    } else {
        fmt.Println("unmar map: ", uSer)
    }
}

輸出

uns struct:  &{http-nginx 10.100.17.27:30001 8080}
unmar map:  map[serIp:192.168.30.133 serPort:3033 serverName:apache2-http]

tag

tag 這個東東把,就是json的別名,感覺這個功能是go的特色,與encoding/json包緊密結合。

為什麼會有這個東西,我估計是這個和 go命名規則 有關,go命名規則,要求public的變數開頭要大寫,小寫開頭的變數是private的,所以,json中的變數就會影響一個介面體變數的訪問許可權,為了不像java那樣複雜,提供了方便的tag功能。

package main

import "fmt"
import "encoding/json"

type ServerInfo struct {
    SerName string  `json:"name"`
    SerIp   string  `json:"ip"`
    SerPort uint16  `json:"port"`
}

func main() {

    server := new(ServerInfo)
    server.SerName = "http-nginx"
    server.SerIp = "127.0.0.1"
    server.SerPort = 8080
    re,err := json.Marshal(server)
    if nil != err {
        fmt.Println("error: ", err.Error())
    } else {
        fmt.Println("struct json string: ", string(re))
    }
}

輸出

struct json string:  {"name":"http-nginx","ip":"10.100.17.27:30001","port":8080}
map json strin

go 特色語法

_

  • _ 變數

這就好比是Linux 裡的 /dev/null, 由於go語言要求宣告的變數必須被使用,返回的變數必須被接收,那麼真有個變數沒用但必須要接受怎麼辦呢,就把返回的引數給他。例如:

package main
import "fmt"

var pow = []int{1, 2, 4, 8, 16, 32, 64, 128}
func main() {
    for _, v := range pow {
        fmt.Printf("value is %d\n", v)
    }
}

這裡我們只要值,不要key的資訊,返回的key不能不收不是,但我也不像把它輸出出來,就讓 _ 來接收好了。

  • _ 包

引入包, 並不直接使用這個包,執行時執行一次它的 init() 函式,


import (
    _ "github.com/go-sql-driver/mysql"
    "github.com/jinzhu/gorm"
)

參考

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章