Golang複習筆記要點

Golang basics

修改字符串:
s := "hello"
c := []byte(s)  // 將字符串 s 轉換爲 []byte 類型
c[0] = 'c'
s2 := string(c)  // 再轉換回 string 類型
fmt.Printf("%s\n", s2)

Go裏面有一個關鍵字`iota`,這個關鍵字用來聲明`enum`的時候採用,它默認開始值是0,每調用一次加1:

const(
    x = iota  // x == 0
    y = iota  // y == 1
    z = iota  // z == 2
    w  // 常量聲明省略值時,默認和之前一個值的字面相同。這裏隱式地說w = iota,因此w == 3。其實上面y和z可同樣不用"= iota"
)

分組定義:
import(
    "fmt"
    "os"
)

const(
    i = 100
    pi = 3.1415
    prefix = "Go_"
)

var(
    i int
    pi float32
    prefix string
)

*   大寫字母開頭的變量是可導出的,也就是其它包可以讀取的,是公用變量;小寫字母開頭的就是不可導出的,是私有變量。
*   大寫字母開頭的函數也是一樣,相當於`class`中的帶`public`關鍵詞的公有函數;小寫字母開頭的就是有`private`關鍵詞的私有函數。

數組:
var arr [10]int  // 聲明瞭一個int類型的數組
arr[0] = 42      // 數組下標是從0開始的
arr[1] = 13      // 賦值操作
fmt.Printf("The first element is %d\n", arr[0])  // 獲取數據,返回42
fmt.Printf("The last element is %d\n", arr[9]) //返回未賦值的最後一個元素,默認返回0
~~~

由於長度也是數組類型的一部分,因此`[3]int`與`[4]int`是不同的類型,數組也就不能改變長度。數組之間的賦值是值的賦值,即當把一個數組作爲參數傳入函數的時候,傳入的其實是該數組的副本,而不是它的指針。如果要使用指針,那麼就需要用到後面介紹的`slice`類型了。

數組可以使用另一種`:=`來聲明

~~~
a := [3]int{1, 2, 3} // 聲明瞭一個長度爲3的int數組

b := [10]int{1, 2, 3} // 聲明瞭一個長度爲10的int數組,其中前三個元素初始化爲1、2、3,其它默認爲0

c := [...]int{4, 5, 6} // 可以省略長度而採用`...`的方式,Go會自動根據元素個數來計算長度
~~~
// 聲明瞭一個二維數組,該數組以兩個數組作爲元素,其中每個數組中又有4個int類型的元素
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
// 上面的聲明可以簡化,直接忽略內部的類型
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}

數組切片(動態數組)
~~~
slice := []byte {'a', 'b', 'c', 'd'}
~~~

`slice`可以從一個數組或一個已經存在的`slice`中再次聲明。`slice`通過`array[i:j]`來獲取,其中`i`是數組的開始位置,`j`是結束位置,但不包含`array[j]`,它的長度是`j-i`。

~~~
// 聲明一個含有10個元素元素類型爲byte的數組
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}

// 聲明兩個含有byte的slice
var a, b []byte

// a指向數組的第3個元素開始,併到第五個元素結束,
a = ar[2:5]
//現在a含有的元素: ar[2]、ar[3]和ar[4]

// b是數組ar的另一個slice
b = ar[3:5]
// b的元素是:ar[3]和ar[4]
~~~
slice有一些簡便的操作

*   `slice`的默認開始位置是0,`ar[:n]`等價於`ar[0:n]`
*   `slice`的第二個序列默認是數組的長度,`ar[n:]`等價於`ar[n:len(ar)]`
*   如果從一個數組裏面直接獲取`slice`,可以這樣`ar[:]`,因爲默認第一個序列是0,第二個是數組的長度,即等價於`ar[0:len(ar)]`


~~~
// 聲明一個數組
var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// 聲明兩個slice
var aSlice, bSlice []byte

// 演示一些簡便操作
aSlice = array[:3] // 等價於aSlice = array[0:3] aSlice包含元素: a,b,c
aSlice = array[5:] // 等價於aSlice = array[5:10] aSlice包含元素: f,g,h,i,j
aSlice = array[:]  // 等價於aSlice = array[0:10] 這樣aSlice包含了全部的元素

// 從slice中獲取slice
aSlice = array[3:7]  // aSlice包含元素: d,e,f,g,len=4,cap=7
bSlice = aSlice[1:3] // bSlice 包含aSlice[1], aSlice[2] 也就是含有: e,f
bSlice = aSlice[:3]  // bSlice 包含 aSlice[0], aSlice[1], aSlice[2] 也就是含有: d,e,f
bSlice = aSlice[0:5] // 對slice的slice可以在cap範圍內擴展,此時bSlice包含:d,e,f,g,h
bSlice = aSlice[:]   // bSlice包含所有aSlice的元素: d,e,f,g
~~~

`map`也就是Python中字典的概念,它的格式爲`map[keyType]valueType`

我們看下面的代碼,`map`的讀取和設置也類似`slice`一樣,通過`key`來操作,只是`slice`的`index`只能是`int`類型,而`map`多了很多類型,可以是`int`,可以是`string`及所有完全定義了`==`與`!=`操作的類型。

~~~
// 聲明一個key是字符串,值爲int的字典,這種方式的聲明需要在使用之前使用make初始化
var numbers map[string]int
// 另一種map的聲明方式
numbers := make(map[string]int)
numbers["one"] = 1  //賦值
numbers["ten"] = 10 //賦值
numbers["three"] = 3

fmt.Println("第三個數字是: ", numbers["three"]) // 讀取數據
// 打印出來如:第三個數字是: 3
~~~

這個`map`就像我們平常看到的表格一樣,左邊列是`key`,右邊列是值

使用map過程中需要注意的幾點:

*   `map`是無序的,每次打印出來的`map`都會不一樣,它不能通過`index`獲取,而必須通過`key`獲取
*   `map`的長度是不固定的,也就是和`slice`一樣,也是一種引用類型
*   內置的`len`函數同樣適用於`map`,返回`map`擁有的`key`的數量
*   `map`的值可以很方便的修改,通過`numbers["one"]=11`可以很容易的把key爲`one`的字典值改爲`11`
*   `map`和其他基本型別不同,它不是thread-safe,在多個go-routine存取時,必須使用mutex lock機制

`map`的初始化可以通過`key:val`的方式初始化值,同時`map`內置有判斷是否存在`key`的方式

通過`delete`刪除`map`的元素:

~~~
參數指針
//簡單的一個函數,實現了參數+1的操作
func add1(a *int) int { // 請注意,
    *a = *a+1 // 修改了a的值
    return *a // 返回新值
}

defer
Go語言中有種不錯的設計,即延遲(defer)語句,你可以在函數中添加多個defer語句。當函數執行到最後時,這些defer語句會按照逆序執行,最後該函數返回。特別是當你在進行一些打開資源的操作時,遇到錯誤需要提前返回,在返回前你需要關閉相應的資源,不然很容易造成資源泄露等問題。

Panic
是一個內建函數,可以中斷原有的控制流程,進入一個令人恐慌的流程中。當函數F調用panic,函數F的執行被中斷,但是F中的延遲函數會正常執行,然後F返回到調用它的地方。在調用的地方,F的行爲就像調用了panic。這一過程繼續向上,直到發生panic的goroutine中所有調用的函數返回,此時程序退出。恐慌可以直接調用panic產生。也可以由運行時錯誤產生,例如訪問越界的數組。

Recover
是一個內建的函數,可以讓進入令人恐慌的流程中的goroutine恢復過來。recover僅在延遲函數中有效。在正常的執行過程中,調用recover會返回nil,並且沒有其它任何效果。如果當前的goroutine陷入恐慌,調用recover可以捕獲到panic的輸入值,並且恢復正常的執行。

var user = os.Getenv("USER")

func init() {
    if user == "" {
        panic("no value for $USER")
    }
}
func throwsPanic(f func()) (b bool) {
    defer func() {
        if x := recover(); x != nil {
            b = true
        }
    }()
    f() //執行函數f,如果f中出現了panic,那麼就可以恢復回來
    return
}

Struct:

type person struct {
    name string
    age int
}

var P person  // P現在就是person類型的變量了

P.name = "Astaxie"  // 賦值"Astaxie"給P的name屬性.
P.age = 25  // 賦值"25"給變量P的age屬性
fmt.Printf("The person's name is %s", P.name)  // 訪問P的name屬性.
除了上面這種P的聲明使用之外,還有另外幾種聲明使用方式:
* 1.按照順序提供初始化值 P := person{"Tom", 25}
* 2.通過field:value的方式初始化,這樣可以任意順序 P := person{age:24, name:"Tom"}
* 3.當然也可以通過new函數分配一個指針,此處P的類型爲*person P := new(person)

Interface:

type Stringer interface {
     String() string
}

