Go編程基礎-學習2

46.結構struct

Go 中的struct與C中的struct非常相似,並且Go沒有class
使用 type <Name> struct{} 定義結構,名稱遵循可見性規則

package main
import "fmt"
type persion struct {
    Name string
    Age int
}
func main() {
    a := persion{}
    a.Name="david"
    a.Age=13
    fmt.Println(a)
}
{david 13}

package main
import "fmt"
type persion struct {
    Name string
    Age int
}
func main() {
    a := persion{Name:"david"}
    fmt.Println(a)
}
{david 0} //0是int的初始值

package main
import "fmt"
type persion struct {
    Name string
    Age int
}
func main() {
    a := persion{Name:"david",Age:13}
    fmt.Println(a)
}
{david 13}

package main
import "fmt"
type persion struct {
    Name string
    Age int
}
func main() {
    a := persion{"david",28}
    fmt.Println(a)
}
{david 28}

struct也是一個值類型,傳遞的時候也是值拷貝

package main
import "fmt"
type persion struct {
    Name string
    Age int
}
func main() {
    a := persion{
        Name:"david",
        Age:28,
        }
    fmt.Println(a)
    A(a)
    fmt.Println(a)
}
func A(per persion){
    per.Age = 13
    fmt.Println("A",per)
}
{david 28}
A {david 13}
{david 28}

支持指向自身的指針類型成員

如何真正修改Age爲13呢?答案是採用指針方式修改內存地址中的值,指針值傳遞
package main
import "fmt"
type persion struct {
    Name string
    Age int
}
func main() {
    a := persion{
        Name:"david",
        Age:28,
        }
    fmt.Println(a)
    A(&a)
    fmt.Println(a)
}
func A(per *persion){
    per.Age = 13
    fmt.Println("A",per)
}
{david 28}
A &{david 13}
{david 13}//這裏修改了內存中的age爲13

package main
import "fmt"
type persion struct {
    Name string
    Age int
}
func main() {
    a := persion{
        Name:"david",
        Age:28,
        }
    fmt.Println(a)
    A(&a)
    B(&a)
    fmt.Println(a)
}
func A(per *persion){
    per.Age = 13
    fmt.Println("A",per)
}
func B(per *persion){
    per.Age = 15
    fmt.Println("B",per)
}
{david 28}
A &{david 13}
B &{david 15}
{david 15}//如果有一個B函數,修改age爲15,最終age等於15

如果有(100個)多個函數需要調用?每次都需要取地址符號,很麻煩,怎麼辦? 用指針保存,把a變爲一個指向結構的指針呢?初始化的時候就把地址取出來
package main
import "fmt"
type persion struct {
    Name string
    Age int
}
func main() {
    a := &persion{    //定義a是一個指針
        Name:"david",
        Age:28,
        }
    fmt.Println(a)
    A(a) //這裏直接調用即可,不用取地址
    B(a) //這裏直接調用即可,不用取地址
    fmt.Println(a)
}
func A(per *persion){
    per.Age = 23
    fmt.Println("A",per)
}
func B(per *persion){
    per.Age = 25
    fmt.Println("B",per)
}
&{david 28}//這裏可以看到a是一個指向struct的指針
A &{david 23}
B &{david 25}
&{david 25}

如果此時我需要要像class一樣對a的屬性性操作,怎麼做?a.Name = "ok"
package main
import "fmt"
type persion struct {
    Name string
    Age int
}
func main() {
    a := &persion{"david", 28,}
    a.Name="ok" //這裏直接修改屬性的值
    fmt.Println(a)
    A(a)
    fmt.Println(a)
}
func A(per *persion) {
    per.Age = 23
    fmt.Println("A", per)
}
&{ok 28}
A &{ok 23}
&{ok 23}

支持匿名結構,可用作成員或定義成員變量

package main
import "fmt"
func main() {
    a := struct{
        Name string
        Age int
        }{
            Name:"david",
            Age:19,
            }
    fmt.Println(a)
}
{david 19}
 也可以定義指針類型的匿名結構
package main
import "fmt"
func main() {
    a := &struct{ //a是沒有名稱的匿名結構,定義指針類型結構
        Name string
        Age int
        }{  //需要對結構屬性(字面值)賦值
            Name:"david",
            Age:19,
            }
    fmt.Println(a)
}
&{david 19}

匿名結構是否可以嵌套其他結構中呢?可以
package main
import "fmt"
type person struct {
    Name string
    Age int
    Contact struct {
        Phone,City string
    }
}
func main() {
    a := person{}
    fmt.Println(a)
}
{ 0 { }} //0是int型的Age初始值,{ }是Contact的結構

package main
import "fmt"
type person struct {
    Name string
    Age int
    Contact struct {
        Phone,City string
    }
}
func main() {
    a := person{Name:"david",Age:13}
    a.Contact.City="sahgnhai"//匿名結構賦值的方法
    a.Contact.Phone="111111111"
    fmt.Println(a)
}
{david 13 {111111111 sahgnhai}}

什麼是匿名字段?
package main
import "fmt"
type person struct {
    string //這就是匿名字段,沒有定義屬性的名字,只是定義了類型
    int
}
func main() {
    a := person{"david",19}//注意,賦值順序是string、int,不能對調,否則報錯
    fmt.Println(a)
}
{david 19}

匿名結構也可以用於map的值
可以使用字面值對結構進行初始化
允許直接通過指針來讀寫結構成員
相同類型的成員可進行直接拷貝賦值
支持 == 與 !=比較運算符,但不支持 > 或 <
支持匿名字段,本質上是定義了以某個類型名爲名稱的字段

結構也是一種類型,相同的類型之間,變量可以進行賦值
package main
import "fmt"
type person struct {
    Name string
    Age int
}
func main() {
    a := person{"david",19}
    var b person
    b=a //把a賦值給b,打印出來b和a是一樣的
    fmt.Println(b)
}
{david 19}

struce是一種類型,所以可以比較?
a和b雖然內容包含相同,但是名稱不同,就是不同類型,沒有可比性,無法比較,所以報錯,只有用相同的struct person纔可以比較。
package main
import "fmt"
type person1 struct {
    Name string
    Age int
}
type person2 struct {
    Name string
    Age int
}
func main() {
    a := person1{"david",19}
    b := person2{"david",19}
    fmt.Println(a == b )
}
invalid operation: a == b (mismatched types person1 and person2)

package main
import "fmt"
type person struct {
    Name string
    Age int
}
func main() {
    a := person{"david",19}
    b := person{"david",19}
    fmt.Println(a == b )
}
true

package main
import "fmt"
type person struct {
    Name string
    Age int
}
func main() {
    a := person{"david",19}
    b := person{"david",20}
    fmt.Println(a == b )
}
false

嵌入結構作爲匿名字段看起來像繼承,但不是繼承
可以使用匿名字段指針

package main
import (
    "fmt"
)
type human struct {
    Sex int
}
type teacher struct {
    human
    Name string
    Age int
}
type student struct {
    human
    Name string
    Age int
}
func main() {
    a := teacher{Name:"joe",Age:19}
    b := student{Name:"joe",Age:20}
    fmt.Println(a,b )
}
{{0} joe 19} {{0} joe 20} //human的int默認值0已經嵌套進去了

package main
import (
    "fmt"
)
type human struct {
    Sex int
}
type teacher struct {
    human
    Name string
    Age int
}
type student struct {
    human
    Name string
    Age int
}
func main() {
    a := teacher{Name:"joe",Age:19,Sex:0}
    b := student{Name:"joe",Age:20,Sex:1}
    fmt.Println(a,b )
}
hello.go:19:36: unknown field 'Sex' in struct literal of type teacher
hello.go:20:36: unknown field 'Sex' in struct literal of type student
//這裏報錯了,因爲初始化方法錯誤,應該怎麼定義呢?

方法1:human在teacher中被當做匿名字段,所以定義human:{Sex:0}
package main
import (
    "fmt"
)
type human struct {
    Sex int
}
type teacher struct {
    human
    Name string
    Age int
}
type student struct {
    human
    Name string
    Age int
}
func main() {
    a := teacher{Name:"joe",Age:19,human:human{Sex:0}}//human在teacher中被當做匿名字段變量,給變量賦值
    b := student{Name:"joe",Age:20,human:human{Sex:1}}
    fmt.Println(a,b )
}
{{0} joe 19} {{1} joe 20}

package main
import (
    "fmt"
)
type human struct {
    Sex int
}
type teacher struct {
    human
    Name string
    Age int
}
type student struct {
    human
    Name string
    Age int
}
func main() {
    a := teacher{Name:"joe",Age:19,human:human{Sex:0}}
    b := student{Name:"joe",Age:20,human:human{Sex:1}}
    a.Name="joe2"
    a.Age=13
    a.human.Sex=100 //標準方法,防止有多個導入字段重複報錯
    a.Sex=100 //sex已經被當做teacher的一個屬性嵌入,human結構嵌入,把嵌入結構的字段sex都給了外層結構teacher
    fmt.Println(a,b )
}
{{100} joe2 13} {{1} joe 20}

如果匿名字段和外層結構有同名字段,應該如何進行操作?

package main
import (
    "fmt"
)
type A struct {
    B
    Name string
}
type B struct {
    Name string
}

func main() {
    a := A{Name: "A", B: B{Name: "B"}}
    fmt.Println(a.Name,a.B.Name) //分別輸出a和b的name值A和B
}
A B

如果A中不存在Name字段,那麼打印a.Name是什麼呢?答案是B
package main
import (
    "fmt"
)
type A struct {
    B
}
type B struct {
    Name string
}

func main() {
    a := A{B: B{Name: "B"}}
    fmt.Println(a.Name,a.B.Name)//此時的a.Name=B,此時a.Name,a.B.Name寫法等價
}
B B

package main
import (
    "fmt"
)
type A struct {
    B
    C
}
type B struct {
    Name string
}

type C struct {
    Name string
}
func main() {
    a := A{B: B{Name: "B"},C: C{Name: "C"}}
    fmt.Println(a.Name,a.B.Name,a.C.Name)
}
ambiguous selector a.Name//報錯提示有重名的字段,因爲a.Name此時到底等於a.B.Name還是等於a.C.Name呢?完全不清楚

47.方法method

Go 中雖沒有class,但依舊有method

只能爲同一個包中的類型結構定義方法method呢?通過顯示說明receiver來實現與某個類型的組合,編譯器根據接受者的類型來判斷是哪一個類型的方法

package main
import (
    "fmt"
)
type A struct {
    Name string
}
type B struct {
    Name string
}
//定義一個鏈接到struct A的方法print
func(a A)Print(){ //局部變量a的接受者類型是struct:A,所以定義的方法print是struct A的類型方法
    fmt.Println("A")
}
func main(){
    a :=A{} //聲明一個結構A
    a.Print()//然後a調用Print方法,打印結果A
}
A

package main
import (
    "fmt"
)
type A struct {
    Name string
}
type B struct {
    Name string
}
//定義一個鏈接到struct A的方法print
func(a A)Print(){ //局部變量a的接受者類型是struct:A,所以定義的方法print是struct A的類型方法
    a.Name = "A"
    fmt.Println("A")
}
func(b B)Print(){ //定義一個連接到B結構的方法Print
    b.Name="B"
    fmt.Println("B")
}
func main(){
    c :=A{} //聲明一個結構A
    c.Print()//然後a調用Print方法,打印結果A
    d :=B{}
    d.Print()//注意這裏調用方法使用c.Print()和d.Print(),不能使用Print(),否則編譯器不清楚是c還是d的Print()方法
}
A
B

Receiver 可以是類型的值或者指針

package main
import (
    "fmt"
)
type A struct {
    Name string
}
type B struct {
    Name string
}
//定義一個鏈接到struct A的指針類型的方法Print
func(a *A)Print(){ //局部變量a的接受者類型是指針類型struct:A,所以定義的方法print是指針類型struct A
    a.Name = "AAA" //引用類型是指針類型拷貝,操作了內存中對象,所以a.Name = "AAA"保存到內存中
    fmt.Println("A")
}
func(b B)Print(){
    b.Name="BB" //值類型以值傳遞,只是得到一個拷貝副本,結束方法之後失效,打印空
    fmt.Println("B")
}
func main(){
    a :=A{} //聲明一個結構A
    a.Print()//然後a調用Print方法,打印結果A
    fmt.Println(a.Name)
    b :=B{}
    b.Print()
    fmt.Println(b.Name)
}
A
AAA
B

不存在方法重載
可以使用值或指針來調用方法,編譯器會自動完成轉換

package main
import (
    "fmt"
)
type TZ int //可以爲任何類型綁定方法Print(),非常靈活,可以對int類型做高級的綁定定義等
func (a *TZ)Print(){
    fmt.Println("TZ")
}
func main(){
    var a TZ
    a.Print()
}
TZ //這裏打印TZ

從某種意義上來說,方法是函數的語法,因爲receiver其實就是
方法所接收的第1個參數(Method Value vs. Method Expression)

package main
import (
    "fmt"
)
type TZ int

func (a *TZ)Print(){
    fmt.Println("TZ")
}
func main(){
    var a TZ
    a.Print()//Method Value,已經聲明瞭receiver a,通過類似類的方法調用的形式
    (*TZ).Print(&a)//Method Expression,通過類型(*TZ),把變量&a作爲receiver(第一個參數)傳給對應的Print方法,而不是類型變量調用方法
}
TZ
TZ

如果外部結構和嵌入結構存在同名方法,則優先調用外部結構的方法
類型別名不會擁有底層類型所附帶的方法

方法訪問權限的問題:
方法可以調用結構中的非公開字段,同一包package中方法訪問權限認爲公開的,不是私有的,相對其他包package才認爲是私有字段:

package main
import (
    "fmt"
)
type A struct {
    name string
}

func (a *A)Print(){
    a.name = "123"
    fmt.Println(a.name)
}
func main() {
    a:=A{}
    a.Print()
    fmt.Println(a.name)
    }
    輸出:
    123
    123

根據爲結構增加方法的知識,嘗試聲明一個底層類型爲int的類型,
並實現調用某個方法就遞增100。0+100=100
如:a:=0,調用a.Increase()之後,a從0變成100。

package main
import (
    "fmt"
)
type  TZ int

func (tz *TZ)Increase(num int){
    *tz += TZ(num)//這裏需要經num強制類型轉換爲TZ型,否則報錯,因爲TZ和int是不同的類型
}
func main() {
    var a TZ //聲明a是TZ類型,a的初始值是0
    a.Increase(100)
    fmt.Println(a)
    }
    100

48.接口interface

接口是一個或多個方法簽名的集合
只要某個類型擁有該接口的所有方法簽名,即算實現該接口,無需顯示聲明實現了哪個接口,這稱爲 Structural Typing

package main
import (
    "fmt"
)
type  USB interface{ //定義一個USB接口
    Name() string //返回USB名稱
    Connect()     //連接的方法
}

type PhoneConnecter struct{ //定義手機連接器結構,有一個name變量
    name string
}
//怎麼樣用結構讓USB實現呢?實現就是爲結構添加方法,對應USB中的Name()
func (pc PhoneConnecter)Name() string  { //定義一個receiver是PhoneConnecter結構的Name()方法,返回一個string類型的pc.name
    return pc.name
}
//怎麼樣用結構讓USB實現呢?實現就是爲結構添加方法,對應USB中的Connect()
func (pc PhoneConnecter)Connect(){ //定義一個receiver是PhoneConnecter結構的Connect()方法,打印pc.name
    fmt.Println("Connect",pc.name)
}

func main(){
    var a USB //定義一個USB接口
    a = PhoneConnecter{"PhoneConnecter"}//初始化一個name=PhoneConnecter的手機連接器(具有接口USB屬性)
    a.Connect()
}
Connect PhoneConnecter

package main
import (
    "fmt"
)
type  USB interface{ //定義一個USB接口
    Name() string //返回USB名稱
    Connect()     //連接的方法
}

type PhoneConnecter struct{ //定義手機連接器結構,有一個name變量
    name string
}
//怎麼樣用結構讓USB實現呢?實現就是爲結構添加方法,對應USB中的Name()
func (pc PhoneConnecter)Name() string  { //定義一個receiver是PhoneConnecter結構的Name()方法,返回一個string類型的pc.name
    return pc.name
}
//怎麼樣用結構讓USB實現呢?實現就是爲結構添加方法,對應USB中的Connect()
func (pc PhoneConnecter)Connect(){ //定義一個receiver是PhoneConnecter結構的Connect()方法,打印pc.name
    fmt.Println("Connect",pc.name)
}
func Disconnect(usb USB){
    fmt.Println("Disconnected")
}

func main(){
    //var a USB 這裏省略定義一個USB接口,因爲PhoneConnecter已經具備了USB的Name屬性和Connect方法,就已經實現了USB接口,可以直接調用,也可以Disconnect
    a := PhoneConnecter{"PhoneConnecter"}//初始化一個name=PhoneConnecter的手機連接器(具有接口USB屬性)
    a.Connect()
    Disconnect(a)
}
Connect PhoneConnecter
Disconnected

接口只有方法聲明,沒有實現,沒有數據字段
接口可以匿名嵌入其它接口,或嵌入到結構中
ok,pattern模式,利用多返回值特性,第一個返回值pc是轉換後的結果,第二個返回值是bool型,用於判斷是否轉換成功?是否是期望的類型?如果是第一個值就返回一個有意義的值,第二值爲false,第一個值是0值或者空。

package main
import (
    "fmt"
)
type  USB interface{ //定義一個USB接口
    Name() string //返回USB名稱
    Connecter     //連接的方法
}
type Connecter interface{ //這裏的Connecter具備Connect()方法,所以USB中直接嵌入Connecter
    Connect()
}
type PhoneConnecter struct{ //定義手機連接器結構,有一個name變量
    name string
}
func (pc PhoneConnecter)Name() string  { //定義一個receiver是PhoneConnecter結構的Name()方法,返回一個string類型的pc.name
    return pc.name
}
func (pc PhoneConnecter)Connect(){ //定義一個receiver是PhoneConnecter結構的Connect()方法,打印pc.name
    fmt.Println("Connect",pc.name)
}
//這裏只是單純的disconnected,但是不知道誰斷開了連接,不知道斷開的是不是一個PhoneConnecter,怎麼辦呢?
func Disconnect(usb USB){//利用多返回值特性,第一個返回值pc是轉換後的結果,第二個返回值是bool型,用於判斷是否轉換成功,是否是需要的類型,如果是第一個值就返回一個有意義的值,第二值爲false,第一個值是0值或者空。
    if pc,ok :=usb.(PhoneConnecter);ok { //ok,pattern模式,類型判斷USB是否是PhoneConnecter結構?如果成立ok=true,打印pc.name
        fmt.Println("Disconnected",pc.name)
        return
    }
    fmt.Println("Unknow device")
}
func main(){
    //var a USB //定義一個USB接口
    a := PhoneConnecter{"PhoneConnecter"}//初始化一個name=PhoneConnecter的手機連接器(具有接口USB屬性)
    a.Connect()
    Disconnect(a) //這裏a類型傳給pc,所以pc的類型是PhoneConnecter類型,與usb.(PhoneConnecter)相同,返回true,if判斷成立
}
Connect PhoneConnecter
Disconnected PhoneConnecter

49.類型斷言

通過類型斷言的ok pattern可以判斷接口中的數據類型
使用type switch則可針對空接口進行比較全面的類型判斷

package main
import (
    "fmt"
)
type empty interface{

}
type  USB interface{ //定義一個USB接口
    Name() string //返回USB名稱
    Connecter     //連接的方法
}
type Connecter interface{ //這裏的Connecter具備Connect()方法,所以USB中直接嵌入Connecter
    Connect()
}
type PhoneConnecter struct{ //定義手機連接器結構,有一個name變量
    name string
}
func (pc PhoneConnecter)Name() string  { //定義一個receiver是PhoneConnecter結構的Name()方法,返回一個string類型的pc.name
    return pc.name
}
func (pc PhoneConnecter)Connect(){ //定義一個receiver是PhoneConnecter結構的Connect()方法,打印pc.name
    fmt.Println("Connect",pc.name)
}
func Disconnect(usb interface{}){
    switch v := usb.(type){  //這裏使用高效簡便的type switch方法判斷局部變量v的類型
    case PhoneConnecter:
        fmt.Println("Disconnected",v.name)
    default:
        fmt.Println("Unknown device")
    }
}
func main(){
    //var a USB //定義一個USB接口
    a := PhoneConnecter{"PhoneConnecter"}//初始化一個name=PhoneConnecter的手機連接器(具有接口USB屬性)
    a.Connect()
    Disconnect(a)
}
Connect PhoneConnecter
Disconnected PhoneConnecter

50.接口轉換

可以將擁有超集的接口轉換爲子集的接口
可以把USB轉換爲Connecter方法,因爲USB還包含了name屬性

package main
import (
    "fmt"
)
type  USB interface{ //定義一個USB接口
    Name() string //返回USB名稱
    Connecter     //連接的方法
}
type Connecter interface{ //這裏的Connecter具備Connect()方法,所以USB中直接嵌入Connecter
    Connect()
}
type PhoneConnecter struct{ //定義手機連接器結構,有一個name變量
    name string
}
func (pc PhoneConnecter)Name() string  { //定義一個receiver是PhoneConnecter結構的Name()方法,返回一個string類型的pc.name
    return pc.name
}
func (pc PhoneConnecter)Connect(){ //定義一個receiver是PhoneConnecter結構的Connect()方法,打印pc.name
    fmt.Println("Connect",pc.name)
}

func main(){
    b := PhoneConnecter{"PhoneConnecter"}//初始化一個name=PhoneConnecter的手機連接器b(天生實現了接口USB接口),因爲b具備Name屬性和Connect()方法
    b.Connect()
    fmt.Println(b.name)
    var a Connecter
    a=Connecter(b)//強制類型轉換,將PhoneConnecter的pc轉換爲Connecter,connect只有Connect()方法,沒有name屬性
    a.Connect()
    //fmt.Println(a.name) 這裏打印出錯,因爲Connecter沒有name屬性
}
Connect PhoneConnecter
PhoneConnecter
Connect PhoneConnecter

將對象賦值給接口時,會發生拷貝,而接口內部存儲的是指向這個複製品的指針,既無法修改複製品的狀態,也無法獲取指針

package main
import (
    "fmt"
)
type  USB interface{ //定義一個USB接口
    Name() string //返回USB名稱
    Connecter     //連接的方法
}
type Connecter interface{ //這裏的Connecter具備Connect()方法,所以USB中直接嵌入Connecter
    Connect()
}
type PhoneConnecter struct{ //定義手機連接器結構,有一個name變量
    name string
}
func (pc PhoneConnecter)Name() string  { //定義一個receiver是PhoneConnecter結構的Name()方法,返回一個string類型的pc.name
    return pc.name
}
func (pc PhoneConnecter)Connect(){ //定義一個receiver是PhoneConnecter結構的Connect()方法,打印pc.name
    fmt.Println("Connect",pc.name)
}
func main(){
    b := PhoneConnecter{"PhoneConnecter"}//初始化一個name=PhoneConnecter的手機連接器b(天生實現了接口USB接口),因爲b具備Name屬性和Connect()方法
    var a Connecter
    a=Connecter(b)//強制類型轉換,將PhoneConnecter的pc轉換爲Connecter,connect只有Connect()方法,沒有name屬性
    a.Connect()
    b.name="pc"
    a.Connect() //這裏完全忽視了我們的修改,因爲拿到的是一個拷貝
}
Connect PhoneConnecter
Connect PhoneConnecter

只有當接口存儲的類型和對象都爲nil時,接口才等於nil

package main
import "fmt"
func main(){
    var a interface{} //a=nil
    fmt.Println(a==nil)

    var p *int = nil
    a = p //a指向的對象是nil,但是本身是一個指向int型的指針,不是nil,所以打印false
    fmt.Println(a==nil)
}
true
false

