温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Go编程基础-学习2

发布时间:2020-07-31 18:25:33 来源:网络 阅读:1004 作者:1350368559 栏目:开发技术

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}

struct是一种类型,所以可以比较?
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) 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 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
向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI