golang教程

環境搭建

Golang在Mac OS上的環境配置

使用Visual Studio Code輔助Go源碼編寫

VS Code折騰記 - (2) 快捷鍵大全,沒有更全

語法

  1. public的變量必須以大寫字母開頭,private變量則以小寫字母開頭
  2. Go語言對{ }應該怎么寫進行了強制
if express{
    ...   
}
  1. Go 語言首創的錯誤處理規范:
f, err := os.Open(filename) if err != nil {
        log.Println("Open file failed:", err)
return }
defer f.Close()
... // 操作已經打開的f文件

這里有兩個關鍵點。

  • 其一是defer關鍵字。defer語句的含義是不管程序是否出現異常,均 在函數退出時自動執行相關代碼。
  • 其二是Go語言的函數允許返回多個值。

大多數函數 的最后一個返回值會為error類型,以在錯誤情況下返回詳細信息。error類型只是一個系統內 置的interface,如下:

type error interface {
    Error() string
}
  1. Go語言支持類、類成員方法、類的組合,但反對繼承,反對虛函數(virtual function) 和虛函數重載。確切地說,Go也提供了繼承,只不過是采用了組合的文法來提供:
type Foo struct { 
    Base
    ... 
}
func (foo *Foo) Bar() { 
    ...
}
  1. Go語言也放棄了構造函數(constructor)和析構函數(destructor)。
  2. Go語言送上了一份非常棒的禮物:接口(interface)。

Go語言中的接口與其他語言最大的一點區別是它的非侵入性。在Go語言中,實現類的時候無需從接口派生,具體代碼如下:

type Foo struct { // Go 文法 
    ...
}
var foo IFoo = new(Foo)

只要Foo實現了接口IFoo要求的所有方法,就實現了該接口,可以進行賦值。

特點:

  • 其一,Go語言的標準庫再也不需要繪制類庫的繼承樹圖。
  • 其二,不用再糾結接口需要拆得多細才合理。
  • 其三,不用為了實現一個接口而專門導入一個包,而目的僅僅是引用其中的某個接口的定義。

在Go語言中,只要兩個接口擁有相同的方法列表,那么它們就是等同的,可以相互賦值,如對 于以下兩個接口,第一個接口:

package one
type ReadWriter interface {
    Read(buf [] byte) (n int, err error) 
    Write(buf [] byte) (n int, err error)
}
//第二個接口:
package two
type IStream interface {
    Write(buf [] byte) (n int, err error) 
    Read(buf [] byte) (n int, err error)
}

在Go語言中,這兩個接口實際上并無區別,因為:

  • 任何實現了one.ReadWriter接口的類,均實現了two.IStream;
  • 任何one.ReadWriter接口對象可賦值給two.IStream,反之亦然;
  • 在任何地方使用one.ReadWriter接口,與使用two.IStream并無差異。
  1. 函數多返回值
//返回多個參數
func getName() (firstName, middleName, lastName, nickName string) {
    return "May", "M", "Chen", "Babe"
}

//靈活賦值
func getName2() (firstName, middleName, lastName, nickName string) {
    firstName = "May"
    middleName = "M"
    lastName = "Chen"
    nickName = "Babe"
    return
}

//賦值
firstName, middleName, lastName, nickName := getName()

并不是每一個返回值都必須賦值,沒有被明確賦值的返回值將保持默認的空值。

如果開發者只對該函數其中的某幾個返回值感興趣的話,也可以直接用下劃線作為占位符來 忽略其他不關心的返回值。

 _, _, lastName, _ := getName()
  1. Go語言引入了3個關鍵字用于標準的錯誤處理流程,這3個關鍵字分別為defer、panic和 recover。

  2. 匿名函數和閉包。

在Go語言中,所有的函數也是值類型,可以作為參數傳遞。Go語言支持常規的匿名函數和 閉包,比如下列代碼就定義了一個名為f的匿名函數,開發者可以隨意對該匿名函數變量進行傳 遞和調用:

f := func(x, y int) int { 
    return x + y
}
  1. 類型和接口。Go語言的類型定義非常接近于C語言中的結構(struct),甚至直接沿用了struct關鍵字。
type Bird struct { 
    ...
}

func (b *Bird) Fly() { 
    // 以鳥的方式飛行
}


type IFly interface { 
    Fly()
}

func main() {
    var fly IFly = new(Bird) fly.Fly()
}

11.并發編程。 Go語言引入了goroutine概念,它使得并發編程變得非常簡單。

通過在函數調用前使用關鍵字go,我們即可讓該函數以goroutine方式執行。

Go語言通過系統的線程來多路派遣這些函數的執行,使得 每個用go關鍵字執行的函數可以運行成為一個單位協程。當一個協程阻塞的時候,調度器就會自 動把其他協程安排到另外的線程中去執行,從而實現了程序無等待并行化運行。

Go語言實現了CSP(通信順序進程,Communicating Sequential Process)模型來作為goroutine 間的推薦通信方式。

在CSP模型中,一個并發系統由若干并行運行的順序進程組成,每個進程不 能對其他進程的變量賦值。進程之間只能通過一對通信原語實現協作。Go語言用channel(通道) 這個概念來輕巧地實現了CSP模型。

另外,由于一個進程內創建的所有goroutine運行在同一個內存地址空間中,因此如果不同的 goroutine不得不去訪問共享的內存變量,訪問前應該先獲取相應的讀寫鎖。Go語言標準庫中的 sync包提供了完備的讀寫鎖功能。

package main

import "fmt"

func sum(values [] int, resultChan chan int) { 
    sum := 0
    for _, value := range values { 
        sum += value
    }
    resultChan <- sum // 將計算結果發送到channel中 
}

func main() {
    values := [] int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
    resultChan := make(chan int, 2)
    go sum(values[:len(values)/2], resultChan)
    go sum(values[len(values)/2:], resultChan)
    sum1, sum2 := <-resultChan, <-resultChan // 接收結果
    fmt.Println("Result:", sum1, sum2, sum1 + sum2)
}

12.反射。

Go語言的反射實現了反射的大部分功能,但沒有像Java語言那樣內置類型工廠,故而無法做 到像Java那樣通過類型字符串創建對象實例。

反射最常見的使用場景是做對象的序列化(serialization,有時候也叫Marshal & Unmarshal)。

package main
import (
    "fmt"
    "reflect"
)
type Bird struct {
    Name string
    LifeExpectance int
}

func (b *Bird) Fly() { 
    fmt.Println("I am flying...")
}

func main() {
    sparrow := &Bird{"Sparrow", 3}
    s := reflect.ValueOf(sparrow).Elem() typeOfT := s.Type()
    for i := 0; i < s.NumField(); i++ {
        f := s.Field(i)
        fmt.Printf("%d: %s %s = %v\n", i, typeOfT.Field(i).Name,f.Type(),f.Interface())
    }
}
  1. 語言的交互性。

由于Go語言與C語言之間的天生聯系,Go語言的設計者們自然不會忽略如何重用現有C模塊 的這個問題,這個功能直接被命名為Cgo。Cgo既是語言特性,同時也是一個工具的名稱

Cgo的用法非常簡單,比如下面例子就可以實現在Go中調用C語言標準庫的puts函數。

package main
/*
#include <stdio.h> 
#include <stdlib.h>
*/
import "C"
import "unsafe"
func main() {
    cstr := C.CString("Hello, world")
    C.puts(cstr) 
    C.free(unsafe.Pointer(cstr))
}