接口調用不會做receiver的自動轉換
接口同樣支持匿名字段方法
接口也可實現類似OOP中的多態
空接口可以作爲任何類型數據的容器

51.反射reflection

反射可大大提高程序的靈活性,使得 interface{} 有更大的發揮餘地
反射使用 TypeOf 和 ValueOf 函數從接口中獲取目標字段信息和類型信息

package main
import (
    "fmt"
    "reflect"
)
type User struct {
    Id int
    Name string
    Age int
}
func (u User) Hello() {
    fmt.Println("Hello world")
}
func Info(o interface{}){
    t := reflect.TypeOf(o)
    fmt.Println("Type:",t.Name())

    v:=reflect.ValueOf(o)
    fmt.Println("Fields:")

    for i :=0; i<t.NumField();i++{
        f := t.Field(i)
        val := v.Field(i).Interface()
        fmt.Printf("%6s: %v = %v\n",f.Name,f.Type,val)
    }
}
func main(){
    u := User{1,"OK",12}
    Info(u)
}
Type: User
Fields:
    Id: int = 1
  Name: string = OK
   Age: int = 12

獲取方法信息?

package main
import (
    "fmt"
    "reflect"
)
type User struct {
    Id int
    Name string
    Age int
}
func (u User) Hello() {
    fmt.Println("Hello world")
}
func Info(o interface{}){
    t := reflect.TypeOf(o)
    fmt.Println("Type:",t.Name())

    v:=reflect.ValueOf(o)
    fmt.Println("Fields:")

    for i :=0; i<t.NumField();i++{
        f := t.Field(i)
        val := v.Field(i).Interface()
        fmt.Printf("%6s: %v = %v\n",f.Name,f.Type,val)
    }
    for i :=0;i < t.NumMethod();i++{//定義獲取方法信息
        m := t.Method(i)
        fmt.Printf("%6s: %v\n",m.Name,m.Type)
    }
}
func main(){
    u := User{1,"OK",12}
    Info(u)
}
Type: User
Fields:
    Id: int = 1
  Name: string = OK
   Age: int = 12
 Hello: func(main.User) //獲取方法信息

上面穿的u是值拷貝方式,如果傳遞的是一個指針,報錯了,該怎麼判斷類型是否符合預期reflect.struct呢?
package main
import (
    "fmt"
    "reflect"
)
type User struct {
    Id int
    Name string
    Age int
}
func (u User) Hello() {
    fmt.Println("Hello world")
}
func Info(o interface{}){
    t := reflect.TypeOf(o)
    fmt.Println("Type:",t.Name())

    if k:=t.Kind();k !=reflect.Struct{//取出類型,判斷是否等於reflect.Struct,不是就直接return退出
        fmt.Println("XX")
        return
    }
    v:=reflect.ValueOf(o)
    fmt.Println("Fields:")

    for i :=0; i<t.NumField();i++{
        f := t.Field(i)
        val := v.Field(i).Interface()
        fmt.Printf("%6s: %v = %v\n",f.Name,f.Type,val)
    }
    for i :=0;i < t.NumMethod();i++{
        m := t.Method(i)
        fmt.Printf("%6s: %v\n",m.Name,m.Type)
    }
}
func main(){
    u := User{1,"OK",12}
    Info(&u)
}
package main
import (
    "fmt"
    "reflect"
)
type User struct {
    Id int
    Name string
    Age int
}
func (u User) Hello() {
    fmt.Println("Hello world")
}
func Info(o interface{}){
    t := reflect.TypeOf(o)
    fmt.Println("Type:",t.Name())

    if k:=t.Kind();k !=reflect.Struct{//取出類型,判斷是否等於reflect.Struct,不是就直接return退出
        fmt.Println("XX")
        return
    }
    v:=reflect.ValueOf(o)
    fmt.Println("Fields:")

    for i :=0; i<t.NumField();i++{
        f := t.Field(i)
        val := v.Field(i).Interface()
        fmt.Printf("%6s: %v = %v\n",f.Name,f.Type,val)
    }
    for i :=0;i < t.NumMethod();i++{
        m := t.Method(i)
        fmt.Printf("%6s: %v\n",m.Name,m.Type)
    }
}
func main(){
    u := User{1,"OK",12}
    Info(&u)
}
Type: 
XX

反射會將匿名字段作爲獨立字段(匿名字段本質)

package main
import (
    "fmt"
    "reflect"
)
type User struct {
    Id int
    Name string
    Age int
}
type Manager struct {
    User //反射會將匿名字段作爲獨立字段處理,這裏輸入User等於輸入User User初始化
    title string
}

func main(){
    m := Manager{User:User{1,"OK",12},title:"13"}
    t:=reflect.TypeOf(m)//使用typeof取出類型
    fmt.Printf("%#v\n",t.Field(0))//如何取出匿名字段呢?go語言中使用序號形式取出匿名字段,user是匿名字段,true
    fmt.Printf("%#v\n",t.FieldByIndex([]int{0,0}))//如何取出匿名字段呢?go語言中使用序號形式取出匿名字段,id不是匿名字段,打印false
    fmt.Printf("%#v\n",t.FieldByIndex([]int{0,1}))//如何取出匿名字段呢?go語言中使用序號形式取出匿名字段,name不是匿名字段,打印false
    fmt.Printf("%#v\n",t.Field(1))//如何取出匿名字段呢?go語言中使用序號形式取出匿名字段,title不是匿名字段,打印false

}
reflect.StructField{Name:"User", PkgPath:"", Type:(*reflect.rtype)(0x10b4b40), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:true}
reflect.StructField{Name:"Id", PkgPath:"", Type:(*reflect.rtype)(0x10a54e0), Tag:"", Offset:0x0, Index:[]int{0}, Anonymous:false}
reflect.StructField{Name:"Name", PkgPath:"", Type:(*reflect.rtype)(0x10a5b60), Tag:"", Offset:0x8, Index:[]int{1}, Anonymous:false}
reflect.StructField{Name:"title", PkgPath:"main", Type:(*reflect.rtype)(0x10a5b60), Tag:"", Offset:0x20, Index:[]int{1}, Anonymous:false}

想要利用反射修改對象內容狀態,前提是 interface.data 是 settable,即 pointer-interface

package main
import (
    "fmt"
    "reflect"
)
func main(){
    x:=123
    v:=reflect.ValueOf(&x)//要求類型是reflect.Struct
    v.Elem().SetInt(999) //通過elem取v的內容,並重新賦值
    fmt.Println(x)
}
999

package main
import (
    "fmt"
    "reflect"
)
type User struct{
    Id int
    Name string
    Age int
}
func Set(o interface{})  {
    v := reflect.ValueOf(o)//先取出o的值
    if v.Kind()==reflect.Ptr && !v.Elem().CanSet(){//先判斷的是不是point interface,判斷對象是否可被修改?返回bool值告訴編譯器是否可以被修改
        fmt.Println("XXX")//如果上述條件不滿足,return退出
        return
    }else {
        v = v.Elem()//如果兩個條件都滿足,重新賦值實際對象
    }
    if f := v.FieldByName("Name");f.Kind() == reflect.String{ //修改username,先取出字段,判斷類型是否是reflect.String,可以用type switch判斷
        f.SetString("BYEBYE")//修改username
    }
}
func main(){
    u := User{1,"OK",12}
    Set(&u)
    fmt.Println(u)
}
{1 BYEBYE 12} //name從OK變爲BYEBYE,修改成功了

怎麼判斷真的找到了name這個字段,並且修改了呢?

package main
import (
    "fmt"
    "reflect"
)
type User struct{
    Id int
    Name string
    Age int
}
func Set(o interface{})  {
    v := reflect.ValueOf(o)//先取出o的值
    if v.Kind()==reflect.Ptr && !v.Elem().CanSet(){//先判斷的是不是point interface,判斷對象是否可被修改?返回bool值告訴編譯器是否可以被修改
        fmt.Println("XXX")//如果上述條件不滿足,return退出
        return
    }else {
        v = v.Elem()//如果兩個條件都滿足,重新賦值實際對象
    }
    f := v.FieldByName("Name123")//先取name字段,name123根本找不到
    if !f.IsValid(){//判斷name字段是否爲空,如果爲空,返回reflect.value=value{} !null=true
        fmt.Println("BAD")
        return
    }
    if  f.Kind() == reflect.String{ //修改username,先取出字段,判斷類型是否是reflect.String,可以用type switch判斷
        f.SetString("BYEBYE")//修改username
    }
}
func main(){
    u := User{1,"OK",12}
    Set(&u)
    fmt.Println(u)
}
BAD
{1 OK 12}

package main
import (
    "fmt"
    "reflect"
)
type User struct{
    Id int
    Name string
    Age int
}
func Set(o interface{})  {
    v := reflect.ValueOf(o)//先取出o的值
    if v.Kind()==reflect.Ptr && !v.Elem().CanSet(){//先判斷的是不是point interface,判斷對象是否可被修改?返回bool值告訴編譯器是否可以被修改
        fmt.Println("XXX")//如果上述條件不滿足,return退出
        return
    }else {
        v = v.Elem()//如果兩個條件都滿足,重新賦值實際對象
    }
    f := v.FieldByName("Name")//先取name字段,name找到
    if !f.IsValid(){ //判斷name字段是否爲空,如果不爲空,返回reflect.value=非空 !true=false,這裏的return不會被執行
        fmt.Println("BAD")
        return
    }
    if  f.Kind() == reflect.String{ //修改username,先取出字段,判斷類型是否是reflect.String,可以用type switch判斷
        f.SetString("BYEBYE")//修改username
    }
}
func main(){
    u := User{1,"OK",12}
    Set(&u)
    fmt.Println(u)
}
{1 BYEBYE 12}

通過反射可以“動態”調用方法
定義一個結構,通過反射來打印其信息,並調用方法。

package main
import (
    "fmt"
)
type User struct{
    Id int
    Name string
    Age int
}

func (u User) Hello(name string)  {
    fmt.Println("Hello",name,",my name is",u.Name)
}
func main(){
    u :=User{1,"OK",12}
    u.Hello("joe")
}
Hello joe ,my name is OK

package main
import (
    "fmt"
    "reflect"
)
type User struct{
    Id int
    Name string
    Age int
}
func (u User) Hello(name string)  {
    fmt.Println("Hello",name,",my name is",u.Name)
}
func main(){
    u :=User{1,"OK",12}
    v:=reflect.ValueOf(u)
    mv :=v.MethodByName("Hello")
    args := []reflect.Value{reflect.ValueOf("joe")}
    mv.Call(args)
}
Hello joe ,my name is OK

52.併發concurrency

很多人都是衝着 Go 大肆宣揚的高併發而忍不住躍躍欲試,但其實從源碼的解析來看,goroutine 只是由官方實現的超級“線程池”而已。不過話說回來,每個實例 4-5KB 的棧內存佔用和由於實現機制而大幅減少的創建和銷燬開銷,是製造 Go 號稱的高併發的根本原因。另外,goroutine 的簡單易用,也在語言層面上給予了開發者巨大的便利。

併發不是並行:Concurrency Is Not Parallelism
併發主要由切換時間片來實現“同時”運行,在並行則是直接利用
多核實現多線程的運行,但 Go 可以設置使用核數,以發揮多核計算機
的能力。

package main
import (
    "fmt"
    "time"
)
func main(){
    go Go()//啓動gorutine
    time.Sleep(2 * time.Second)//main函數sleep的時候,啓動goruntine
}
func Go(){
    fmt.Println("Go GO GO !!!")
}
Go GO GO !!!
最好使用匿名函數,gorutine,結合閉包,就很便捷了

如果運行某個函數超過2s,不可能無限制的sleep等待,怎麼辦呢?
使用channel

Goroutine 奉行通過通信來共享內存,而不是共享內存來通信。

53.Channel

Channel 是 goroutine 溝通的橋樑,大都是阻塞同步的
通過 make 創建,close 關閉
Channel 是引用類型

