在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ GO/ 2.3 流程和函數(shù)
7 文本處理
3 Web基礎
14 擴展Web框架
10.4 小結
2.2 Go基礎
2.8 總結
6.1 session和cookie
5.5 使用beedb庫進行ORM開發(fā)
8.3 REST
13.6 小結
5.4 使用PostgreSQL數(shù)據(jù)庫
14.6 pprof支持
14.1 靜態(tài)文件支持
11.2 使用GDB調(diào)試
7.7 小結
1 GO環(huán)境配置
14.5 多語言支持
7.1 XML處理
1.5 總結
13 如何設計一個Web框架
14.3 表單及驗證支持
12 部署與維護
10 國際化和本地化
1.1 Go 安裝
6.2 Go如何使用session
5.6 NOSQL數(shù)據(jù)庫操作
6.5 小結
9.4 避免SQL注入
12.1 應用日志
4.2 驗證表單的輸入
10.1 設置默認地區(qū)
1.3 Go 命令
9.6 加密和解密數(shù)據(jù)
4.1 處理表單的輸入
4.4 防止多次遞交表單
11.3 Go怎么寫測試用例
8 Web服務
12.3 應用部署
5.7 小結
12.5 小結
11 錯誤處理,調(diào)試和測試
9.2 確保輸入過濾
14.2 Session支持
6.4 預防session劫持
12.4 備份和恢復
8.1 Socket編程
13.1 項目規(guī)劃
13.4 日志和配置設計
7.6 字符串處理
13.2 自定義路由器設計
6.3 session存儲
3.4 Go的http包詳解
8.2 WebSocket
10.3 國際化站點
7.5 文件操作
7.4 模板處理
9.1 預防CSRF攻擊
13.3 controller設計
2.6 interface
14.4 用戶認證
2.3 流程和函數(shù)
附錄A 參考資料
11.1 錯誤處理
9.5 存儲密碼
9.3 避免XSS攻擊
12.2 網(wǎng)站錯誤處理
6 session和數(shù)據(jù)存儲
2.4 struct類型
3.3 Go如何使得Web工作
2.5 面向?qū)ο?/span>
3.1 Web工作方式
1.2 GOPATH與工作空間
2.1 你好,Go
9.7 小結
13.5 實現(xiàn)博客的增刪改
7.2 JSON處理
10.2 本地化資源
7.3 正則處理
2 Go語言基礎
5.1 database/sql接口
4.5 處理文件上傳
8.5 小結
4.3 預防跨站腳本
5.3 使用SQLite數(shù)據(jù)庫
14.7 小結
3.2 Go搭建一個Web服務器
2.7 并發(fā)
5 訪問數(shù)據(jù)庫
4 表單
3.5 小結
1.4 Go開發(fā)工具
11.4 小結
9 安全與加密
5.2 使用MySQL數(shù)據(jù)庫
4.6 小結
8.4 RPC

2.3 流程和函數(shù)

這小節(jié)我們要介紹Go里面的流程控制以及函數(shù)操作。

流程控制

流程控制在編程語言中是最偉大的發(fā)明了,因為有了它,你可以通過很簡單的流程描述來表達很復雜的邏輯。Go中流程控制分三大類:條件判斷,循環(huán)控制和無條件跳轉。

if

if也許是各種編程語言中最常見的了,它的語法概括起來就是:如果滿足條件就做某事,否則做另一件事。

Go里面if條件判斷語句中不需要括號,如下代碼所示

if x > 10 {
    fmt.Println("x is greater than 10")
} else {
    fmt.Println("x is less than 10")
}

Go的if還有一個強大的地方就是條件判斷語句里面允許聲明一個變量,這個變量的作用域只能在該條件邏輯塊內(nèi),其他地方就不起作用了,如下所示

// 計算獲取值x,然后根據(jù)x返回的大小,判斷是否大于10。
if x := computedValue(); x > 10 {
    fmt.Println("x is greater than 10")
} else {
    fmt.Println("x is less than 10")
}

//這個地方如果這樣調(diào)用就編譯出錯了,因為x是條件里面的變量
fmt.Println(x)

多個條件的時候如下所示:

if integer == 3 {
    fmt.Println("The integer is equal to 3")
} else if integer < 3 {
    fmt.Println("The integer is less than 3")
} else {
    fmt.Println("The integer is greater than 3")
}

goto

Go有goto語句——請明智地使用它。用goto跳轉到必須在當前函數(shù)內(nèi)定義的標簽。例如假設這樣一個循環(huán):

func myFunc() {
    i := 0
Here:   //這行的第一個詞,以冒號結束作為標簽
    println(i)
    i++
    goto Here   //跳轉到Here去
}

標簽名是大小寫敏感的。

for

Go里面最強大的一個控制邏輯就是for,它即可以用來循環(huán)讀取數(shù)據(jù),又可以當作while來控制邏輯,還能迭代操作。它的語法如下:

for expression1; expression2; expression3 {
    //...
}

expression1、expression2expression3都是表達式,其中expression1expression3是變量聲明或者函數(shù)調(diào)用返回值之類的,expression2是用來條件判斷,expression1在循環(huán)開始之前調(diào)用,expression3在每輪循環(huán)結束之時調(diào)用。

一個例子比上面講那么多更有用,那么我們看看下面的例子吧:

package main
import "fmt"

func main(){
    sum := 0;
    for index:=0; index < 10 ; index++ {
        sum += index
    }
    fmt.Println("sum is equal to ", sum)
}
// 輸出:sum is equal to 45

有些時候需要進行多個賦值操作,由于Go里面沒有,操作符,那么可以使用平行賦值i, j = i+1, j-1

有些時候如果我們忽略expression1expression3

sum := 1
for ; sum < 1000;  {
    sum += sum
}

其中;也可以省略,那么就變成如下的代碼了,是不是似曾相識?對,這就是while的功能。

sum := 1
for sum < 1000 {
    sum += sum
}

在循環(huán)里面有兩個關鍵操作breakcontinue ,break操作是跳出當前循環(huán),continue是跳過本次循環(huán)。當嵌套過深的時候,break可以配合標簽使用,即跳轉至標簽所指定的位置,詳細參考如下例子:

for index := 10; index>0; index-- {
    if index == 5{
        break // 或者continue
    }
    fmt.Println(index)
}
// break打印出來10、9、8、7、6
// continue打印出來10、9、8、7、6、4、3、2、1

breakcontinue還可以跟著標號,用來跳到多重循環(huán)中的外層循環(huán)

for配合range可以用于讀取slicemap的數(shù)據(jù):

for k,v:=range map {
    fmt.Println("map's key:",k)
    fmt.Println("map's val:",v)
}

由于 Go 支持 “多值返回”, 而對于“聲明而未被調(diào)用”的變量, 編譯器會報錯, 在這種情況下, 可以使用_來丟棄不需要的返回值 例如

for _, v := range map{
    fmt.Println("map's val:", v)
}

switch

有些時候你需要寫很多的if-else來實現(xiàn)一些邏輯處理,這個時候代碼看上去就很丑很冗長,而且也不易于以后的維護,這個時候switch就能很好的解決這個問題。它的語法如下

switch sExpr {
case expr1:
    some instructions
case expr2:
    some other instructions
case expr3:
    some other instructions
default:
    other code
}

sExprexpr1expr2、expr3的類型必須一致。Go的switch非常靈活,表達式不必是常量或整數(shù),執(zhí)行的過程從上至下,直到找到匹配項;而如果switch沒有表達式,它會匹配true

i := 10
switch i {
case 1:
    fmt.Println("i is equal to 1")
case 2, 3, 4:
    fmt.Println("i is equal to 2, 3 or 4")
case 10:
    fmt.Println("i is equal to 10")
default:
    fmt.Println("All I know is that i is an integer")
}

在第5行中,我們把很多值聚合在了一個case里面,同時,Go里面switch默認相當于每個case最后帶有break,匹配成功后不會自動向下執(zhí)行其他case,而是跳出整個switch, 但是可以使用fallthrough強制執(zhí)行后面的case代碼。

integer := 6
switch integer {
case 4:
    fmt.Println("The integer was <= 4")
    fallthrough
case 5:
    fmt.Println("The integer was <= 5")
    fallthrough
case 6:
    fmt.Println("The integer was <= 6")
    fallthrough
case 7:
    fmt.Println("The integer was <= 7")
    fallthrough
case 8:
    fmt.Println("The integer was <= 8")
    fallthrough
default:
    fmt.Println("default case")
}

上面的程序?qū)⑤敵?/p>

The integer was <= 6
The integer was <= 7
The integer was <= 8
default case

函數(shù)

函數(shù)是Go里面的核心設計,它通過關鍵字func來聲明,它的格式如下:

func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {
    //這里是處理邏輯代碼
    //返回多個值
    return value1, value2
}

上面的代碼我們看出

  • 關鍵字func用來聲明一個函數(shù)funcName
  • 函數(shù)可以有一個或者多個參數(shù),每個參數(shù)后面帶有類型,通過,分隔
  • 函數(shù)可以返回多個值
  • 上面返回值聲明了兩個變量output1output2,如果你不想聲明也可以,直接就兩個類型
  • 如果只有一個返回值且不聲明返回值變量,那么你可以省略 包括返回值 的括號
  • 如果沒有返回值,那么就直接省略最后的返回信息
  • 如果有返回值, 那么必須在函數(shù)的外層添加return語句

下面我們來看一個實際應用函數(shù)的例子(用來計算Max值)

package main
import "fmt"

// 返回a、b中最大值.
func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

func main() {
    x := 3
    y := 4
    z := 5

    max_xy := max(x, y) //調(diào)用函數(shù)max(x, y)
    max_xz := max(x, z) //調(diào)用函數(shù)max(x, z)

    fmt.Printf("max(%d, %d) = %d\n", x, y, max_xy)
    fmt.Printf("max(%d, %d) = %d\n", x, z, max_xz)
    fmt.Printf("max(%d, %d) = %d\n", y, z, max(y,z)) // 也可在這直接調(diào)用它
}

上面這個里面我們可以看到max函數(shù)有兩個參數(shù),它們的類型都是int,那么第一個變量的類型可以省略(即 a,b int,而非 a int, b int),默認為離它最近的類型,同理多于2個同類型的變量或者返回值。同時我們注意到它的返回值就是一個類型,這個就是省略寫法。

多個返回值

Go語言比C更先進的特性,其中一點就是函數(shù)能夠返回多個值。

我們直接上代碼看例子

package main
import "fmt"

//返回 A+B 和 A*B
func SumAndProduct(A, B int) (int, int) {
    return A+B, A*B
}

func main() {
    x := 3
    y := 4

    xPLUSy, xTIMESy := SumAndProduct(x, y)

    fmt.Printf("%d + %d = %d\n", x, y, xPLUSy)
    fmt.Printf("%d * %d = %d\n", x, y, xTIMESy)
}

上面的例子我們可以看到直接返回了兩個參數(shù),當然我們也可以命名返回參數(shù)的變量,這個例子里面只是用了兩個類型,我們也可以改成如下這樣的定義,然后返回的時候不用帶上變量名,因為直接在函數(shù)里面初始化了。但如果你的函數(shù)是導出的(首字母大寫),官方建議:最好命名返回值,因為不命名返回值,雖然使得代碼更加簡潔了,但是會造成生成的文檔可讀性差。

func SumAndProduct(A, B int) (add int, Multiplied int) {
    add = A+B
    Multiplied = A*B
    return
}

變參

Go函數(shù)支持變參。接受變參的函數(shù)是有著不定數(shù)量的參數(shù)的。為了做到這點,首先需要定義函數(shù)使其接受變參:

func myfunc(arg ...int) {}

arg ...int告訴Go這個函數(shù)接受不定數(shù)量的參數(shù)。注意,這些參數(shù)的類型全部是int。在函數(shù)體中,變量arg是一個intslice

for _, n := range arg {
    fmt.Printf("And the number is: %d\n", n)
}

傳值與傳指針

當我們傳一個參數(shù)值到被調(diào)用函數(shù)里面時,實際上是傳了這個值的一份copy,當在被調(diào)用函數(shù)中修改參數(shù)值的時候,調(diào)用函數(shù)中相應實參不會發(fā)生任何變化,因為數(shù)值變化只作用在copy上。

為了驗證我們上面的說法,我們來看一個例子

package main
import "fmt"

//簡單的一個函數(shù),實現(xiàn)了參數(shù)+1的操作
func add1(a int) int {
    a = a+1 // 我們改變了a的值
    return a //返回一個新值
}

func main() {
    x := 3

    fmt.Println("x = ", x)  // 應該輸出 "x = 3"

    x1 := add1(x)  //調(diào)用add1(x)

    fmt.Println("x+1 = ", x1) // 應該輸出"x+1 = 4"
    fmt.Println("x = ", x)    // 應該輸出"x = 3"
}

看到了嗎?雖然我們調(diào)用了add1函數(shù),并且在add1中執(zhí)行a = a+1操作,但是上面例子中x變量的值沒有發(fā)生變化

理由很簡單:因為當我們調(diào)用add1的時候,add1接收的參數(shù)其實是x的copy,而不是x本身。

那你也許會問了,如果真的需要傳這個x本身,該怎么辦呢?

這就牽扯到了所謂的指針。我們知道,變量在內(nèi)存中是存放于一定地址上的,修改變量實際是修改變量地址處的內(nèi)存。只有add1函數(shù)知道x變量所在的地址,才能修改x變量的值。所以我們需要將x所在地址&x傳入函數(shù),并將函數(shù)的參數(shù)的類型由int改為*int,即改為指針類型,才能在函數(shù)中修改x變量的值。此時參數(shù)仍然是按copy傳遞的,只是copy的是一個指針。請看下面的例子

package main
import "fmt"

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

func main() {
    x := 3

    fmt.Println("x = ", x)  // 應該輸出 "x = 3"

    x1 := add1(&x)  // 調(diào)用 add1(&x) 傳x的地址

    fmt.Println("x+1 = ", x1) // 應該輸出 "x+1 = 4"
    fmt.Println("x = ", x)    // 應該輸出 "x = 4"
}

這樣,我們就達到了修改x的目的。那么到底傳指針有什么好處呢?

  • 傳指針使得多個函數(shù)能操作同一個對象。
  • 傳指針比較輕量級 (8bytes),只是傳內(nèi)存地址,我們可以用指針傳遞體積大的結構體。如果用參數(shù)值傳遞的話, 在每次copy上面就會花費相對較多的系統(tǒng)開銷(內(nèi)存和時間)。所以當你要傳遞大的結構體的時候,用指針是一個明智的選擇。
  • Go語言中channelslice,map這三種類型的實現(xiàn)機制類似指針,所以可以直接傳遞,而不用取地址后傳遞指針。(注:若函數(shù)需改變slice的長度,則仍需要取地址傳遞指針)

defer

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

func ReadWrite() bool {
    file.Open("file")
// 做一些工作
    if failureX {
        file.Close()
        return false
    }

    if failureY {
        file.Close()
        return false
    }

    file.Close()
    return true
}

我們看到上面有很多重復的代碼,Go的defer有效解決了這個問題。使用它后,不但代碼量減少了很多,而且程序變得更優(yōu)雅。在defer后指定的函數(shù)會在函數(shù)退出前調(diào)用。

func ReadWrite() bool {
    file.Open("file")
    defer file.Close()
    if failureX {
        return false
    }
    if failureY {
        return false
    }
    return true
}

如果有很多調(diào)用defer,那么defer是采用后進先出模式,所以如下代碼會輸出4 3 2 1 0

for i := 0; i < 5; i++ {
    defer fmt.Printf("%d ", i)
}

函數(shù)作為值、類型

在Go中函數(shù)也是一種變量,我們可以通過type來定義它,它的類型就是所有擁有相同的參數(shù),相同的返回值的一種類型

type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])

函數(shù)作為類型到底有什么好處呢?那就是可以把這個類型的函數(shù)當做值來傳遞,請看下面的例子

package main
import "fmt"

type testInt func(int) bool // 聲明了一個函數(shù)類型

func isOdd(integer int) bool {
    if integer%2 == 0 {
        return false
    }
    return true
}

func isEven(integer int) bool {
    if integer%2 == 0 {
        return true
    }
    return false
}

// 聲明的函數(shù)類型在這個地方當做了一個參數(shù)

func filter(slice []int, f testInt) []int {
    var result []int
    for _, value := range slice {
        if f(value) {
            result = append(result, value)
        }
    }
    return result
}

func main(){
    slice := []int {1, 2, 3, 4, 5, 7}
    fmt.Println("slice = ", slice)
    odd := filter(slice, isOdd)    // 函數(shù)當做值來傳遞了
    fmt.Println("Odd elements of slice are: ", odd)
    even := filter(slice, isEven)  // 函數(shù)當做值來傳遞了
    fmt.Println("Even elements of slice are: ", even)
}

函數(shù)當做值和類型在我們寫一些通用接口的時候非常有用,通過上面例子我們看到testInt這個類型是一個函數(shù)類型,然后兩個filter函數(shù)的參數(shù)和返回值與testInt類型是一樣的,但是我們可以實現(xiàn)很多種的邏輯,這樣使得我們的程序變得非常的靈活。

Panic和Recover

Go沒有像Java那樣的異常機制,它不能拋出異常,而是使用了panicrecover機制。一定要記住,你應當把它作為最后的手段來使用,也就是說,你的代碼中應當沒有,或者很少有panic的東西。這是個強大的工具,請明智地使用它。那么,我們應該如何使用它呢?

Panic

是一個內(nèi)建函數(shù),可以中斷原有的控制流程,進入一個令人恐慌的流程中。當函數(shù)F調(diào)用panic,函數(shù)F的執(zhí)行被中斷,但是F中的延遲函數(shù)會正常執(zhí)行,然后F返回到調(diào)用它的地方。在調(diào)用的地方,F的行為就像調(diào)用了panic。這一過程繼續(xù)向上,直到發(fā)生panicgoroutine中所有調(diào)用的函數(shù)返回,此時程序退出??只趴梢灾苯诱{(diào)用panic產(chǎn)生。也可以由運行時錯誤產(chǎn)生,例如訪問越界的數(shù)組。

Recover

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

下面這個函數(shù)演示了如何在過程中使用panic

var user = os.Getenv("USER")

func init() {
    if user == "" {
        panic("no value for $USER")
    }
}

下面這個函數(shù)檢查作為其參數(shù)的函數(shù)在執(zhí)行時是否會產(chǎn)生panic

func throwsPanic(f func()) (b bool) {
    defer func() {
        if x := recover(); x != nil {
            b = true
        }
    }()
    f() //執(zhí)行函數(shù)f,如果f中出現(xiàn)了panic,那么就可以恢復回來
    return
}

main函數(shù)和init函數(shù)

Go里面有兩個保留的函數(shù):init函數(shù)(能夠應用于所有的package)和main函數(shù)(只能應用于package main)。這兩個函數(shù)在定義時不能有任何的參數(shù)和返回值。雖然一個package里面可以寫任意多個init函數(shù),但這無論是對于可讀性還是以后的可維護性來說,我們都強烈建議用戶在一個package中每個文件只寫一個init函數(shù)。

Go程序會自動調(diào)用init()main(),所以你不需要在任何地方調(diào)用這兩個函數(shù)。每個package中的init函數(shù)都是可選的,但package main就必須包含一個main函數(shù)。

程序的初始化和執(zhí)行都起始于main包。如果main包還導入了其它的包,那么就會在編譯時將它們依次導入。有時一個包會被多個包同時導入,那么它只會被導入一次(例如很多包可能都會用到fmt包,但它只會被導入一次,因為沒有必要導入多次)。當一個包被導入時,如果該包還導入了其它的包,那么會先將其它包導入進來,然后再對這些包中的包級常量和變量進行初始化,接著執(zhí)行init函數(shù)(如果有的話),依次類推。等所有被導入的包都加載完畢了,就會開始對main包中的包級常量和變量進行初始化,然后執(zhí)行main包中的init函數(shù)(如果存在的話),最后執(zhí)行main函數(shù)。下圖詳細地解釋了整個執(zhí)行過程:

http://wiki.jikexueyuan.com/project/go-web-programming/images/2.3.init.png" alt="" />

圖2.6 main函數(shù)引入包初始化流程圖

import

我們在寫Go代碼的時候經(jīng)常用到import這個命令用來導入包文件,而我們經(jīng)常看到的方式參考如下:

import(
    "fmt"
)

然后我們代碼里面可以通過如下的方式調(diào)用

fmt.Println("hello world")

上面這個fmt是Go語言的標準庫,其實是去GOROOT環(huán)境變量指定目錄下去加載該模塊,當然Go的import還支持如下兩種方式來加載自己寫的模塊:

  1. 相對路徑

    import “./model” //當前文件同一目錄的model目錄,但是不建議這種方式來import

  2. 絕對路徑

    import “shorturl/model” //加載gopath/src/shorturl/model模塊

上面展示了一些import常用的幾種方式,但是還有一些特殊的import,讓很多新手很費解,下面我們來一一講解一下到底是怎么一回事

  1. 點操作

    我們有時候會看到如下的方式導入包

      import(
          . "fmt"
      )

    這個點操作的含義就是這個包導入之后在你調(diào)用這個包的函數(shù)時,你可以省略前綴的包名,也就是前面你調(diào)用的fmt.Println("hello world")可以省略的寫成Println("hello world")

  2. 別名操作

    別名操作顧名思義我們可以把包命名成另一個我們用起來容易記憶的名字

      import(
          f "fmt"
      )

    別名操作的話調(diào)用包函數(shù)時前綴變成了我們的前綴,即f.Println("hello world")

  3. _操作

    這個操作經(jīng)常是讓很多人費解的一個操作符,請看下面這個import

      import (
          "database/sql"
          _ "github.com/ziutek/mymysql/godrv"
      )

    _操作其實是引入該包,而不直接使用包里面的函數(shù),而是調(diào)用了該包里面的init函數(shù)。

上一篇:7.6 字符串處理下一篇:3.5 小結