interface可以被任意的對象實現。我們看到上面的Men interface被Human、Student和Employee實現。同理,一個對象可以實現任意多個interface,例如上面的Student實現了Men和YoungChap兩個interface。
最後,任意的類型都實現了空interface(我們這樣定義:interface{}),也就是包含0個method的interface。
interface值
那麼interface裏面到底能存什麼值呢?如果我們定義了一個interface的變量,那麼這個變量裏面可以存實現這個interface的任意類型的對象。例如上面例子中,我們定義了一個Men interface類型的變量m,那麼m裏面可以存Human、Student或者Employee值。
因爲m能夠持有這三種類型的對象,所以我們可以定義一個包含Men類型元素的slice,這個slice可以被賦予實現了Men接口的任意結構的對象,這個和我們傳統意義上面的slice有所不同。

Comma-ok斷言
Go語言裏面有一個語法,可以直接判斷是否是該類型的變量: value, ok = element.(T),這裏value就是變量的值,ok是一個bool類型,element是interface變量,T是斷言的類型。
如果element裏面確實存儲了T類型的數值,那麼ok返回true,否則返回false。
讓我們通過一個例子來更加深入的理解。
if value, ok := element.(int); ok {
            fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
        }

switch value := element.(type) {
            case int:
                fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
            case string:
                fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
            case Person:
                fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
            default:
                fmt.Println("list[%d] is of a different type", index)
        }


反射
Go語言實現了反射,所謂反射就是能檢查程序在運行時的狀態。我們一般用到的包是reflect包。如何運用reflect包,官方的這篇文章詳細的講解了reflect包的實現原理,laws of reflection
使用reflect一般分成三步,下面簡要的講解一下:要去反射是一個類型的值(這些值都實現了空interface),首先需要把它轉化成reflect對象(reflect.Type或者reflect.Value,根據不同的情況調用不同的函數)。這兩種獲取方式如下:
t := reflect.TypeOf(i)    //得到類型的元數據,通過t我們能獲取類型定義裏面的所有元素
v := reflect.ValueOf(i)   //得到實際的值,通過v我們獲取存儲在裏面的值,還可以去改變值
轉化爲reflect對象之後我們就可以進行一些操作了,也就是將reflect對象轉化成相應的值,例如
tag := t.Elem().Field(0).Tag  //獲取定義在struct裏面的標籤
name := v.Elem().Field(0).String()  //獲取存儲在第一個字段裏面的值
獲取反射值能返回相應的類型和數值
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
最後,反射的話,那麼反射的字段必須是可修改的,我們前面學習過傳值和傳引用,這個裏面也是一樣的道理。反射的字段必須是可讀寫的意思是,如果下面這樣寫,那麼會發生錯誤
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)
如果要修改相應的值,必須這樣寫
var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)

goroutine
goroutine是Go並行設計的核心。goroutine說到底其實就是線程,但是它比線程更小,十幾個goroutine可能體現在底層就是五六個線程,Go語言內部幫你實現了這些goroutine之間的內存共享。執行goroutine只需極少的棧內存(大概是4~5KB),當然會根據相應的數據伸縮。也正因爲如此,可同時運行成千上萬個併發任務。goroutine比thread更易用、更高效、更輕便。
goroutine是通過Go的runtime管理的一個線程管理器。goroutine通過go關鍵字實現了,其實就是一個普通的函數。
func say(s string) {
    for i := 0; i < 5; i++ {
        runtime.Gosched()
        fmt.Println(s)
    }
}

func main() {
    go say("world") //開一個新的Goroutines執行
    say("hello") //當前Goroutines執行
}

// 以上程序執行後將輸出:
// hello
// world
// hello
// world
// hello
// world
// hello
// world
// hello


channels
goroutine運行在相同的地址空間,因此訪問共享內存必須做好同步。那麼goroutine之間如何進行數據的通信呢,Go提供了一個很好的通信機制channel。channel可以與Unix shell 中的雙向管道做類比:可以通過它發送或者接收值。這些值只能是特定的類型:channel類型。定義一個channel時,也需要定義發送到channel的值的類型。注意,必須使用make 創建channel:
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})
channel通過操作符<-來接收和發送數據
ch <- v    // 發送v到channel ch.
v := <-ch  // 從ch中接收數據,並賦值給v


package main

import "fmt"

func sum(a []int, c chan int) {
    total := 0
    for _, v := range a {
        total += v
    }
    c <- total  // send total to c
}

func main() {
    a := []int{7, 2, 8, -9, 4, 0}

    c := make(chan int)
    go sum(a[:len(a)/2], c)
    go sum(a[len(a)/2:], c)
    x, y := <-c, <-c  // receive from c

    fmt.Println(x, y, x + y)
}

Buffered Channels
上面我們介紹了默認的非緩存類型的channel,不過Go也允許指定channel的緩衝大小,很簡單,就是channel可以存儲多少元素。ch:= make(chan bool, 4),創建了可以存儲4個元素的bool 型channel。在這個channel 中,前4個元素可以無阻塞的寫入。當寫入第5個元素時,代碼將會阻塞,直到其他goroutine從channel 中讀取一些元素,騰出空間。
ch := make(chan type, value)