package main
import (
    "fmt"
)
func main(){
    c:=make(chan bool)//創建一個chan,bool類型
    //此時怎麼對chan進行讀取寫入的操作呢?
    go func(){
        fmt.Println("GO GO GO!!")
        c <- true //把對象c存起來,存的值是bool類型的true
    }()
    //啓動goroutine之後,mian執行到這裏,因爲沒有將內容放進去,所以阻塞了,一直在等待,直到輸出GO GO GO!!執行完,把true存到chan中,這時候取到值,才能繼續執行程序
    <-c//取chan值的操作,一直等待,直到能chan值才結束main
}
GO GO GO!!

可以使用 for range 來迭代不斷操作 channel

package main
import (
    "fmt"
)
func main(){
    c:=make(chan bool)//創建一個chan,bool類型
    //此時怎麼對chan進行讀取寫入的操作呢?
    go func(){
        fmt.Println("GO GO GO!!")
        c <- true //把對象c存起來,存的值是bool類型的true
        close(c) //這裏需要關閉chan,然後main執行完成,退出,否則go routine都在等待中,死鎖了
    }()
    //啓動goroutine之後,mian執行到這裏,因爲沒有將內容放進去,所以阻塞了,一直在等待,直到輸出GO GO GO!!執行完,把true存到chan中,這時候取到值,才能繼續執行程序
    for v := range c{
        fmt.Println(v) //使用for range 迭代chan,等待chan有個值進去,然後打印v的值true,並沒有退出,進行下一次for range等待
    }
}
GO GO GO!!
true

package main
import (
    "fmt"
)
func main(){
    c:=make(chan bool)//創建一個chan,bool類型
    //此時怎麼對chan進行讀取寫入的操作呢?
    go func(){
        fmt.Println("GO GO GO!!")
        c <- true //把對象c存起來,存的值是bool類型的true
        //這裏需要關閉chan,然後main執行完成,退出,否則go routine都在等待中,死鎖了
    }()
    //啓動goroutine之後,mian執行到這裏,因爲沒有將內容放進去,所以阻塞了,一直在等待,直到輸出GO GO GO!!執行完,把true存到chan中,這時候取到值,才能繼續執行程序
    for v := range c{
        fmt.Println(v) //使用for range 迭代chan,等待chan有個值進去,然後打印v的值true,並沒有退出,進行下一次for range等待
    }
}
GO GO GO!!
fatal error: all goroutines are asleep - deadlock! //這裏死鎖了,崩潰退出
true
goroutine 1 [chan receive]:
main.main()
    /Users/daixuan/qbox/test/test.go:14 +0xcd

可以設置單向或雙向通道
make雙向通道可以存也可以取,單項通道是隻能存或者只能取,只能夠寫不能讀

可以設置緩存大小,在未被填滿前不會發生阻塞

有緩存和無緩存區別是什麼?

那就是一個是同步的 一個是非同步的

怎麼說?比如
c1:=make(chan int) 無緩衝
c2:=make(chan int,1) 有緩衝
c1<-1
無緩衝的 不僅僅是 向 c1 通道放 1 而是 一直要有別的攜程 <-c1 接手了 這個參數,那麼c1<-1纔會繼續下去,要不然就一直阻塞着
而 c2<-1 則不會阻塞,因爲緩衝大小是1 只有當 放第二個值的時候 第一個還沒被人拿走,這時候纔會阻塞。
打個比喻
無緩衝的 就是一個送信人去你家門口送信 ,你不在家 他不走,你一定要接下信,他纔會走。無緩衝保證信能到你手上
有緩衝的 就是一個送信人去你家仍到你家的信箱 轉身就走 ,除非你的信箱滿了 他必須等信箱空下來。有緩衝的 保證 信能進你家的郵箱

注意:
chan沒有緩存的話,注意“取chan操作”應該在前,“放chan操作”放在後

package main
import (
    "fmt"
)
func main(){
    c:=make(chan bool)//創建一個chan,bool類型
    //此時怎麼對chan進行讀取寫入的操作呢?
    go func(){
        fmt.Println("GO GO GO!!")
        c <- true //把對象c存起來,存的值是bool類型的true,放chan操作放在後 
    }()
    <-c //這是取chan操作”放在前,沒有東西可以取,只能等待快遞送信過來
}
GO GO GO!!

chan如果有緩存的話,注意“取chan操作”應該在後,“放chan操作”放在前

package main
import (
    "fmt"
)
func main(){
    c:=make(chan bool, 1)//創建一個chan,bool類型 1
    go func(){
        fmt.Println("GO GO GO!!")
        c <- true //把對象c存起來,存的值是bool類型的true
    }()
    <-c //這是取chan操作”放在前,信箱(緩存)沒有東西可以取,只能等送信的來,還是直接結束呢?測試結果是直接結束退出
}
GO GO GO!!//這裏輸出GO GO GO沒有問題

此時把c <- true和c <- 對調一下,沒有輸出結果
package main
import (
    "fmt"
)
func main(){
    c:=make(chan bool,1)//創建一個chan,bool類型,沒有緩存
    //此時怎麼對chan進行讀取寫入的操作呢?
    go func(){//main函數已經結束了,不會等待該線程(gotoutine)執行了,不打印GO GO GO
        fmt.Println("GO GO GO!!")
        <-c
    }()
        c <- true //快遞來了(先存),有信箱(緩存),所以快遞直接放到信箱裏面走人,main結束,不打印GO GO GO
    }
這裏輸出爲空

如果把bool 1的緩存1刪除,又輸出GO GO GO了
package main
import (
    "fmt"
)
func main(){
    c:=make(chan bool)//創建一個chan,bool類型,沒有緩存
    //此時怎麼對chan進行讀取寫入的操作呢?
    go func(){
        fmt.Println("GO GO GO!!")//線程會先打印出 Go Go GO
        <-c //直到有人出來取快遞了,才能結束快遞員的等待
    }()
        c <- true //快遞來了(先存),沒有信箱(緩存),同時沒有人取,所以快遞一直在等等待
    }

GO GO GO!!

什麼原因呢?
有緩存的時候直接讀出bool值,有緩存是異步,無緩存是同步阻塞的。

舉個新手及其容易犯的錯誤,併發執行的時候gorutine並沒有按照順序執行,而是隨機執行,以第9個goruntine執行完作爲判斷標準,並不合理,因爲9執行完成的時候,只有5,1也執行完成,其他的goroutine並沒有執行完成。

package main
import (
    "fmt"
    "runtime"
)
func main(){
    runtime.GOMAXPROCS(runtime.NumCPU())
    c := make(chan bool)
    for i := 0; i<10; i++{
        go Go(c,i)
    }
    <-c
}
func Go(c chan bool,index int){
    a := 1
    for i :=0;i< 10000000;i++{
        a +=i
    }
    fmt.Println(index,a)
    if index == 9{
        c <- true
    }
}
5 49999995000001
1 49999995000001
9 49999995000001

解決辦法:
1、取10次chan值,緩存10次c := make(chan bool,10),這裏緩存應該大於等於10,防止突然10次請求一起來,如果緩存是8,就不夠用,有2個需要等待,影響性能,但是結果ok(測試一致)

package main
import (
    "fmt"
    "runtime"
)
func main(){
    runtime.GOMAXPROCS(runtime.NumCPU())
    c := make(chan bool,10)
    for i := 0; i<10; i++{
        go Go(c,i)
    }
    for i :=0;i<10;i++ {//取10次chan值
        <-c
    }
}
func Go(c chan bool,index int){
    a := 1
    for i :=0;i< 10000000;i++{
        a +=i
    }
    fmt.Println(index,a)
        c <- true
}

3 49999995000001
9 49999995000001
0 49999995000001
1 49999995000001
4 49999995000001
6 49999995000001
7 49999995000001
8 49999995000001
2 49999995000001
5 49999995000001

通過sync設置waitGroup(10)任務數,每完成一個任務,標記wg.Done(),總任務數減一,最終完成所有任務。

package main
import (
    "fmt"
    "runtime"
    "sync"
)
func main(){
    runtime.GOMAXPROCS(runtime.NumCPU())
    wg := sync.WaitGroup{}
    wg.Add(10)
    for i := 0; i<10; i++{
        go Go(&wg,i)
    }
    wg.Wait()
}
func Go(wg *sync.WaitGroup ,index int){
    a := 1
    for i :=0;i< 10000000;i++{
        a +=i
    }
    fmt.Println(index,a)
        wg.Done()//標記一次任務完成
}
5 49999995000001
3 49999995000001
1 49999995000001
6 49999995000001
2 49999995000001
9 49999995000001
4 49999995000001
0 49999995000001
8 49999995000001
7 49999995000001

54.Select

可處理一個或多個 channel 的發送與接收

package main
import (
    "fmt"
)
func main(){
    c1,c2 := make(chan int),make(chan string)
    o :=make(chan bool,2)//信號通道,通知裏面的東西是否都被處理完了,緩存爲2
    go func(){
        for {         //這裏使用for的無限循環,selct,不斷的信息接收和發送操作
            select {
            case v,ok := <- c1: //
                if !ok { //如果chan被關閉,ok!=true,退出slect
                    o<- true//當c1或者c2其中任何一個關閉,傳true進去,讀到<-o,main函數退出
                    break
                }
                fmt.Println("c1",v) //如果沒有被關閉,打印傳進來的值
            case v,ok := <-c2:
                if !ok{
                    o<-true
                    break
                }
                fmt.Println("c2",v)
            }
        }
    }()
    c1 <-1
    c2 <-"hi"
    c1 <-3
    c2 <-"hello"
    close(c1)//關閉c1和c2
    close(c2)
    for i :=0;i<2;i++{ //通信chan的緩存爲2,取兩次o的值
        <-o
    }
}

c1 1
c2 hi
c1 3
c2 hello

同時有多個可用的 channel時按隨機順序處理

package main
import (
    "fmt"
)
func main(){
        c:=make(chan int)
        go func() {
            for v := range c {
                fmt.Println(v)
            }
        }()
        for {
            select {
                case c <- 0:
                case c <- 1:
            }
        }
}
1 //0或者1隨機打印
0
0
0
1
1
1
1
1
0
0
1
0
0
1
1
0

可用空的 select 來阻塞 main 函數
可設置超時

package main
import (
    "fmt"
    "time"
)
func main(){
        c := make(chan bool)
        select {
        case v := <-c:
            fmt.Println(v)
        case <- time.After(3 * time.Second):
            fmt.Println("Timeout")
        }
}
3s後打印
Timeout

創建一個 goroutine,與主線程按順序相互發送信息若干次並打印

package main
import (
    "fmt"
)
var c chan string //創建一個全局變量的chan

//定義一個接受goroutine,先接收,再發送
func Pingpong()  {//定義一個goroutine,//先接受c,再發送From Pingpong:Hi....
    i := 0
    for { //3、執行到這裏,無限循環,等待chan有內容傳遞進去爲止,
        fmt.Println(<-c)//6、這裏從chan取到內容:From main:Hello,打印出來
        c<-fmt.Sprintf("From Pingpong: Hi,#%d",i)//7、把From Pingpong:傳到chan中去
        i++//8、i的值加1
    }
}
func main(){
    c=make(chan string)//1、初始化chan
    go Pingpong()  //2、啓動goroutine:Pingpong,進入for無限循環,然後等待接受
    //這裏相反,先發送再接受
    for i := 0;i<10;i++{
        c<-fmt.Sprintf("From main:Hello, #%d",i)//4、這裏先向全局chan中放字符串From main:Hello
        fmt.Println(<-c)//5、然後等待接受,等待gorutine從取出chan數據後再放回去 9、這裏取到chan中的值From Pingpong:然後打印出來
    }
}

From main:Hello, #0
From Pingpong: Hi,#0
From main:Hello, #1
From Pingpong: Hi,#1
From main:Hello, #2
From Pingpong: Hi,#2
From main:Hello, #3
From Pingpong: Hi,#3
From main:Hello, #4
From Pingpong: Hi,#4
From main:Hello, #5
From Pingpong: Hi,#5
From main:Hello, #6
From Pingpong: Hi,#6
From main:Hello, #7
From Pingpong: Hi,#7
From main:Hello, #8
From Pingpong: Hi,#8
From main:Hello, #9
From Pingpong: Hi,#9
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章