注意:使用C.puts,C.free需要include stdlib.h

開始

1.第一個 Go 程序

  • 每個Go源代碼文件的開頭都是一個package聲明,表示該Go代碼所屬的包。包是Go語言里 最基本的分發單位,也是工程管理中依賴關系的體現。
  • 要生成Go可執行程序,必須建立一個名 字為main的包,并且在該包中包含一個叫main()的函數(該函數是Go可執行程序的執行起點)。
  • Go語言的main()函數不能帶參數,也不能定義返回值。命令行傳入的參數在os.Args變量 中保存。如果需要支持命令行開關,可使用flag包。
  • 不得包含在源代碼文件中沒有用到的包,否則Go編譯器會報編譯錯誤。
  • 所有Go函數(包括在對象編程中會提到的類型成員函數)以關鍵字func開頭。

一個常規的 函數定義包含以下部分:

func 函數名(參數列表)(返回值列表) { 
    // 函數體
}
  • Go支持多個返回值。

并不是所有返回值都必須賦值。在函數返回時沒有被明確賦值的返回值都會被設置為默認 值,比如result會被設為0.0,err會被設為nil。

  • Go程序的代碼注釋與C++保持一致,即同時支持以下兩種用法:
/*
塊注釋
*/
// 行注釋
  • Go代碼里沒有出現分號。Go 程序并不要求開發者在每個語句后面加上分號表示語句結束
package main
import "fmt"http:// 我們需要使用fmt包中的Println()函數
func main() {
    fmt.Println("Hello, world. 你好,世界!")
}
  1. 編譯環境準備

安裝包的下載地址為http://code.google.com/p/go/downloads/list

在*nix環境中,Go默認會被安裝到/usr/local/go目錄中。安裝包在安裝完成后會自動添加執行文件目錄到系統路徑中。

安裝完成后,請重新啟動命令行程序,然后運行以下命令以驗證Go是否已經正確安裝:

$ go version
go version go1

如果提 示找不到go命令,可以通過手動添加/usr/local/go/bin到PATH環境變量來解決。

  1. 編譯程序

假設之前介紹的Hello, world代碼被保存為了hello.go,并位于~/goyard目錄下,那么可以用以 下命令行編譯并直接運行該程序:

$ cd ~/goyard
$ go run hello.go # 直接運行 
Hello, world. 你好,世界!

使用這個命令,會將編譯、鏈接和運行3個步驟合并為一步,運行完后在當前目錄下也看不 到任何中間文件和最終的可執行文件。如果要只生成編譯結果而不自動運行,我們也可以使用 Go 命令行工具的build命令:

$ cd ~/goyard
$ go build hello.go
$ ./hello
Hello, world. 你好,世界!
  1. 工程管理

例子:帶尖括號的名字表示其為目錄。xxx_test.go表示的是一個對于xxx.go的單元 測試,這也是Go工程里的命名規則。

<calcproj> ├─<src>
├─<calc> ├─calc.go
├─<simplemath> ├─add.go
├─add_test.go ├─sqrt.go ├─sqrt_test.go
├─<bin> ├─<pkg>#包將被安裝到此處
  • 為了能夠構建這個工程,需要先把這個工程的根目錄加入到環境變量GOPATH中。假設calcproj 目錄位于/goyard下,則應編輯/.bashrc文件,并添加下面這行代碼:
export GOPATH=~/goyard/calcproj

然后執行以下命令應用該設置:

$ source ~/.bashrc

GOPATH和PATH環境變量一樣,也可以接受多個路徑,并且路徑和路徑之間用冒號分割。

  • 設置完GOPATH后,現在我們開始構建工程。假設我們希望把生成的可執行文件放到
    calcproj/bin目錄中,需要執行的一系列指令如下:
$ cd ~/goyard/calcproj
$ mkdir bin
$ cd bin
$ go build calc
  1. 么我們到底怎么運行這些單元測試呢?這也非常簡單。因為 已經設置了GOPATH,所以可以在任意目錄下執行以下命令:
$ go test simplemath
ok  simplemath  0.014s
  1. 打印日志

Go語言包中包含一個fmt包,其中提供了大量易用的打印函數,我們會接觸到的主要是 Printf()和Println()。

fval := 110.48
ival := 200
sval := "This is a string. "
fmt.Println("The value of fval is", fval)
fmt.Printf("fval=%f, ival=%d, sval=%s\n", fval, ival, sval)
fmt.Printf("fval=%v, ival=%v, sval=%v\n", fval, ival, sval)

輸出結果:

The value of fval is 100.48
fval=100.48, ival=200, sval=This is a string.
fval=100.48, ival=200, sval=This is a string.
  1. GDB調試

不用設置什么編譯選項,Go語言編譯的二進制程序直接支持GDB調試,比如之前用go build
calc編譯出來的可執行文件calc,就可以直接用以下命令以調試模式運行:

$ gdb calc
  1. 變量
  • 變量聲明

Go語言的變量聲明方式與C和C++語言有明顯的不同。對于純粹的變量聲明,Go語言引入了 關鍵字var,而類型信息放在變量名之后

var v1 int
var v2 string 
var v3 [10]int // 數組
var v4 []int // 數組切片
var v5 struct {
    f int 
}
var v6 *int // 指針
var v7 map[string] int // map,key為string類型,value為int類型
var v8 func(a int) int

// var關鍵字的另一種用法是可以將若干個需要聲明的變量放置在一起,免得程序員需要重復 寫var關鍵字

var (
    v1 int
    v2 string
)
  • 變量初始化

對于聲明變量時需要進行初始化的場景,var關鍵字可以保留,但不再是必要的元素

var v1 int = 10 // 正確的使用方式1
var v2 = 10 // 正確的使用方式2,編譯器可以自動推導出v2的類型 
v3 := 10 // 正確的使用方式3,編譯器可以自動推導出v3的類型

這里Go語言也引入了另一個C和C++中沒有的符號(冒號和等號的組合:=),用于明確表達同時進行變量聲明和初始化的工作。出現在:=左側的變量不應該是已經被聲明過的,否則會導致編譯錯誤

  • 變量賦值

在Go語法中,變量初始化和變量賦值是兩個不同的概念。下面為聲明一個變量之后的賦值過程:

var v10 int
v10 = 123

fmt.Println("Go語言的變量賦值與多數語言一致,但Go語言中提供了C/C++程序員期盼多年的多重賦值功 能,比如下面這個交換i和j變量的語句:")

i, j = j, i
  • 匿名變量
func GetName() (firstName, lastName, nickName string) { 
    return "May", "Chan", "Chibi Maruko"
}

//若只想獲得nickName,則函數調用語句可以用如下方式編寫:
_, _, nickName := GetName()
  1. 常量

在Go語言中,常量是指編譯期間就已知且不可改變的值。常量可以是數值類型(包括整型、
浮點型和復數類型)、布爾類型、字符串類型等。

  • 字面常量

所謂字面常量(literal),是指程序中硬編碼的常量,如:

-12
3.14159265358979323846 // 浮點類型的常量 
3.2+12i // 復數類型的常量 
true // 布爾類型的常量 
"foo" // 字符串常量
  • 常量定義

通過const關鍵字,你可以給字面常量指定一個友好的名字

const Pi float64 = 3.14159265358979323846
const zero = 0.0 // 無類型浮點常量
const (
    size int64 = 1024
    eof = -1 // 無類型整型常量
)
const u, v float32 = 0, 3  // u = 0.0, v = 3.0,常量的多重賦值
const a, b, c = 3, 4, "foo" //a=3,b=4,c="foo", 無類型整型和字符串常量

Go的常量定義可以限定常量類型,但不是必需的。如果定義常量時沒有指定類型,那么它 與字面常量一樣,是無類型常量。
常量定義的右值也可以是一個在編譯期運算的常量表達式,比如

const mask = 1 << 3

由于常量的賦值是一個編譯期行為,所以右值不能出現任何需要運行期才能得出結果的表達

  • 預定義常量

Go語言預定義了這些常量:true、false和iota

iota比較特殊,可以被認為是一個可被編譯器修改的常量,在每一個const關鍵字出現時被重置為0,然后在下一個const出現之前,每出現一次iota,其所代表的數字會自動增1

const (         // iota被重設為0
    c0 = iota   // c0 == 0
    c1 = iota   // c0 == 1
    c2 = iota   // c0 == 2
)

const (
    a = 1 << iota //a == 1 (iota在每個const開頭被重設為0)
    b = 1 << iota //a == 2 (iota在每個const開頭被重設為0)
    c = 1 << iota //a == 4 (iota在每個const開頭被重設為0)
)
const ( 
    u = iota * 42   //u == 0
    v = iota * 42   //v==42.0
    w = iota * 42   //v==84.0
)

const x = iota // x == 0 (因為iota又被重設為0了)
const y = iota // y == 0 (同上)

如果兩個const的賦值語句的表達式是一樣的,那么可以省略后一個賦值表達式。因此,上面的前兩個const語句可簡寫為:

const (         // iota被重設為0
    c0 = iota   // c0 == 0
    c1          // c0 == 1
    c2          // c0 == 2
)

const (
    a = 1 << iota //a == 1 (iota在每個const開頭被重設為0)
    b             //a == 2 (iota在每個const開頭被重設為0)
    c             //a == 4 (iota在每個const開頭被重設為0)
)
const ( 
    u = iota * 42   //u == 0
    v               //v==42.0
    w               //v==84.0
)
  • 枚舉

枚舉指一系列相關的常量,比如下面關于一個星期中每天的定義。

const (
    Sunday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
    numberOfDays    // 這個常量沒有導出
)

同Go語言的其他符號(symbol)一樣,以大寫字母開頭的常量在包外可見。
)
以上例子中numberOfDays為包內私有,其他符號則可被其他包訪問。

  1. 類型

基礎類型

  • 布爾類型:bool。

Go語言中的布爾類型與其他語言基本一致,關鍵字也為bool,可賦值為預定義的true和false示例代碼如下:

var v1 bool
v1 = true
v2 := (1 == 2) // v2也會被推導為bool類型

布爾類型不能接受其他類型的賦值,不支持自動或強制的類型轉換。

  • 整型:int8、byte、int16、int、uint、uintptr等。 ?

整型是所有編程語言里最基礎的數據類型。

類型 長度(字節) 值范圍
int8 1 -?128 ~ 127
uint8(即byte) 1 0~255
int16 2 -32768~32767
uint16 2 0~65535
int32 4 -?2147483648~2147483647
uint32 4 0~4294967295
int64 8 -?9223372036854775808~9223372036854775807
uint64 8 0~18446744073709551615
int 平臺相關 平臺相關
uint 平臺相關 平臺相關
uintptr 同指針 在32位平臺下為4字節,64位平臺下為8字節

int和int32在Go語言里被認為是兩種不同的類型,編譯器也不會幫你自動 做類型轉換

var value2 int32
value1 := 64 // value1將會被自動推導為int類型
value2 = int32(value1) // 編譯通過

當然,開發者在做強制類型轉換時,需要注意數據長度被截短而發生的數據精度損失(比如
將浮點數強制轉為整數)和值溢出(值超過轉換的目標類型的值范圍時)問題。

兩個不同類型的整型數不能直接比較,比如int8類型的數和int類型的數不能直接比較,但各種類型的整型變量都可以直接與字面常量(literal)進行比較

var i int32
var j int64
i, j = 1, 2
if i==j{ // 編譯錯誤 
    fmt.Println("i and j are equal.")
}
if i==1 || j==2{// 編譯通過 
    fmt.Println("i and j are equal.")
}

位運算

運算 含義
x << y 左移
x >> y 右移
x^y 異或
x&y
x|y
^x 取反
  • 浮點類型:float32、float64。

float32等價于C語言的float類型, float64等價于C語言的double類型。

var fvalue1 float32
fvalue1 = 12
fvalue2 := 12.0 // 如果不加小數點,fvalue2會被推導為整型而不是浮點型

以上例子中類型被自動推導的fvalue2,需要注意的是其類型將被自動設為float64, 而不管賦給它的數字是否是用32位長度表示的。

因為浮點數不是一種精確的表達方式,所以像整型那樣直接用==來判斷兩個浮點數是否相等 是不可行的,這可能會導致不穩定的結果。

import "math"

// p為用戶自定義的比較精度,比如0.00001 

func IsEqual(f1, f2, p float64) bool {
    return math.Fdim(f1, f2) < p 
}
  • 復數類型:complex64、complex128。 ?

復數實際上由兩個實數(在計算機中用浮點數表示)構成,一個表示實部(real),一個表示 虛部(imag)。


var value1 complex64
value1 = 3.2 + 12i
value2 := 3.2 + 12i
value3 := complex(3.2, 12)

對于一個復數z = complex(x,y),就可以通過Go語言內置函數real(z)獲得該復數的實部,也就是x,通過imag(z)獲得該復數的虛部,也就是y。

  • 字符串:string。
var str string // 聲明一個字符串變量
str = "Hello world" // 字符串賦值
ch := str[0] // 取字符串的第一個字符
fmt.Printf("The length of \"%s\" is %d \n", str, len(str)) fmt.Printf("The first character of \"%s\" is %c.\n", str, ch)

字符串的內容可以用類似于數組下標的方式獲取,但與數組不同,字符串的內容不能在初始 化后被修改

Go語言僅支持UTF-8和Unicode編碼。對于其他編碼,Go語言標準庫并沒有內置的編碼轉換支持。

字符串遍歷

//方式一
str := "Hello,世界"
n := len(str)
for i := 0; i < n; i++ {
ch := str[i] // 依據下標取字符串中的字符,類型為byte
    fmt.Println(i, ch)
}

//方式二
str := "Hello,世界"
for i, ch := range str {
    fmt.Println(i, ch)//ch的類型為rune 
}
  • 字符類型:rune。

在Go語言中支持兩個字符類型,一個是byte(實際上是uint8的別名),代表UTF-8字符串的單個字節的值;另一個是rune,代表單個Unicode字符。

  • 錯誤類型:error。

復合類型:

  • 指針(pointer)
  • 數組(array)

一些常規的數組聲明方法:

[32]byte // 長度為32的數組,每個元素為一個字節 
[2*N] struct { x, y int32 } // 復雜類型數組
[1000]*float64  // 指針數組
[3][5]int  // 二維數組
[2][2][2]float64 // 等同于[2]([2]([2]float64))

arrLength := len(arr)

for i, v := range array {
    fmt.Println("Array element[", i, "]=", v)
}

數組長度在定義后就不可更改,在聲明時長度可以為一個常量或者一個常量 表達式(常量表達式是指在編譯期即可計算結果的表達式)。

