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
}
}