value == 0 ! 無緩衝(阻塞)
value > 0 ! 緩衝(非阻塞,直到value 個元素)

func main() {
    c := make(chan int, 2)//修改2爲1就報錯,修改2爲3可以正常運行
    c <- 1
    c <- 2
    fmt.Println(<-c)
    fmt.Println(<-c)
}
    //修改爲1報如下的錯誤:
    //fatal error: all goroutines are asleep - deadlock!

Range和Close

func fibonacci(n int, c chan int) {
    x, y := 1, 1
    for i := 0; i < n; i++ {
        c <- x
        x, y = y, x + y
    }
    close(c)
}

func main() {
    c := make(chan int, 10)
    go fibonacci(cap(c), c)
    for i := range c {
        fmt.Println(i)
    }
}


Select
我們上面介紹的都是隻有一個channel的情況,那麼如果存在多個channel的時候,我們該如何操作呢,Go裏面提供了一個關鍵字select,通過select可以監聽channel上的數據流動。
select默認是阻塞的,只有當監聽的channel中有發送或接收可以進行時纔會運行,當多個channel都準備好的時候,select是隨機的選擇一個執行的。
func fibonacci(c, quit chan int) {
    x, y := 1, 1
    for {
        select {
        case c <- x:
            x, y = y, x + y
        case <-quit:
            fmt.Println("quit")
            return
        }
    }
}

func main() {
    c := make(chan int)
    quit := make(chan int)
    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(<-c)
        }
        quit <- 0
    }()
    fibonacci(c, quit)
}


超時
有時候會出現goroutine阻塞的情況,那麼我們如何避免整個程序進入阻塞的情況呢?我們可以利用select來設置超時,通過如下的方式實現:
func main() {
    c := make(chan int)
    o := make(chan bool)
    go func() {
        for {
            select {
                case v := <- c:
                    println(v)
                case <- time.After(5 * time.Second):
                    println("timeout")
                    o <- true
                    break
            }
        }
    }()
    <- o
}

runtime goroutine
runtime包中有幾個處理goroutine的函數:
* Goexit 退出當前執行的goroutine,但是defer函數還會繼續調用
* Gosched 讓出當前goroutine的執行權限,調度器安排其他等待的任務運行,並在下次某個時候從該位置恢復執行。
* NumCPU 返回 CPU 核數量
* NumGoroutine 返回正在執行和排隊的任務總數
* GOMAXPROCS 用來設置可以並行計算的CPU核數的最大值,並返回之前的值。

beego 默認會解析當前應用下的 conf/app.conf 文件。

定義 struct:
type user struct {
    Id    int         `form:"-"`
    Name  interface{} `form:"username"`
    Age   int         `form:"age"`
    Email string
}
表單:
<form id="user">
    名字:<input name="username" type="text" />
    年齡:<input name="age" type="text" />
    郵箱:<input name="Email" type="text" />
    <input type="submit" value="提交" />
</form>
Controller 裏解析:
func (this *MainController) Post() {
    u := user{}
    if err := this.ParseForm(&u); err != nil {
        //handle error
    }
}

json結構體中首字母大寫
type requestLogin struct {
    Name     string `json:"name"`
    Password string `json:"password"`
    Icon     string `json:"icon"`
    Remark   string `json:"remark"`
}


Content-Type : application/json; charset=utf-8

// 修改 "UNIX_TIMESTAMP(NOW()) - UNIX_TIMESTAMP(created_at)" 爲
fmt.Sprintf(`UNIX_TIMESTAMP("%s") - UNIX_TIMESTAMP(created_at)`, time.Now().Format("2006-01-02 15:04:05"))


func CopyUserInfo() {
   for {
      rows, err := MysqlClient.Query("SELECT name,mail,department,title FROM UsersInfo")
      if err != nil {
         log4go.Info("query mysqlDB fail")
         return
      }

      userInfos := make(map[int]models.UserInfo)
      userInfo := models.UserInfo{}
      i := 0
      for rows.Next() {
         rows.Scan(&userInfo.Name, &userInfo.Mail, &userInfo.Department, &userInfo.Title)
         userInfos[i] = userInfo
         i++
      }

      SetUserNameMail(userInfos)  //save userInfo into Redis
      SetUserDisplaynameMail(userInfos) //save userInfo into Redis
      fmt.Println("userinfo copy to redis successfully")
      ticker := time.NewTicker(time.Hour * 12)
      <-ticker.C
   }

}

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章