需要特別注意的是,在Go語言中數組是一個值類型(value type)。所有的值類型變量在賦值和作為參數傳遞時都將產生一次復制動作。

package main

import "fmt"

func modify(array [10]int) {
    array[0] = 10 // 試圖修改數組的第一個元素 fmt.Println("In modify(), array values:", array)
}
func main() {
    array := [5]int{1,2,3,4,5} // 定義并初始化一個數組
    modify(array) // 傳遞給一個函數,并試圖在函數體內修改這個數組內容
    fmt.Println("In main(), array values:", array)
}

結果

In modify(), array values: [10 2 3 4 5]
In main(), array values: [1 2 3 4 5]
  • 切片(slice)

數組切片就像一個指向數組的指針,實際上它擁有自己的數據結構,而不僅僅是 個指針。數組切片的數據結構可以抽象為以下3個變量: 一個指向原生數組的指針;數組切片中的元素個數;數組切片已分配的存儲空間。

package main
import "fmt"
func main() {
    // 先定義一個數組
    var myArray [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} // 基于數組創建一個數組切片
    var mySlice []int = myArray[:5]
    fmt.Println("Elements of myArray: ") 
    for _, v := range myArray {
        fmt.Print(v, " ")
    }
    fmt.Println("\nElements of mySlice: ")
    for _, v := range mySlice {
        fmt.Print(v, " ")
    }
    fmt.Println()
}

Go語言支持用myArray[first:last]這樣的方式來基于數組生成一 個數組切片,而且這個用法還很靈活

//創建一個初始元素個數為5的數組切片,元素初始值為0:
mySlice1 := make([]int, 5) 
//創建一個初始元素個數為5的數組切片,元素初始值為0,并預留10個元素的存儲空間:
mySlice2 := make([]int, 5, 10) 
//直接創建并初始化包含5個元素的數組切片:
mySlice3 := []int{1, 2, 3, 4, 5}

可動態增減元素是數組切片比數組更為強大的功能。與數組相比,數組切片多了一個存儲能 力(capacity)的概念,即元素個數和分配的空間可以是兩個不同的值。

package main
import "fmt" func main() {
mySlice := make([]int, 5, 10)
fmt.Println("len(mySlice):", len(mySlice))
fmt.Println("cap(mySlice):", cap(mySlice)) }

結果

len(mySlice): 5
cap(mySlice): 10

函數:

append:在末尾添加元素或者切片

cap:切片空間

len:切片長度

copy:復制。加入的兩個數組切片不一樣大,就會按其中較小的那個數組切片的元素個數進行 復制。

mySlice = append(mySlice, 1, 2, 3)

mySlice2 := []int{8, 9, 10}
// 給mySlice后面添加另一個數組切片
mySlice = append(mySlice, mySlice2...)


slice1 := []int{1, 2, 3, 4, 5} slice2 := []int{5, 4, 3}
copy(slice2, slice1) // 只會復制slice1的前3個元素到slice2中 copy(slice1, slice2) // 只會復制slice2的3個元素到slice1的前3個位置
  • 字典(map)
package main
import "fmt"
// PersonInfo是一個包含個人詳細信息的類型 
type PersonInfo struct {
    ID string
    Name string Address string
}
func main() {
    var personDB map[string] PersonInfo
    personDB = make(map[string] PersonInfo)
    // 往這個map里插入幾條數據
    personDB["12345"] = PersonInfo{"12345", "Tom", "Room 203,..."} personDB["1"] = PersonInfo{"1", "Jack", "Room 101,..."}
    // 從這個map查找鍵為"1234"的信息 
    person, ok := personDB["1234"]
    // ok是一個返回的bool型,返回true表示找到了對應的數據 
    if ok {
        fmt.Println("Found person", person.Name, "with ID 1234.")
    } else {
        fmt.Println("Did not find person with ID 1234.")
    }
}
//聲明
var myMap map[string] PersonInfo
//創建
myMap = make(map[string] PersonInfo)
//創建了一個初始存儲能力為100的map
myMap = make(map[string] PersonInfo, 100)
myMap = map[string] PersonInfo{
    "1234": PersonInfo{"1", "Jack", "Room 101,..."},
}
//賦值
myMap["1234"] = PersonInfo{"1", "Jack", "Room 101,..."}
//刪除
delete(myMap, "1234")
//元素查找
value, ok := myMap["1234"] 
if ok{// 找到了
    // 處理找到的value 
}
  • 通道(chan)
  • 結構體(struct)
  • 接口(interface)
  1. 流程控制
  • 條件 if,else,else if

條件語句不需要使用括號將條件包含起來();

無論語句體內有幾條語句,花括號{}都是必須存在的;

左花括號{必須與if或者else處于同一行;

有返回值的函數中,不允許將“最終的”return語句包含在if...else...結構中

  • 選擇 switch,case,select
switch i { 
    case 0:
        fmt.Printf("0") 
    case 1:
        fmt.Printf("1") 
    case 2:
        fallthrough 
    case 3:
        fmt.Printf("3") 
    case 4, 5, 6:
        fmt.Printf("4, 5, 6") 
    default:
        fmt.Printf("Default")
}


switch {
    case 0 <= Num && Num <= 3:
        fmt.Printf("0-3")
    case 4 <= Num && Num <= 6:
        fmt.Printf("4-6")
    case 7 <= Num && Num <= 9:
        fmt.Printf("7-9")
}

結果

i = 0時,輸出0;
i = 1時,輸出1;
i = 2時,輸出3;
i = 3時,輸出3;
?i = 4時,輸出4, 5, 6;
?i = 5時,輸出4, 5, 6;
?i = 6時,輸出4, 5, 6;
i = 其他任意值時,輸出Default。

只有在case中明確添加fallthrough關鍵字,才會繼續執行緊跟的下一個case;

條件表達式不限制為常量或者整數;

單個case中,可以出現多個結果選項;

與C語言等規則相反,Go語言不需要用break來明確退出一個case;

  • 循環 for,range

與多數語言不同的是,Go語言中的循環語句只支持for關鍵字,而不支持while和do-while 結構。

sum := 0 
for {
    sum++
    if sum > 100 {
       break 
    }
}

Go語言的for循環同樣支持continue和break來控制循環,但是它提供了一個更高級的break,可以選擇中斷哪一個循環

for j := 0; j < 5; j++ {
    for i := 0; i < 10; i++ {
        if i > 5 { 
            break JLoop
        
        fmt.Println(i)
    } 
}
JLoop: // break語句終止的是JLoop標簽處的外層循環。
//
  • 跳轉 goto
  • break,continue,fallthrough
  1. 函數
package mymath 
import "errors"
func Add(a int, b int) (ret int, err error) {
    if a < 0 || b < 0 { // 假設這個函數只支持兩個非負數字的加法
        err= errors.New("Should be non-negative numbers!")
    return
}
return a + b, nil // 支持多重返回值 }

如果參數列表中若干個相鄰的參數類型的相同,比如上面例子中的a和b,則可以在參數列表 中省略前面變量的類型聲明

如果返回值列表中多個返回值的類型相同,也可以用同樣的方式合并

小寫字母開頭的函數只在本包內可見,大寫字母開頭的函數才能被其他包使用。

  • 不定參數類型:...type本質上是一個數組切片,也就是[]type
func myfunc(args ...int) {
    for _, arg := range args {
        fmt.Println(arg)
    }
}
  • 定參數的傳遞
func myfunc(args ...int) { 
    // 按原樣傳遞
    myfunc3(args...)
    // 傳遞片段,實際上任意的int slice都可以傳進去
    myfunc3(args[1:]...)
}
  • 任意類型的不定參數

如果你希望傳任意類型,可以指定類型為 interface{}

func Printf(format string, args ...interface{}) { 
    // ...
}

package main
import "fmt"
func MyPrintf(args ...interface{}) { 
    for _, arg := range args {
        switch arg.(type) { 
            case int:
                fmt.Println(arg, "is an int value.") 
            case string:
                fmt.Println(arg, "is a string value.") 
            case int64:
                fmt.Println(arg, "is an int64 value.") 
            default:
                fmt.Println(arg, "is an unknown type.")
        }
    }
}
  • 匿名函數

匿名函數可以直接賦值給一個變量或者直接執行

f := func(x, y int) int { 
    return x + y
}
func(ch chan int) { 
    ch <- ACK
} (reply_chan) // 花括號后直接跟參數列表表示函數調用
  • defer 相當于c++的析構函數和java中的finally
defer func() {
    // 做你復雜的清理工作
} ()

  • panic()和recover()

Go語言引入了兩個內置函數panic()和recover()以報告和處理運行時錯誤和程序中的錯誤場景:

func panic(interface{}) 
func recover() interface{}

panic(404)
panic("network broken") 
panic(Error("file not exists"))

當在一個函數執行過程中調用panic()函數時,正常的函數執行流程將立即終止,但函數中 之前使用defer關鍵字延遲執行的語句將正常展開執行,之后該函數將返回到調用函數,并導致 逐層向上執行panic流程,直至所屬的goroutine中所有正在執行的函數被終止。

recover()函數用于終止錯誤處理流程。一般情況下,recover()應該在一個使用defer 關鍵字的函數中執行以有效截取錯誤處理流程。

defer func() {
    if r := recover(); r != nil {
        log.Printf("Runtime error caught: %v", r)
    }
}()
  • 快迅解析命令行參數的flag包。
package main
import "flag"

import "fmt"
var infile *string = flag.String("i", "infile", "File contains values for sorting") 
var outfile *string = flag.String("o", "outfile", "File to receive sorted values") 
var algorithm *string = flag.String("a", "qsort", "Sort algorithm")
func main() { 
    flag.Parse()
    if infile != nil {
        fmt.Println("infile =", *infile, "outfile =", *outfile,"algorithm =",*algorithm)
    } 
}

調用:

$ go build sorter.go
$ ./sorter -i unsorted.dat -o sorted.dat -a bubblesort
#結果
#infile = unsorted.dat outfile = sorted.dat algorithm = bubblesort
  1. 面向對象
  • 引用類型

channel和map類似,本質上是一個指針。將它們設計為引用類型而不是統一的值類型的原因 是,完整復制一個channel或map并不是常規需求。同樣,接口具備引用語義,是因為內部維持了兩個指針

type interface struct { 
    data *void
    itab *Itab 
}

  • 所有的Go語言類型(指針類型除外)都可以有自己的方法。
  • 初始化

type Rect struct { 
    x, y float64
    width, height float64 
}

rect1 := new(Rect)
rect2 := &Rect{}
rect3 := &Rect{0, 0, 100, 200}
rect4 := &Rect{width: 100, height: 200}

在Go語言中,未進行顯式初始化的變量都會被初始化為該類型的零值,例如bool類型的零 值為false,int類型的零值為0,string類型的零值為空字符串。

在Go語言中沒有構造函數的概念,對象的創建通常交由一個全局的創建函數來完成,以 NewXXX來命名,表示“構造函數”:

func NewRect(x, y, width, height float64) *Rect { 
    return &Rect{x, y, width, height}
}
  • 可訪問性

需要注意的一點是,Go語言中符號的可訪問性是包一級的而不是類型一級的。在上面的例 子中,盡管area()是Rect的內部方法,但同一個包中的其他類型也都可以訪問到它。

  1. 接口

接口賦值并不要求兩個接口必須等價。如果接口A的方法列表是接口B的方法列表的子集, 那么接口B可以賦值給接口A

  • 接口查詢,讓Writer接口轉換為two.IStream
type Reader interface {
    Read(buf []byte) (n int, err error)
}
type Writer interface {
    Write(buf []byte) (n int, err error)
}

type IStream interface {
    Write(buf []byte) (n int, err error) 
    Read(buf []byte) (n int, err error)
}

var file1 Writer = ...
if file5, ok := file1.(two.IStream); ok {
    ... 
}

  • 類型查詢
var v1 interface{} = ... 
switch v := v1.(type) {
    case int: // 現在v的類型是int 
    case string: // 現在v的類型是string 
    ...
}
  • 接口組合
// ReadWriter接口將基本的Read和Write方法組合起來 
type ReadWriter interface {
    Reader
    Writer 
}
  • Any類型

由于Go語言中任何對象實例都滿足空接口interface{},所以interface{}看起來像是可以指向任何對象的Any類型

  1. 并發
  • go

goroutine是Go語言中的輕量級線程實現,由Go運行時(runtime)管理。

func Add(x, y int) { z := x + y
    fmt.Println(z)
}
go Add(1, 1)
package main
import "fmt" 
import "sync" 
import "runtime"

var counter int = 0


func Count(lock *sync.Mutex) { 
    lock.Lock()
    counter++
    fmt.Println(z)
    lock.Unlock()
}

func main() {
    lock := &sync.Mutex{}
    for i := 0; i < 10; i++ { 
        go Count(lock)
    }
    
    for {
        lock.Lock()
        
        c := counter
        
        lock.Unlock()
        runtime.Gosched()//讓出CPU時間
        if c >= 10 {
            break
        }
    }
}
  • chan

Go語言提供的是另一種通信模型,即以消息機制而非共享內存作為通信方式。

channel是Go語言在語言級別提供的goroutine間的通信方式。我們可以使用channel在兩個或 多個goroutine之間傳遞消息。

package main
import "fmt"
func Count(ch chan int) { 
    ch <- 1
    fmt.Println("Counting")
}
func main() {
    chs := make([]chan int, 10) 
    for i := 0; i < 10; i++ {
        chs[i] = make(chan int)
        go Count(chs[i]) 
    }
    for _, ch := range(chs) { 
        <-ch
    }
}
  • select

Go語言直接在語言級別支持select關鍵字,用于處理異步IO 問題。select有比較多的限制,其中最大的一條限制就是每個case語句里必須是一個IO操作

select {
    case <-chan1:
        // 如果chan1成功讀到數據,則進行該case處理語句 
    case chan2 <- 1:
        // 如果成功向chan2寫入數據,則進行該case處理語句 
    default:
        // 如果上面都沒有成功,則進入default處理流程 
}
  • 緩沖

之前我們示范創建的都是不帶緩沖的channel,這種做法對于傳遞單個數據的場景可以接受,但對于需要持續傳輸大量數據的場景就有些不合適了。

要創建一個帶緩沖的channel,其實也非常容易:

c := make(chan int, 1024)

從帶緩沖的channel中讀取數據可以使用與常規非緩沖channel完全一致的方法,但我們也可 以使用range關鍵來實現更為簡便的循環讀取:

for i := range c { 
    fmt.Println("Received:", i)
}
  • 超時處理
    雖然select機制不是專為超時而設計的,卻能很方便地解決超時問題。因為select的特點是只要其中一個case已經 完成,程序就會繼續往下執行,而不會考慮其他case的情況。
// 首先,我們實現并執行一個匿名的超時等待函數 
timeout := make(chan bool, 1)

go func() {
    time.Sleep(1e9) // 等待1秒鐘
    timeout <- true 
}()

// 然后我們把timeout這個channel利用起來 
select {
    case <-ch:
        // 從ch中讀取到數據
    case <-timeout:
        // 一直沒有從ch中讀取到數據,但從timeout中讀取到了數據
}
  • 單向channel
var ch1 chan int // ch1是一個正常的channel,不是單向的
var ch2 chan<- float64// ch2是單向channel,只用于寫float64數據
var ch3 <-chan int // ch3是單向channel,只用于讀取int數據

ch4 := make(chan int)
ch5 := <-chan int(ch4) // ch5就是一個單向的讀取channel 
ch6 := chan<- int(ch4) // ch6 是一個單向的寫入channel

//單向channel的用法
func Parse(ch <-chan int) { 
    for value := range ch {
        fmt.Println("Parsing value", value)
    }
}
  • 關閉channel
//判斷channel是否被關閉
x, ok := <-ch

//關閉channel
close(ch)
  • 同步鎖

sync包提供了兩種鎖類型:sync.Mutex和sync.RWMutex。


var l sync.Mutex func foo() {
    l.Lock()
    defer l.Unlock()
    //...
}
  • 全局唯一性操作
var a string
var once sync.Once
func setup() {
    a = "hello, world"
}
func doprint() { 
    once.Do(setup)
    print(a) 
}
func twoprint() { 
    go doprint() 
    go doprint()
}
  1. 網絡編程

Go語言標準庫對此過程進行了抽象和封裝。無論我們期望使用什么協議建立什么形式的連接,都只需要調用net.Dial()即可。

func Dial(net, addr string) (Conn, error)

//TCP鏈接:
conn, err := net.Dial("tcp", "192.168.0.10:2100")

//UDP鏈接:
conn, err := net.Dial("udp", "192.168.0.12:975")

//ICMP鏈接(使用協議名稱):
conn, err := net.Dial("ip4:icmp", "www.baidu.com")

//ICMP鏈接(使用協議編號):
conn, err := net.Dial("ip4:1", "10.0.0.3")


目前,Dial()函數支持如下幾種網絡協議:"tcp"、"tcp4"(僅限IPv4)、"tcp6"(僅限 IPv6)、"udp"、"udp4"(僅限IPv4)、"udp6"(僅限IPv6)、"ip"、"ip4"(僅限IPv4)和"ip6"(僅限IPv6)。

實際上,Dial()函數是對DialTCP()、DialUDP()、DialIP()和DialUnix()的封裝。我們也可以直接調用這些函數,它們的功能是一致的。

func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err error) 
func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err error) 
func DialIP(netProto string, laddr, raddr *IPAddr) (*IPConn, error)
func DialUnix(net string, laddr, raddr *UnixAddr) (c *UnixConn, err error
  • 工具包

net.ResolveTCPAddr(),用于解析地址和端口號;

net.DialTCP(),用于建立鏈接。

func net.ParseIP(),驗證IP地址有效性

func IPv4Mask(a, b, c, d byte) IPMask,創建子網掩碼

func (ip IP) DefaultMask() IPMask,獲取默認子網掩碼

根據域名查找IP:

func ResolveIPAddr(net, addr string) (*IPAddr, error){}
func LookupHost(name string) (cname string, addrs []string, err error){}
  • HTTP客戶端
func (c *Client) Get(url string) (r *Response, err error){}
func (c *Client) Post(url string, bodyType string, body io.Reader) (r *Response, err error){}
func (c *Client) PostForm(url string, data url.Values) (r *Response, err error) {}
func (c *Client) Head(url string) (r *Response, err error){}
func (c *Client) Do(req *Request) (resp *Response, err error){}

http.Get() 等價于http.DefaultClient.Get()

resp, err := http.Get("http://example.com/") 
if err != nil {
    // 處理錯誤 ...
    return
}
defer resp.Body.close() 
io.Copy(os.Stdout, resp.Body)

添加cookie

req, err := http.NewRequest("GET", "http://example.com", nil) 
// ...
req.Header.Add("User-Agent", "Gobook Custom User-Agent")
// ...
client := &http.Client{ //... }
resp, err := client.Do(req)
// ...

DisableKeepAlives bool:是否取消長連接,默認值為 false,即啟用長連接。

DisableCompression bool 是否取消壓縮(GZip),默認值為 false,即啟用壓縮。

MaxIdleConnsPerHost int 指定與每個請求的目標主機之間的最大非活躍連接(keep-alive)數量。如果不指定,默認使
用 DefaultMaxIdleConnsPerHost 的常量值。

func(t *Transport) CloseIdleConnections()。該方法用于關閉所有非活躍的
連接

func(t *Transport) RegisterProtocol(scheme string, rt RoundTripper)。
該方法可用于注冊并啟用一個新的傳輸協議

func(t *Transport) RoundTrip(req *Request) (resp *Response, err error)。
用于實現 http.RoundTripper 接口

tr := &http.Transport{
    TLSClientConfig: &tls.Config{RootCAs:  pool}, 
    DisableCompression: true,
}
client := &http.Client{Transport: tr}
resp, err := client.Get("https://example.com")

通常,我們可以在默認的 http.Transport 之上包一層 Transport 并實現 RoundTrip() 方法

package main
import( 
    "net/http"
)
type OurCustomTransport struct { 
    Transport http.RoundTripper
}
func (t *OurCustomTransport) transport() http.RoundTripper { 
    if t.Transport != nil {
        return t.Transport 
    }
    return http.DefaultTransport
}

func (t *OurCustomTransport) RoundTrip(req *http.Request) (*http.Response, error) { 
    // 處理一些事情 ...
    // 發起HTTP請求
    // 添加一些域到req.Header中
    return t.transport().RoundTrip(req)
}

func (t *OurCustomTransport) Client() *http.Client { 
    return &http.Client{Transport: t}
}

func main() {
    t := &OurCustomTransport{ 
        //...
    }
    c := t.Client()
    resp, err := c.Get("http://example.com")
    // ...
}
  • HTTP服務端

使用 net/http 包提供的http.ListenAndServe()方法,可以在指定的地址進行監聽, 開啟一個HTTP,服務端該方法的原型如下:

func ListenAndServe(addr string, handler Handler) error

該方法有兩個參數:第一個參數 addr 即監聽地址;第二個參數表示服務端處理程序, 通常為空,這意味著服務端調用 http.DefaultServeMux 進行處理,而服務端編寫的業務邏 輯處理程序 http.Handle() 或 http.HandleFunc() 默認注入 http.DefaultServeMux 中

http.Handle("/foo", fooHandler)
http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
log.Fatal(http.ListenAndServe(":8080", nil))

如果想更多地控制服務端的行為,可以自定義 http.Server

s := &http.Server{
    Addr:"8080",
    Handler:myHander,
    ReadTimeout:10*time.Second,
    WriteTimeout:10*time.Second,
    MaxHeaderBytes: 1 << 20,   
}
log.Fatal(s.ListenAndServe())
  1. JSON 處理

使用json.Marshal()函數可以對一組數據進行JSON格式的編碼。

func Marshal(v interface{}) ([]byte, error)

Go語言的大多數數據類型都可以轉化為有效的JSON文本,但channel、complex和函數這幾種 類型除外。

如果轉化前的數據結構中出現指針,那么將會轉化指針所指向的值,如果指針指向的是零值, 那么null將作為轉化后的結果輸出。

數組和切片會轉化為JSON里邊的數組,但[]byte類型的值將會被轉化為 Base64編碼后的字符串,slice類型的零值會被轉化為 null。

字符串將以UTF-8編碼轉化輸出為Unicode字符集的字符串,特殊字符比如<將會被轉義為\u003c。

結構體會轉化為JSON對象,并且只有結構體里邊以大寫字母開頭的可被導出的字段才會被轉化輸出,而這些可導出的字段會作為JSON對象的字符串索引。

轉化一個map類型的數據結構時,該數據的類型必須是 map[string]T(T可以是
encoding/json 包支持的任意數據類型)。

  • 解碼JSON數據
func Unmarshal(data []byte, v interface{}) error

如果JSON中的字段在Go目標類型中不存在,json.Unmarshal()函數在解碼過程中會丟棄 該字段。

  • 解碼未知結構的JSON數據

如果要解碼一段未知結構的JSON,只需將這段JSON數據解碼輸出到一個空接口即可。

JSON中的布爾值將會轉換為Go中的bool類型; ?

數值會被轉換為Go中的float64類型;

字符串轉換后還是string類型;

JSON數組會轉換為[]interface{}類型;

JSON對象會轉換為map[string]interface{}類型; ? null值會轉換為nil。

var r interface{} err := json.Unmarshal(b, &r)
gobook, ok := r.(map[string]interface{})
if ok {
for k, v := range gobook {
    switch v2 := v.(type) { 
        case string:
            fmt.Println(k, "is string", v2) 
        case int:
            fmt.Println(k, "is int", v2) 
        case bool:
            fmt.Println(k, "is bool", v2) 
        case []interface{}:
            fmt.Println(k, "is an array:") 
            for i, iv := range v2 {
                fmt.Println(i, iv)
            }
        default:
            fmt.Println(k, "is another type not handle yet")
      } 
    }
}
  • JSON的流式讀寫

func NewDecoder(r io.Reader) *Decoder{}
func NewEncoder(w io.Writer) *Encoder{}

package main import (
        "encoding/json"
        "log"
        "os"
)
func main() {
    dec := json.NewDecoder(os.Stdin) 
    enc := json.NewEncoder(os.Stdout) 
    for {
        var v map[string]interface{}
        if err := dec.Decode(&v); err != nil {
            log.Println(err)
            return
        }
        for k := range v {
            if k != "Title" { 
                v[k] = nil, false
            }
        }
        if err := enc.Encode(&v); err != nil { 
            log.Println(err)
        }
    }
}
  1. 網站程序
package main

import ( 
    "io"
    "log"
    "net/http" 
)

func helloHandler(w http.ResponseWriter, r *http.Request) {               io.WriteString(w, "Hello, world!")
}
func main() {
    http.HandleFunc("/hello", helloHandler) 
    err := http.ListenAndServe(":8080", nil) 
    if err != nil {
        log.Fatal("ListenAndServe: ", err.Error())
    }
}
  • 渲染網頁模板

使用Go標準庫提供的html/template包,可以讓我們將HTML從業務邏輯程序中抽離出來 形成獨立的模板文件,這樣業務邏輯程序只負責處理業務邏輯部分和提供模板需要的數據,模板 文件負責數據要表現的具體形式。

雙大括號{{}}是區分模板代碼和HTML的分隔符,括號里邊可以是要顯示輸 出的數據,或者是控制語句,比如if判斷式或者range循環體等。

在使用range語句遍 歷的過程中,.即表示該循環體中的當前元素,.|formatter表示對當前這個元素的值以 formatter 方式進行格式化輸出

.|urlquery}即表示對當前元素的值進行轉換以適合作為URL一部 分,而{{.|html 表示對當前元素的值進行適合用于HTML 顯示的字符轉化

<!doctype html>
<html>
    <head>
        <meta charset="utf-8">
        <title>List</title>
    </head>
    <body>
        <ol>
            {{range $.images}}
            <li><a href="/view?id={{.|urlquery}}">{{.|html}}</a></li>
            {{end}}
        </ol>
    </body>
</html>

如果要更改模板中默認的分隔符,可以使用template包提供的Delims()方法。

package main
import ( 
    "io"
    "log"
    "net/http"
    "io/ioutil"
    "html/template"
)
func uploadHandler(w http.ResponseWriter, r *http.Request) { 
    if r.Method == "GET" {
        t, err := template.ParseFiles("upload.html") 
        if err != nil {
            http.Error(w, err.Error(),http.StatusInternalServerError)
            return
        }
        t.Execute(w, nil) return
    }
    if r.Method == "POST" {
        // ... 
    }
}
func listHandler(w http.ResponseWriter, r *http.Request) {          
    fileInfoArr, err := ioutil.ReadDir("./uploads")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError) 
        return
    }
    locals := make(map[string]interface{}) 
    images := []string{}
    for _, fileInfo := range fileInfoArr {
        images = append(images, fileInfo.Name) 
    }
    locals["images"] = images 
    t, err := template.ParseFiles("list.html") 
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError) 
        return
    }
    t.Execute(w, locals)
}

  • 模板緩存
