1. 程式人生 > >一、go語言基礎流程控制,語法,反射特性--go語言筆記

一、go語言基礎流程控制,語法,反射特性--go語言筆記

for迴圈

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

for sum<1000{
    sum ++
}

在第5行中,我們把很多值聚合在了一個case裡面,同時,Go裡面switch預設相當於每個case最後帶有break,匹配成功後不會自動向下執行其他case,而是跳出整個switch, 但是可以使用fallthrough強制執行後面的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"
) }

自動呼叫init->main

我們在寫Go程式碼的時候經常用到import這個命令用來匯入包檔案,而我們經常看到的方式參考如下:

import(
    "fmt"
)

然後我們程式碼裡面可以通過如下的方式呼叫

fmt.Println(“hello world”)
上面這個fmt是Go語言的標準庫,其實是去GOROOT環境變數指定目錄下去載入該模組,當然Go的import還支援如下兩種方式來載入自己寫的模組:

相對路徑

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

絕對路徑

import “shorturl/model” /
/載入gopath/src/shorturl/model模組

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

點操作

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

import(
    . "fmt"
)

這個點操作的含義就是這個包匯入之後在你呼叫這個包的函式時,你可以省略字首的包名,也就是前面你呼叫的fmt.Println(“hello world”)可以省略的寫成Println(“hello world”)

別名操作

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

import(
    f "fmt"
)

別名操作的話呼叫包函式時字首變成了我們的字首,即f.Println(“hello world”)

_操作

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

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

_操作其實是引入該包,而不直接使用包裡面的函式,而是呼叫了該包裡面的init函式。

interface

package main
import "fmt"

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名欄位
    school string
    loan float32
}

type Employee struct {
    Human //匿名欄位
    company string
    money float32
}

//Human實現SayHi方法
func (h Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Human實現Sing方法
func (h Human) Sing(lyrics string) {
    fmt.Println("La la la la...", lyrics)
}

//Employee過載Human的SayHi方法
func (e Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone)
    }

// Interface Men被Human,Student和Employee實現
// 因為這三個型別都實現了這兩個方法
type Men interface {
    SayHi()
    Sing(lyrics string)
}

func main() {
    mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
    paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
    sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
    tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}

    //定義Men型別的變數i
    var i Men

    //i能儲存Student
    i = mike
    fmt.Println("This is Mike, a Student:")
    i.SayHi()
    i.Sing("November rain")

    //i也能儲存Employee
    i = tom
    fmt.Println("This is tom, an Employee:")
    i.SayHi()
    i.Sing("Born to be wild")

    //定義了slice Men
    fmt.Println("Let's use a slice of Men and see what happens")
    x := make([]Men, 3)
    //這三個都是不同型別的元素,但是他們實現了interface同一個介面
    x[0], x[1], x[2] = paul, sam, mike

    for _, value := range x{
        value.SayHi()
    }
}

通過上面的程式碼,你會發現interface就是一組抽象方法的集合,它必須由其他非interface型別實現,而不能自我實現, Go通過interface實現了duck-typing:即”當看到一隻鳥走起來像鴨子、游泳起來像鴨子、叫起來也像鴨子,那麼這隻鳥就可以被稱為鴨子”。

空interface

空interface(interface{})不包含任何的method,正因為如此,所有的型別都實現了空interface。空interface對於描述起不到任何的作用(因為它不包含任何的method),但是空interface在我們需要儲存任意型別的數值的時候相當有用,因為它可以儲存任意型別的數值。它有點類似於C語言的void*型別。

// 定義a為空介面
var a interface{}
var i int = 5
s := "Hello world"
// a可以儲存任意型別的數值
a = i
a = s

一個函式把interface{}作為引數,那麼他可以接受任意型別的值作為引數,如果一個函式返回interface{},那麼也就可以返回任意型別的值。是不是很有用啊!

interface的變數可以持有任意實現該interface型別的物件,這給我們編寫函式(包括method)提供了一些額外的思考,我們是不是可以通過定義interface引數,讓函式接受各種型別的引數。

舉個例子:fmt.Println是我們常用的一個函式,但是你是否注意到它可以接受任意型別的資料。開啟fmt的原始碼檔案,你會看到這樣一個定義:

type Stringer interface {
String() string
}
也就是說,任何實現了String方法的型別都能作為引數被fmt.Println呼叫,讓我們來試一試

package main
import (
    "fmt"
    "strconv"
)

type Human struct {
    name string
    age int
    phone string
}

// 通過這個方法 Human 實現了 fmt.Stringer
func (h Human) String() string {
    return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years -  ✆ " +h.phone+"❱"
}

func main() {
    Bob := Human{"Bob", 39, "000-7777-XXX"}
    fmt.Println("This Human is : ", Bob)
}

現在我們再回顧一下前面的Box示例,你會發現Color結構也定義了一個method:String。其實這也是實現了fmt.Stringer這個interface,即如果需要某個型別能被fmt包以特殊的格式輸出,你就必須實現Stringer這個介面。如果沒有實現這個介面,fmt將以預設的方式輸出。

//實現同樣的功能

fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor())

注:實現了error介面的物件(即實現了Error() string的物件),使用fmt輸出時,會呼叫Error()方法,因此不必再定義String()方法了。

我們知道interface的變數裡面可以儲存任意型別的數值(該型別實現了interface)。那麼我們怎麼反向知道這個變數裡面實際儲存了的是哪個型別的物件呢?目前常用的有兩種方法:

Comma-ok斷言

Go語言裡面有一個語法,可以直接判斷是否是該型別的變數: value, ok = element.(T),這裡value就是變數的值,ok是一個bool型別,element是interface變數,T是斷言的型別。

如果element裡面確實儲存了T型別的數值,那麼ok返回true,否則返回false。

讓我們通過一個例子來更加深入的理解。

package main

import (
“fmt”
“strconv”
)

type Element interface{}
type List [] Element

type Person struct {
name string
age int
}

//定義了String方法,實現了fmt.Stringer
func (p Person) String() string {
return “(name: ” + p.name + ” - age: “+strconv.Itoa(p.age)+ ” years)”
}

func main() {
list := make(List, 3)
list[0] = 1 // an int
list[1] = “Hello” // a string
list[2] = Person{“Dennis”, 70}

for index, element := range list {
    if value, ok := element.(int); ok {
        fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
    } else if value, ok := element.(string); ok {
        fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
    } else if value, ok := element.(Person); ok {
        fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
    } else {
        fmt.Printf("list[%d] is of a different type\n", index)
    }
}

}
是不是很簡單啊,同時你是否注意到了多個if裡面,還記得我前面介紹流程時講過,if裡面允許初始化變數。

也許你注意到了,我們斷言的型別越多,那麼if else也就越多,所以才引出了下面要介紹的switch。

switch測試

最好的講解就是程式碼例子,現在讓我們重寫上面的這個實現

package main

import (
    "fmt"
    "strconv"
)

type Element interface{}
type List [] Element

type Person struct {
    name string
    age int
}

//列印
func (p Person) String() string {
    return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
}

func main() {
    list := make(List, 3)
    list[0] = 1 //an int
    list[1] = "Hello" //a string
    list[2] = Person{"Dennis", 70}

    for index, element := range list{
        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)
        }
    }
}

這裡有一點需要強調的是:element.(type)語法不能在switch外的任何邏輯裡面使用,如果你要在switch外面判斷一個型別就使用comma-ok。

反射

having a rough time working with struct fields using reflect package. in particular, have not figured out how to set the field value.

type t struct { fi int; fs string }
var r t = t{ 123, "jblow" }
var i64 int64 = 456
getting Name of field i - this seems to work

var field = reflect.TypeOf(r).Field(i).Name

getting value of field i as a) interface{}, b) int - this seems to work

var iface interface{} = reflect.ValueOf(r).Field(i).Interface()

var i int = int(reflect.ValueOf(r).Field(i).Int())

setting value of field i - try one - panic

reflect.ValueOf(r).Field(i).SetInt( i64 )

panic: reflect.Value·SetInt using value obtained using unexported field

assuming it did not like field names "id" and "name", so renamed to "Id" and "Name"

a) is this assumption correct?

b) if correct, thought not necessary since in same file / package

setting value of field i - try two (with field names capitalized ) - panic

reflect.ValueOf(r).Field(i).SetInt( 465 )

reflect.ValueOf(r).Field(i).SetInt( i64 )

panic: reflect.Value·SetInt using unaddressable value



Four. this works:

reflect.ValueOf(&r).Elem().Field(i).SetInt( i64 )

he documents as well that the field names must be exportable (begin with capital letter)
package main

import (
    "fmt"
    "reflect"
)

type Human struct {
    name  string
    age   int
    phone string
}

type Student struct {
    Human  //匿名欄位
    school string
}

type Employee struct {
    Human   //匿名欄位
    company string
}

//Human定義method
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Employee的method重寫Human的method
func (e *Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone) //Yes you can split into 2 lines here.
}

type t struct {
    fi int
    fs string
}

func main() {
    //mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
    //sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}
    /*
        var i = 10
        //markT := reflect.TypeOf(mark)
        t := reflect.TypeOf(i)
        v := reflect.ValueOf(i)
        tag := t.Elem().Field(0).Tag
        name := v.Elem().Field(0).String()
        //fmt.Println("markT", markT)
        fmt.Println("mark")
        fmt.Println("tag:", tag)
        fmt.Println("name:", name)
    */

    var r t = t{123, "jblow"}
    //var i64 int64 = 456
    var field = reflect.TypeOf(r).Field(0).Name

    //reflect.ValueOf(r).Field(0).SetInt(i64)
    fmt.Println(field)
    fmt.Println(r)

    var iface interface{} = reflect.ValueOf(r).Field(0).Interface()
    var i int = int(reflect.ValueOf(r).Field(1).Int())
    fmt.Println(iface)
    fmt.Println(i)
    /*
            panic: reflect.Value.Interface: cannot return value obtained from unexported field or method
            this works:

        reflect.ValueOf(&r).Elem().Field(i).SetInt( i64 )

        he documents as well that the field names must be exportable (begin with capital letter) 大寫才行
    */
}
package main

import (
    "fmt"
    "reflect"
)

type Foo struct {
    FirstName string `tag_name:"tag 1"`
    LastName  string `tag_name:"tag 2"`
    Age       int    `tag_name:"tag 3"`
}

func (f *Foo) reflect() {
    val := reflect.ValueOf(f).Elem()

    for i := 0; i < val.NumField(); i++ {
        valueField := val.Field(i)
        typeField := val.Type().Field(i)
        tag := typeField.Tag

        fmt.Printf("Field Name: %s,\t Field Value: %v,\t Tag Value: %s\n", typeField.Name, valueField.Interface(), tag.Get("tag_name"))
    }
}

func main() {
    f := &Foo{
        FirstName: "Drew",
        LastName:  "Olson",
        Age:       30,
    }

    f.reflect()
}