Go编程基础-学习2
Go 中的struct与C中的struct非常相似,并且Go没有class
使用 type <Name> struct{} 定义结构,名称遵循可见性规则
package mainimport "fmt"type persion struct { Name string Age int}func main() { a := persion{} a.Name="david" a.Age=13 fmt.Println(a)}{david 13}package mainimport "fmt"type persion struct { Name string Age int}func main() { a := persion{Name:"david"} fmt.Println(a)}{david 0} //0是int的初始值package mainimport "fmt"type persion struct { Name string Age int}func main() { a := persion{Name:"david",Age:13} fmt.Println(a)}{david 13}package mainimport "fmt"type persion struct { Name string Age int}func main() { a := persion{"david",28} fmt.Println(a)}{david 28}
struct也是一个值类型,传递的时候也是值拷贝
package mainimport "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 mainimport "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为13package mainimport "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 mainimport "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 mainimport "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 mainimport "fmt"func main() { a := struct{ Name string Age int }{ Name:"david", Age:19, } fmt.Println(a)}{david 19} 也可以定义指针类型的匿名结构package mainimport "fmt"func main() { a := &struct{ //a是没有名称的匿名结构,定义指针类型结构 Name string Age int }{ //需要对结构属性(字面值)赋值 Name:"david", Age:19, } fmt.Println(a)}&{david 19}匿名结构是否可以嵌套其他结构中呢?可以package mainimport "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 mainimport "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 mainimport "fmt"type person struct { string //这就是匿名字段,没有定义属性的名字,只是定义了类型 int}func main() { a := person{"david",19}//注意,赋值顺序是string、int,不能对调,否则报错 fmt.Println(a)}{david 19}
匿名结构也可以用于map的值
可以使用字面值对结构进行初始化
允许直接通过指针来读写结构成员
相同类型的成员可进行直接拷贝赋值
支持 == 与 !=比较运算符,但不支持 > 或 <
支持匿名字段,本质上是定义了以某个类型名为名称的字段
结构也是一种类型,相同的类型之间,变量可以进行赋值package mainimport "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}struct是一种类型,所以可以比较?a和b虽然内容包含相同,但是名称不同,就是不同类型,没有可比性,无法比较,所以报错,只有用相同的struct person才可以比较。package mainimport "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 mainimport "fmt"type person struct { Name string Age int}func main() { a := person{"david",19} b := person{"david",19} fmt.Println(a == b )}truepackage mainimport "fmt"type person struct { Name string Age int}func main() { a := person{"david",19} b := person{"david",20} fmt.Println(a == b )}false
嵌入结构作为匿名字段看起来像继承,但不是继承
可以使用匿名字段指针
package mainimport ( "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 mainimport ( "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 teacherhello.go:20:36: unknown field 'Sex' in struct literal of type student//这里报错了,因为初始化方法错误,应该怎么定义呢?方法1:human在teacher中被当做匿名字段,所以定义human:{Sex:0}package mainimport ( "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 mainimport ( "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 mainimport ( "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是什么呢?答案是Bpackage mainimport ( "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 Bpackage mainimport ( "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 mainimport ( "fmt")type A struct { Name string}type B struct { Name string}//定义一个链接到struct A的方法printfunc(a A)Print(){ //局部变量a的接受者类型是struct:A,所以定义的方法print是struct A的类型方法 fmt.Println("A")}func main(){ a :=A{} //声明一个结构A a.Print()//然后a调用Print方法,打印结果A}Apackage mainimport ( "fmt")type A struct { Name string}type B struct { Name string}//定义一个链接到struct A的方法printfunc(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()方法}AB
Receiver 可以是类型的值或者指针
package mainimport ( "fmt")type A struct { Name string}type B struct { Name string}//定义一个链接到struct A的指针类型的方法Printfunc(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)}AAAAB
不存在方法重载
可以使用值或指针来调用方法,编译器会自动完成转换
package mainimport ( "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 mainimport ( "fmt")type TZ intfunc (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方法,而不是类型变量调用方法}TZTZ
如果外部结构和嵌入结构存在同名方法,则优先调用外部结构的方法
类型别名不会拥有底层类型所附带的方法
方法访问权限的问题:
方法可以调用结构中的非公开字段,同一包package中方法访问权限认为公开的,不是私有的,相对其他包package才认为是私有字段:
package mainimport ( "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 mainimport ( "fmt")type TZ intfunc (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 mainimport ( "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 PhoneConnecterpackage mainimport ( "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 PhoneConnecterDisconnected
接口只有方法声明,没有实现,没有数据字段
接口可以匿名嵌入其它接口,或嵌入到结构中
ok,pattern模式,利用多返回值特性,第一个返回值pc是转换后的结果,第二个返回值是bool型,用于判断是否转换成功?是否是期望的类型?如果是第一个值就返回一个有意义的值,第二值为false,第一个值是0值或者空。
package mainimport ( "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 PhoneConnecterDisconnected PhoneConnecter
49.类型断言
通过类型断言的ok pattern可以判断接口中的数据类型
使用type switch则可针对空接口进行比较全面的类型判断
package mainimport ( "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 PhoneConnecterDisconnected PhoneConnecter
50.接口转换
可以将拥有超集的接口转换为子集的接口
可以把USB转换为Connecter方法,因为USB还包含了name属性
package mainimport ( "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 PhoneConnecterPhoneConnecterConnect PhoneConnecter
将对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,既无法修改复制品的状态,也无法获取指针
package mainimport ( "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 PhoneConnecterConnect PhoneConnecter
只有当接口存储的类型和对象都为nil时,接口才等于nil
package mainimport "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)}truefalse
接口调用不会做receiver的自动转换
接口同样支持匿名字段方法
接口也可实现类似OOP中的多态
空接口可以作为任何类型数据的容器
反射可大大提高程序的灵活性,使得 interface{} 有更大的发挥余地
反射使用 TypeOf 和 ValueOf 函数从接口中获取目标 类型信息和字段信息
package mainimport ( "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: UserFields: Id: int = 1 Name: string = OK Age: int = 12
获取方法信息?
package mainimport ( "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: UserFields: Id: int = 1 Name: string = OK Age: int = 12 Hello: func(main.User) //获取方法信息上面传的u是值拷贝方式,如果传递的是一个指针,报错了,该怎么判断类型是否符合预期reflect.struct呢?package mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "fmt" "reflect")func main(){ x:=123 v:=reflect.ValueOf(&x)//要求类型是reflect.Struct v.Elem().SetInt(999) //通过elem取v的内容,并重新赋值 fmt.Println(x)}999package mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 OKpackage mainimport ( "fmt" "reflect")type User struct { Id int Name string Age int}func (u User) HelloWorld(name string) { fmt.Println("Hello", name, ",my name is", u.Name)}func main() { u := User{1, "OK", 12} v := reflect.ValueOf(u) //ValueOf 获取字段信息 mv := v.MethodByName("HelloWorld") //与上面的方法HelloWorld对应 args := []reflect.Value{reflect.ValueOf("daixuan")}//这里是被调用的name mv.Call(args)}Hello daixuan ,my name is OK
52.并发concurrency
很多人都是冲着 Go 大肆宣扬的高并发而忍不住跃跃欲试,但其实从源码的解析来看,goroutine 只是由官方实现的超级“线程池”而已。不过话说回来,每个实例 4-5KB 的栈内存占用和由于实现机制而大幅减少的创建和销毁开销,是制造 Go 号称的高并发的根本原因。另外,goroutine 的简单易用,也在语言层面上给予了开发者巨大的便利。
并发不是并行:Concurrency Is Not Parallelism
并发主要由切换时间片来实现“同时”运行,在并行则是直接利用
多核实现多线程的运行,但 Go 可以设置使用核数,以发挥多核计算机
的能力。
package mainimport ( "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.ChannelChannel 是 goroutine 沟通的桥梁,大都是阻塞同步的
通过 make 创建,close 关闭
Channel 是引用类型
package mainimport ( "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 mainimport ( "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!!truepackage mainimport ( "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! //这里死锁了,崩溃退出truegoroutine 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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 499999950000011 499999950000019 49999995000001
解决办法:
1、取10次chan值,缓存10次c := make(chan bool,10),这里缓存应该大于等于10,防止突然10次请求一起来,如果缓存是8,就不够用,有2个需要等待,影响性能,但是结果ok(测试一致)
package mainimport ( "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 499999950000019 499999950000010 499999950000011 499999950000014 499999950000016 499999950000017 499999950000018 499999950000012 499999950000015 49999995000001
通过sync设置waitGroup(10)任务数,每完成一个任务,标记wg.Done(),总任务数减一,最终完成所有任务。
package mainimport ( "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 499999950000013 499999950000011 499999950000016 499999950000012 499999950000019 499999950000014 499999950000010 499999950000018 499999950000017 49999995000001
54.Select
可处理一个或多个 channel 的发送与接收
package mainimport ( "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 1c2 hic1 3c2 hello
同时有多个可用的 channel时按随机顺序处理
package mainimport ( "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随机打印0001111100100110
可用空的 select 来阻塞 main 函数
可设置超时
package mainimport ( "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 mainimport ( "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, #0From Pingpong: Hi,#0From main:Hello, #1From Pingpong: Hi,#1From main:Hello, #2From Pingpong: Hi,#2From main:Hello, #3From Pingpong: Hi,#3From main:Hello, #4From Pingpong: Hi,#4From main:Hello, #5From Pingpong: Hi,#5From main:Hello, #6From Pingpong: Hi,#6From main:Hello, #7From Pingpong: Hi,#7From main:Hello, #8From Pingpong: Hi,#8From main:Hello, #9From Pingpong: Hi,#9
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。