templates := make(map[string]*template.Template)
func init() {
    for _, tmpl := range []string{"upload", "list"} {
        t := template.Must(template.ParseFiles(tmpl + ".html"))
        templates[tmpl] = t
    }
}
  • 巧用閉包避免程序運行時出錯崩潰

我們定義了一個名為 safeHandler() 的函數,該函數有一個參數并且返回一個值,傳入的參數和返回值都是一個函數,且都是http. HandlerFunc類型,這種類型的函數有兩個參數:http.ResponseWriter和 *http.Request。函 數規格同photoweb 的業務邏輯處理函數完全一致。

func safeHandler(fn http.HandlerFunc) http.HandlerFunc { 
    return func(w http.ResponseWriter, r *http.Request) {
        defer func() {
            if e, ok := recover().(error); ok {
                http.Error(w, err.Error(),http.StatusInternalServerError) // 或者輸出自定義的 50x 錯誤頁面
                // w.WriteHeader(http.StatusInternalServerError)
                // renderHtml(w, "error", e)
                // logging
                log.Println("WARN: panic in %v - %v", fn, e)
                log.Println(string(debug.Stack()))
            }
        }()
        fn(w,r)
    }
}

要應用safeHandler()函數,只需在main()中對各個業務邏輯處理函數做一次包裝,如下 面的代碼所示:

func main() {
    http.HandleFunc("/", safeHandler(listHandler))
    http.HandleFunc("/view", safeHandler(viewHandler))
    http.HandleFunc("/upload", safeHandler(uploadHandler)) 
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err.Error())
    }
}
  • 動態請求和靜態資源分離
const (
    ListDir = 0x0001
)

func staticDirHandler(mux *http.ServeMux, prefix string, staticDir string, flags int) {
    mux.HandleFunc(prefix, func(w http.ResponseWriter, r *http.Request) { 
        file := staticDir + r.URL.Path[len(prefix)-1:]
        if (flags & ListDir) == 0 {
            if exists := isExists(file); !exists { 
                http.NotFound(w, r)
                return
            }
        }
        http.ServeFile(w, r, file)
    })
}

func main() {
    mux := http.NewServeMux()
    staticDirHandler(mux, "/assets/", "./public", 0) mux.HandleFunc("/", safeHandler(listHandler)) mux.HandleFunc("/view", safeHandler(viewHandler)) mux.HandleFunc("/upload", safeHandler(uploadHandler)) err := http.ListenAndServe(":8080", mux)
    if err != nil {
        log.Fatal("ListenAndServe: ", err.Error())
    }
}
  1. 加密
package main import(
    "fmt"
    "crypto/sha1"
    "crypto/md5"
)
func main(){ TestString:="Hi,pandaman!"
    Md5Inst:=md5.New()
    Md5Inst.Write([]byte(TestString))
    Result:=Md5Inst.Sum([]byte(""))
    fmt.Printf("%x\n\n",Result)
    Sha1Inst:=sha1.New()
    Sha1Inst.Write([]byte(TestString))
    Result=Sha1Inst.Sum([]byte(""))
    fmt.Printf("%x\n\n",Result)
}
  1. 單元測試

單元測試源文件的命名規則如下:在需要測試的包下面創建以“_test”結尾的go文件,形 如[^.]*_test.go。

Go的單元測試函數分為兩類:功能測試函數和性能測試函數,分別為以Test和Benchmark 6 為函數名前綴并以*testing.T為單一參數的函數。下面是測試函數聲明的例子:

func TestAdd1(t *testing.T){}
func BenchmarkAdd1(t *testing.T){}

func TestAdd1(t *testing.T) { 
    r := Add(1, 2)
    if r != 2 { // 這里本該是3,故意改成2測試錯誤場景 
        t.Errorf("Add(1, 2) failed. Got %d, expected 3.", r)
    }
}

func BenchmarkAdd1(b *testing.B) { 
    for i := 0; i < b.N; i++ {
        Add(1, 2) 
    }
}

如果測試代碼中一些準備工作的時間太長,我們也可以這樣處理以明確排除這些準備工作所花費 時間對于性能測試的時間影響:

func BenchmarkAdd1(b *testing.B) {
    b.StopTimer() // 暫停計時器
    DoPreparation() // 一個耗時較長的準備工作,比如讀文件 b.StartTimer() // 開啟計時器,之前的準備時間未計入總花費時間內
    for i := 0; i < b.N; i++ { 
        Add(1, 2)
    }
}

性能單元測試的執行與功能測試一樣簡單,只不過調用時需要增加-test.bench參數

 $ go test–test.bench add.go
  1. 反射

Type主要表達的是被反射的這個變量本身的類型信息,而Value則為該變量實例本身 的信息。

package main
import ( 
    "fmt"
    "reflect" 
)

func main() {
    var x float64 = 3.4
    fmt.Println("type:", reflect.TypeOf(x), " ;value:", reflect.ValueOf(x))
    v := reflect.ValueOf(x)
    fmt.Println("type:", v.Type(), " ;value:", v.Float(), "; kind:", v.Kind())//v.Kind() == reflect.Float64
    if v.CanSet(){
        v.Set(4.1)
    }else{
        p := reflect.ValueOf(&x) // 注意:得到X的地址
        fmt.Println("settability of p:" , p.CanSet())
        w := p.Elem()
        w.SetFloat(7.1)
        fmt.Println(w.Interface())
        fmt.Println(x)
    }
    
    
}

在 調用ValueOf()的地方,需要注意到x將會產生一個副本,因此ValueOf()內部對x的操作其實 都是對著x的一個副本。假如v允許調用Set(),那么我們也可以想象出,被修改的將是這個x的 副本,而不是x本身。

  • 對結構的反射操作

type T struct { 
    A int
    B string
}
t := T{203, "mh203"}
s := reflect.ValueOf(&t).Elem() typeOfT := s.Type()
for i := 0; i < s.NumField(); i++ {
    f := s.Field(i)
    fmt.Printf("%d: %s %s = %v\n", i,typeOfT.Field(i).Name, f.Type(), f.Interface())
}

以上例子的輸出為:

0: A int = 203
1: B string = mh203

在試圖修改成員的值時,也需要注意可賦值屬性。

  1. 語言的交互性
package hello
/*
#include <stdio.h> void hello() {
    printf("Hello, Cgo! -- From C world.\n");
}
*/
import "C"
func Hello() int { 
    return int(C.hello())
}

那就是如果這里的C代碼需要依賴一個非C標準庫的第三方庫。Cgo提供了#cgo這樣的 C文法,讓開發者有機會定依賴的第三方庫和編譯選。

cgo的第一種用法:

// #cgo CFLAGS: -DPNG_DEBUG=1 
// #cgo linux CFLAGS: -DLINUX=1 
// #cgo LDFLAGS: -lpng
// #include <png.h>
import "C"

cgo還有另 外一種更簡 一些的用法

// #cgo pkg-config: png cairo 
// #include <png.h>
import "C"

調用者可以用以下 的方式 得到錯誤碼,在傳遞數組類型的參數時需要 意,在Go語言中將第一個元 的地 作為整個數組的起 始地 傳入:

n, err := C.f(&array[0]) // 需要顯示指定第一個元素的地址  
  • 鏈接符號

接符號關心的是如何將語言文法使用的符號 化為 接期使用的符號

由于 Go 語言并無重載, 此語言的“ 接符號”由如下信息構成。

  • [x] Package。Package 名可以是多層,例如A/B/C。

  • [x] ClassType。 很特別的是,Go 語言中 ClassType 可以是 ,也可以不是。

  • [x] Method。

其“ 接符號”的組成規則如下:

  • [x] Package.Method 或 Package.ClassType·Method
func New(cfg Config) *MockFS
func (fs *MockFS) Mkdir(dir string) (code int, err error) 
func (fs MockFS) Foo(bar Bar)
它們的 接符號分別為:
qbox.us/mockfs.New
qbox.us/mockfs.*MockFS·Mkdir
qbox.us/mockfs.MockFS·Foo
  1. 常用包介紹
  • fmt。它實現了格式化的輸入輸出操作,其中的fmt.Printf()和fmt.Println()是開發者使用最為頻繁的函數。
  • io。它實現了一系列非平臺相關的IO相關接口和實現,比如提供了對os中系統相關的IO功能的封裝。我們在進行流式讀寫(比如讀寫文件)時,通常會用到該包。
  • bufio。它在io的基礎上提供了緩存功能。在具備了緩存功能后,bufio可以比較方便地提供ReadLine之類的操作。
  • strconv。本包提供字符串與基本數據類型互轉的能力。
  • os。本包提供了對操作系統功能的非平臺相關訪問接口。接口為Unix風格。提供的功能 包括文件操作、進程管理、信號和用戶帳號等。
  • sync。它提供了基本的同步原語。在多個goroutine訪問共享資源的時候,需要使用sync中提供的鎖機制。
  • flag。它提供命令行參數的規則定義和傳入參數解析的功能。絕大部分的命令行程序都需要用到這個包。
  • encoding/json。JSON目前 用做網絡程序中的通信格式。本包提供了對JSON的基本支持,比如從一個對象序列化為JSON字符 ,或者從JSON字符 反序列化出一個具體的對象等。
  • http。它是一個強大而易用的包,也是Golang語言是一門“互聯網語言”的最好 證。通過http包,只需要數行代碼,可實現一個爬蟲或者一個Web務器,這在傳統語言中 是無法想象的。
golang思維導圖.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容