Go 反射

目录

什么是反射

反射的弊端

reflect 包

Go 提供的反射方法

type Type 类型

type Kind 类型

TypeOf

ValueOf


什么是反射

​反射(reflection)是在 Java 出现后迅速流行起来的一种概念,通过反射可以获取丰富的类型信息,并可以利用这些类型信息做非常灵活的工作。大多数现代的高级语言都以各种形式支持反射功能,反射是把双刃剑,功能强大但代码可读性并不理想,若非必要并不推荐使用反射。

反射可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

反射的弊端

  • 代码难以阅读和维护。
  • 编译期间不能发现类型错误,有些bug只能在运行很长时间才能发现,可能造成不良后果。
  • 反射性能差,通常比正常代码慢一到两个数量级。在对性能要求高或大量反复调用的代码块里建议不要使用反射。

reflect 包

Go语言中的反射是由 reflect 包提供支持的,它定义了两个重要的类型 Type 和 Value 任意接口值在反射中都可以理解为由 reflect.Type 和 reflect.Value 两部分组成,并且 reflect 包提供了 reflect.TypeOf 和 reflect.ValueOf 两个函数来获取任意对象的 Value 和 Type。

Go 提供的反射方法

GoLang reflect 反射官网

type Type 类型

type Type interface {

    // 在内存中分配时,返回此类型值的对齐方式(以字节为单位)
    Align() int

    // 当用作结构体中的字段时,返回此类型值的对齐方式(以字节为单位)。
    FieldAlign() int

    // 返回结构体中的第 i 个方法
    Method(i int) Method

    // 返回结构体中指定的方法,并返回是否找到该方法的bool值
    MethodByName(string) (Method, bool)

    // 返回可访问的方法数量
    NumMethod() int

    // 返回结构体名称
    Name() string

    // 返回包路径
    PkgPath() string

    // 返回类型存储所占用的直接大小
    Size() uintptr

    // 返回类型的字符串表示形式。字符串表示可以使用缩短的包名称,并且不能保证在类型之间是唯一的。要测试类型标识,请直接比较类型。
    String() string

    // 返回此类型的特定种类
    Kind() Kind

    // 判断是否实现了指定的接口u
    Implements(u Type) bool

    // 判断类型的值是否可分配给u类型
    AssignableTo(u Type) bool

    // 判断类型的值是否可转换为u类型,即使返回true,也可能会宕机,转换类型(切片)长度小于被转换类型的长度可能会宕机
    ConvertibleTo(u Type) bool

    // 判断此类型的值是否具有可比性。即使Comparable返回true,这种比较仍可能引发宕机。例如,接口类型的值是可比较的,但如果它们的动态类型不可比较,则比较会死机
    Comparable() bool

    // 返回类型的字节大小
    Bits() int

    // 返回通道类型的方向。如果这个类型的Kind不是Chan,会宕机
    ChanDir() ChanDir

    // 判断函数输入类型
    IsVariadic() bool

    // 返回指针类型的数据类型。如果类型的Kind不是Array、Chan、Map、Pointer或Slice,则会引发宕机
    Elem() Type

    // 返回结构体种的第 i 个字段
    Field(i int) StructField

    // 返回与索引相对应的嵌套字段
    FieldByIndex(index []int) StructField

    // 返回具有给定名称的结构字段,并返回一个布尔值,指示是否找到该字段。
    FieldByName(name string) (StructField, bool)

    // 以广度优先的顺序考虑结构本身中的字段,然后考虑任何嵌入结构中的字段。在最浅的嵌套深度处停止,嵌套深度包含一个或多个满足匹配函数的字段。如果该深度的多个字段满足匹配函数,则它们会相互抵消,FieldByNameFunc不会返回匹配。此行为反映了Go对包含嵌入字段的结构中的名称查找的处理
    FieldByNameFunc(match func(string) bool) (StructField, bool)

    // 返回函数类型的第i个输入参数的类型
    In(i int) Type

    // 返回映射类型的键类型。如果类型的Kind不是Map,会宕机
    Key() Type

    // 返回数组类型的长度
    Len() int

    // 返回结构类型的字段数量
    NumField() int

    // 返回函数类型的输入参数数量
    NumIn() int

    // 返回函数类型的输出参数数量
    NumOut() int

    // 返回函数类型的第i个输出参数的类型
    Out(i int) Type
}

type Kind 类型

Kind表示type所表示的特定类型。

type Kind uint
const (
    Invalid Kind = iota
    Bool
    Int
    Int8
    Int16
    Int32
    Int64
    Uint
    Uint8
    Uint16
    Uint32
    Uint64
    Uintptr
    Float32
    Float64
    Complex64
    Complex128
    Array
    Chan
    Func
    Interface
    Map
    Pointer        // ptr
    Slice
    String
    Struct
    UnsafePointer
)

TypeOf

func TypeOf(i any) Type返回 i 的反射Type类型。如果 i 的值是nil接口,TypeOf返回nil。(返回Type类型后,返回值可以使用上面 type Type interface的方法)

示例1        reflect.TypeOf 的使用

func getType() {
	typeInt := reflect.TypeOf(1)
	fmt.Println(typeInt)          // 打印 int
	fmt.Println(typeInt.String()) // 打印 int
	fmt.Println(typeInt.Kind())   // 打印 int

	typeString := reflect.TypeOf("hello")
	fmt.Println(typeString)          // 打印 string
	fmt.Println(typeString.String()) // 打印 string
	fmt.Println(typeString.Kind())   // 打印 string
}

示例2        reflect.TypeOf 的使用

// 自定义一个数据类型
type User struct {
	UserName string `我是Tag`
	Age      int
}

func getType() {
	var u1 User
	typeUser1 := reflect.TypeOf(u1)
	fmt.Println(typeUser1)               // 打印 main.User
	fmt.Println(typeUser1.String())      // 打印 main.User
	fmt.Println(typeUser1.Kind())        // 打印 struct
	fmt.Println(typeUser1.Field(0).Name) // 打印 UserName

	var u2 = new(User)
	typeUser2 := reflect.TypeOf(u2)
	fmt.Println(typeUser2)          // 打印 *main.User
	fmt.Println(typeUser2.String()) // 打印 *main.User
	fmt.Println(typeUser2.Kind())   // 打印 ptr

	var u3 = new(User)
	typeUser3 := reflect.TypeOf(u3).Elem() // Elem():把指针类型转成普通类型
	fmt.Println(typeUser3)                 // 打印 main.User
	fmt.Println(typeUser3.String())        // 打印 main.User
	fmt.Println(typeUser3.Kind())          // 打印 struct
}

示例3        获取成员变量详细信息

type User struct {
	UserName string `我是Tag`
	Age      int
	student  Student
}

type Student struct {
	score float32
}

func getField() {
	typeUser := reflect.TypeOf(User{})
	// 获取成员变量详情
	numField := typeUser.NumField()
	for i := 0; i < numField; i++ {
		field := typeUser.Field(i)
		fmt.Println("成员变量详情:", field)
	}

	// 获取结构体中嵌套结构体student中的变量score
	subField := typeUser.FieldByIndex([]int{2, 0}) // []int{2,0} 2是student在User中的下标,0是score在Student中的下标
	fmt.Println(subField)
}

示例4        获取成员方法详细信息

type User struct {
	UserName string `我是Tag`
	Age      int
}

func (User) Insert() int {
	return 1
}

// 获取成员方法,不是指针方法,如果需要获取指针方法,需要reflect.TypeOf(&User{})取地址才可
func getMethod() {
	typeUser := reflect.TypeOf(User{})
	numMethod := typeUser.NumMethod()
	for i := 0; i < numMethod; i++ {
		method := typeUser.Method(i)
		fmt.Println("成员方法详情:", method)
	}
}

 示例5        获取方法入参出参详细信息

func Add(a, b int) int {
	return a + b
}

// 获取普通方法详情
func getFunc() {
	typeFunc := reflect.TypeOf(Add)
	fmt.Println("方法类型:", typeFunc.Kind())

	numIn := typeFunc.NumIn()
	fmt.Println("方法输入参数个数:", numIn)
	numOut := typeFunc.NumOut()
	fmt.Println("方法输出参数个数:", numOut)

	for i := 0; i < numIn; i++ {
		fmt.Printf("第 %d 个输入参数类型是 %s \n", i, typeFunc.In(i))
	}
	for i := 0; i < numOut; i++ {
		fmt.Printf("第 %d 个输出参数类型是 %s \n", i, typeFunc.Out(i))
	}
}

ValueOf

func ValueOf(i any) Value返回一个新值,初始化为存储在接口中的具体值。ValueOf(nil)返回零。
func (v Value) Type() Type返回v的Type类型。
func (v Value) Kind() Kind返回v的类型。如果v是零值(IsValid返回false),Kind返回Invalid。
func (v Value) Addr() Value返回一个表示v的地址的指针值 // 把普通类型转成指针类型。
func (v Value) CanAddr() bool判断该值的地址是否可以通过Addr获取。这样的值称为可寻址值。如果值是切片的一个元素,可寻址数组的一个元素,可寻址结构的一个字段,或者指针解引用(elem)的结果,那么它就是可寻址的。如果CanAddr返回false,则调用Addr会出现panic。//是可寻址值的数据才能通过反射进行修改。
func (v Value) CanSet() bool判断v的值是否可以更改。只有当Value是可寻址的并且不是通过使用未导出(未导出就是首字符小写的意思)的结构字段获得时,才能更改它。如果CanSet返回false,则调用Set或任何类型特定的setter(例如,SetBool、SetInt)将panic。
func (v Value) Elem() Value返回接口v包含的值或指针v所指向的值。如果v的类型不是接口或指针,它会panic。如果v为nil,则返回0值。// 把指针类型转成普通类型。
func (v Value) Interface() (i any)返回v的当前值作为接口{}。它相当于:var i interface{}
func (v Value) IsValid() bool报告v是否代表一个值。如果v为零值,则返回false。// var i interface{};fmt.Println(reflect.ValueOf(i).IsValid()) 返回false
func (v Value) IsNil() bool判断参数v是否为空。参数必须是chan、func、interface、map、pointer或slice值
func (v Value) IsZero() bool判断v是否为其类型的零值。
func (v Value) Index(i int) Value返回v的第i个元素,是可寻址值。如果v的类型不是数组、切片或字符串,或者i超出了范围,它就会panic。
func (v Value) Set(x Value)将x赋值给v。如果CanSet返回false,它会panic。x的值Value类型,并且未导出的字段不能赋值。// 设置复合数据类型的时候使用
func (v Value) SetInt(x int64)将v的底层值设置为x,如果v的Kind不是Int、Int8、Int16、Int32或Int64,或者CanSet()为false,则会panic。
func (v Value) SetString(x string)将v的底层值设置为x。如果v的Kind不是String或CanSet()为false,则会panic。
func (v Value) FieldByName(name string) Value返回名称为name的结构体字段。如果未找到字段,则返回零值。如果v的Kind不是结构体,它会panic。
func (v Value) Len() int返回v的长度。如果v的Kind不是Array、Chan、Map、Slice、String或指向Array的指针,它会panic。
func (v Value) SetLen(n int)将v的长度设置为n。如果v的Kind不是Slice,或者n为负数或大于Slice的cap,则会panic。
func (v Value) SetCap(n int)将v的容量设置为n。如果v的Kind不是Slice,或者n小于Slice的len或大于Slice的原来的cap,则会panic。
func (v Value) MapIndex(key Value) Value返回v中的键关联的值。如果v的Kind不是map,则会宕机。如果在映射中找不到键,或者如果v表示nil映射,则返回零值。与Go中一样,键的值必须可分配给映射的键类型。
func (v Value) SetMapIndex(key, elem Value)将v中与key相关联的元素设置为elem。如果v的Kind不是Map,它会宕机。如果elem是零值,则SetMapIndex会从映射中删除该键。否则,如果v持有nil映射,则SetMapIndex将宕机。elem的键值对类型必须和v的Map键值对类型保持一致。
func (v Value) Send(x Value)在通道v上发送x,阻塞的。如果v的Kind不是Chan,或者x的类型与v的元素类型不同,它会宕机。与Go一样,x的值必须是可分配给通道的元素类型(类型要一样)。
func (v Value) TrySend(x Value) bool尝试在信道v上发送x,但不会阻塞。如果v的Kind不是Chan,它会宕机。它返回是否发送了值。与Go一样,x的值必须是可分配给通道的元素类型。
func (v Value) Recv() (x Value, ok bool)从通道v接收并返回一个值。如果v的Kind不是Chan,它会宕机。接收将阻塞,直到值准备好为止。如果值x对应于通道上的发送,则布尔值ok为true,如果由于通道关闭而接收到零值,则为false。
func (v Value) TryRecv() (x Value, ok bool)尝试从信道v接收值,但不会阻塞。如果v的Kind不是Chan,它会宕机。如果receive传递了一个值,那么x是传递的值,ok为true。如果接收不能在无阻塞的情况下完成,则x为零值,ok为false。如果通道是关闭的,则x是通道元素类型的零值,ok为false。
func (v Value) Call(in []Value) []Value调用函数v。如果v的Kind不是Func,则会panic。它将输出结果作为Value切片返回。与Go一样,每个输入参数都必须可分配给函数对应输入参数的类型。如果v是一个可变函数,Call会创建可变分片参数,复制相应的值。
func (v Value) MethodByName(name string) Value返回name对应的v中的方法。对返回函数调用的参数不应包括接收器;返回的函数将始终使用v作为接收器。如果没有找到任何方法,它将返回零值。
func New(typ Type) Value返回一个值,该值表示指向指定类型的新零值的指针。也就是说,返回的Value的Type是PointerTo(典型值)// 就是创建一个对象的Kind类型。
func MakeSlice(typ Type, len, cap int) Value创建一个指定类型、长度和容量的新的初始化为零的切片。
func MakeMap(typ Type) Value创建指定类型的map。
func MakeMapWithSize(typ Type, n int) Value创建一个n容量的指定类型的map。
func MakeChan(typ Type, buffer int) Value创建一个指定类型和缓冲区大小的新通道。
更多API介绍,请查阅官网https://golang.google.cn/pkg/reflect

示例1        reflect.ValueOf 的使用

func getValue() {
	intValue := reflect.ValueOf(1)
	stringValue := reflect.ValueOf("hello")
	userValue := reflect.ValueOf(User{})
	fmt.Println(intValue)    // 打印 1
	fmt.Println(stringValue) // 打印 hello
	fmt.Println(userValue)   // 打印 { 0}
}

示例2        Value 转 Type

func getValue() {
	intValue := reflect.ValueOf(1)
    // Value 转 Type
	intType := intValue.Type()	
	fmt.Println(intType)
}

示例3        指针结构体互相转换

func trans() {
	userValue := reflect.ValueOf(&User{})
	fmt.Println(userValue.Kind()) // 打印 ptr
	// 指针 转成 结构体
	userValuePtr := userValue.Elem()
	fmt.Println(userValuePtr.Kind()) // 打印 struct
	// 结构体 转成 指针
	userValue2 := userValuePtr.Addr()
	fmt.Println(userValue2.Kind()) // 打印 ptr
}

示例4        反射类型转普通类型

func trans() {
	iValue := reflect.ValueOf(1)
	// 方式一:把 反射类型转成普通类型
	iValue.Int()
	// 方式二:把 反射类型转成普通类型
	iValue2 := iValue.Interface().(int)
	fmt.Println(iValue2)

	// 把 反射类型 转成 结构体类型
	userType := reflect.ValueOf(User{})
	user := userType.Interface().(User)
	fmt.Println(user.UserName)
}

示例5        通过反射修改基础类型的值

func changeValue() {
	var i = 10
	iValue := reflect.ValueOf(&i)
	fmt.Println(iValue.Kind()) // 打印 ptr
	// 判断是否是可寻址值
	if iValue.CanAddr() {
		iValue.SetInt(20)
		fmt.Println("i = ", i)	// if 进不来,无打印
	}

	// 通过 elem 把指针类型解析成反射类型。注意:elem 只能被指针类型的反射所调用,所以在ValueOf的时候带&取址符号
	iValue2 := iValue.Elem()
	fmt.Println(iValue2.Kind()) // 打印 int
	// 判断是否是可寻址值
	if iValue2.CanAddr() {
		iValue2.SetInt(30)
		fmt.Println("i = ", i) // 打印 30
	}
}

示例6        通过反射修改结构体成员变量的值

type User struct {
	UserName string `我是Tag`
	Age      int
    gender   int    // 首字母小写是未导出字段,反射不能修改未导出字段
}

func changeValue() {
	var user = User{
		UserName: "张三",
		Age:      18,
	}
	fmt.Println("修改前的值:", user.UserName, user.Age) // 打印 张三 18

	userValue := reflect.ValueOf(&user).Elem()
	if userValue.CanAddr() {
		userValue.FieldByName("UserName").SetString("李四")
		userValue.FieldByName("Age").SetInt(28)
		fmt.Println("修改后的值:", user.UserName, user.Age) // 打印 李四 28
	}
}

示例7        通过反射修改嵌套结构体成员变量的值

type User struct {
	UserName string `我是Tag`
	Age      int
	gender   int
	Student  Student
}

type Student struct {
	Score float32
}

func changeValue() {
	var user = User{
		UserName: "张三",
		Student: Student{
			Score: 98,
		},
	}

	userValue := reflect.ValueOf(&user).Elem()
	if userValue.CanAddr() {
		studentValue := userValue.FieldByName("Student")
		fmt.Println(studentValue.Kind()) // 打印 struct
		// 修改 Score 的值
		studentValue.FieldByName("Score").SetFloat(59)
		fmt.Println(user.Student.Score) // 打印 59
	}
}

示例8        通过反射修改 slice 切片中的值

type User struct {
	UserName string `我是Tag`
	Age      int
    gender   int    // 首字母小写是未导出字段,反射不能修改未导出字段
}

func changeValue() {
	var userSlice = make([]*User, 3, 5)
	userSlice[0] = &User{
		UserName: "张三",
		Age:      18,
	}

	fmt.Println("修改前的数据:", userSlice[0].UserName, userSlice[0].Age) // 打印 张三 18

	sliceValue := reflect.ValueOf(userSlice)
	// 判断是否是可寻址的
	fmt.Println(sliceValue.CanAddr()) // 打印 false
	if sliceValue.Len() > 0 {
		// 获取切片中第0个元素
		sliceValue0 := sliceValue.Index(0)
		// 判断是否是可寻址的
		fmt.Println(sliceValue0.CanAddr()) // 打印 true
		// 查看是否是指针类型,如果是指针类型,需要 elem 解析
		fmt.Println(sliceValue0.Kind()) // 打印 ptr 是指针类型
		userValue := sliceValue0.Elem()
		userValue.FieldByName("UserName").SetString("李四")
		userValue.FieldByName("Age").SetInt(28)
		fmt.Println("修改后的数据:", userSlice[0].UserName, userSlice[0].Age) // 打印 李四 28
	}

	// 直接修改整个 user 对象
	sliceValue.Index(1).Set(reflect.ValueOf(&User{
		UserName: "王五",
		Age:      16,
	}))
	fmt.Println(userSlice[1].UserName, userSlice[1].Age) // 打印 王五 16
}

示例9        通过反射修改 map 中的值

func changeValue() {
	u1 := &User{
		UserName: "张三",
		Age:      18,
	}
	u2 := &User{
		UserName: "李四",
		Age:      20,
	}
	// 定义一个map对象
	userMap := make(map[int]*User, 5)
	userMap[0] = u1
	userMap[1] = u2

	// 反射value对象
	mapValue := reflect.ValueOf(userMap)
	// 把value类型转成type类型
	mapType := mapValue.Type()
	// 获取map中 key 的数据类型
	keyType := mapType.Key()
	fmt.Println("key的数据类型是:", keyType) // 打印 int
	// 获取map中 value 的数据类型。 注意这里的 elem 是 Type 的方法,作用是获取mapType的元素类型,不是解析指针
	valueType := mapType.Elem()
	fmt.Println("value的数据类型是:", valueType) //打印 *main.User

	// 修改 map中映射对应的值中的某一个成员变量的数据,比如只修改用户的年龄
	u1Value := mapValue.MapIndex(reflect.ValueOf(1))            // 修改map中key是1的数据
	fmt.Println("修改前的数据:", userMap[1].UserName, userMap[1].Age) // 打印  李四 20
	u1Value.Elem().FieldByName("Age").SetInt(28)
	fmt.Println("修改后的数据:", userMap[1].UserName, userMap[1].Age) // 打印  李四 28

	// 通过反射设置map的键值对
	k3 := 2
	u3 := &User{
		UserName: "王五",
		Age:      22,
	}
	mapValue.SetMapIndex(reflect.ValueOf(k3), reflect.ValueOf(u3))
	fmt.Println(userMap[k3].UserName, userMap[k3].Age) // 打印 王五 22
}

示例10        通过反射操作channel管道类型数据

func changeValue() {
	// 定义一个 chan 的数据
	var ch = make(chan int, 8)
	// 向 chan 中写入一行数据
	ch <- 10

	// 反射chan类型
	chanValue := reflect.ValueOf(&ch).Elem()

	if chanValue.CanAddr() {
		// 读取 chan 管道中的数据
		fmt.Println(chanValue.Recv())
	}

	// 向 chan 管道中写入数据
	chanValue.Send(reflect.ValueOf(20))

	// 获取把反射类型,转成普通类型
	c := chanValue.Interface().(chan int)
	fmt.Println(<-c)
}

示例11        通过反射调用普通方法

func callFunc() {
	valueFunc := reflect.ValueOf(Add)
	fmt.Println(valueFunc.Kind()) // 打印 func
	typeFunc := valueFunc.Type()
	numIn := typeFunc.NumIn() // 获取参数个数

	// 定义函数实参
	params := make([]reflect.Value, numIn)
	for i := 0; i < numIn; i++ {
		params[i] = reflect.ValueOf(1)
	}
	// 通过反射调用函数,返回切片Value,返回值中是函数返回结果
	callResult := valueFunc.Call(params)
	for i := 0; i < len(callResult); i++ {
		// 打印返回结果 // 实际开发中这里可以使用类型判断
		fmt.Println(callResult[i])
	}
}

示例12        通过反射调用结构体的成员方法

type User struct {
	UserName string `我是Tag`
	Age      int
	gender   int
	Student  Student
}

func (User) Insert(userName string) int {
	return 1
}

func callFunc() {
	user := &User{}
	userValue := reflect.ValueOf(user)
	// 通过方法名称获取结构体中的方法
	insertMethod := userValue.MethodByName("Insert")
	// 调用方法,返回函数结果
	callResult := insertMethod.Call([]reflect.Value{reflect.ValueOf("张三")})
	for i := 0; i < len(callResult); i++ {
		// 打印返回结果
		fmt.Println(callResult[i])
	}
}

示例13        通过反射创建结构体对象实例

func newStruct() {
	u := reflect.TypeOf(User{})
	// new一个对象的指针
	value := reflect.New(u)
	// 赋值
	value.Elem().FieldByName("UserName").SetString("张三")
	// 赋值
	value.Elem().FieldByName("Age").SetInt(18)

	// 反射类型转普通类型
	user := value.Interface().(*User)
	fmt.Println(user.UserName, user.Age) // 打印 张三 18
}

示例14        通过反射创建slice切片实例

func newSlice() {
	var slice []User
	sliceType := reflect.TypeOf(slice)
	// 创建一个指针类型的切片
	sliceValue := reflect.MakeSlice(sliceType, 3, 5)
	sliceValue.Index(0).Set(reflect.ValueOf(User{
		UserName: "张三",
		Age:      18,
	}))
	
	// 把反射的类型转成 普通类型
	users := sliceValue.Interface().([]User)
	for _, user := range users {
		fmt.Println(user.UserName, user.Age)	// 打印 张三 18,0,0
	}
}

示例15        通过反射创建map实例

func newMap() {
	var userMap map[int]*User
	mapType := reflect.TypeOf(userMap)
	// 创建map
	mapValue := reflect.MakeMap(mapType)
	//reflect.MakeMapWithSize(mapType,5)	// 指定容量

	// 给对象赋值
	u := &User{
		UserName: "张三",
		Age:      18,
	}
	mapValue.SetMapIndex(reflect.ValueOf(0), reflect.ValueOf(u))
	
	mp := mapValue.Interface().(map[int]*User)
	// 遍历对象
	for k, v := range mp {
		fmt.Printf("下标:%d, 数据:%s %d", k, v.UserName, v.Age)
	}
}

示例16        通过反射创建channel管道实例

func newChannel() {
	var ch chan User
	chanType := reflect.TypeOf(ch)
	// 创建 chan 对象
	chanValue := reflect.MakeChan(chanType, 5)
	u := User{
		UserName: "张三",
		Age:      18,
	}
	// 向 chan 中添加数据
	chanValue.Send(reflect.ValueOf(u))
	// 将反射类型转成普通类型
	c := chanValue.Interface().(chan User)
	fmt.Println(<-c)
}

全套教程地址:Golang全套教程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/6718.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

实战!项目管理实施过程的五大难点和痛点

作为一个在项目摸爬滚打十余年的管理人员&#xff0c;对项目管理的难点和痛点深有体会&#xff0c;这就结合我自身体验来说一说。 我认为&#xff0c;项目管理实施中的难点和痛点其实可以归结为两类&#xff1a;一类是对于项目任务本身&#xff0c;另一类则涉及到团队内部的管…

2023年,转行学Java还是web前端?

2023年要想顺利入行IT建议选择Java。 理由很简单&#xff0c;前端开发岗位需求大量减少&#xff0c;大厂裁员导致大量有经验的前端开发人员或者初级后端开发人员流入就业市场&#xff1b;作为新人缺乏技能优势和项目优势&#xff0c;而用人单位也更愿意招聘熟手&#xff0c;或…

Python 自动化指南(繁琐工作自动化)第二版:八、输入验证

原文&#xff1a;https://automatetheboringstuff.com/2e/chapter8/ 输入验证代码检查用户输入的值&#xff0c;比如来自input()函数的文本&#xff0c;格式是否正确。例如&#xff0c;如果您希望用户输入他们的年龄&#xff0c;您的代码不应该接受无意义的答案&#xff0c;如负…

chatgpt大模型赋能人形机器人之我见

我个人的看法&#xff08;不涉及任何和他项目相关的细节或商业机密&#xff0c;仅仅是我个人的泛泛而谈&#xff09;&#xff1a; 1、从大趋势来说&#xff0c;人形机器人的灵活度和通用性确实非常的高&#xff0c;是有前景的。另外轮式足式也不是他独一例&#xff0c;像 ETH …

【Redis学习】Redis管道

理论简介 问题由来 客户端向服务端发送命令分四步(发送命令→命令排队→命令执行→返回结果)&#xff0c;并监听Socket返回&#xff0c;通常以阻塞模式等待服务端响应。 服务端处理命令&#xff0c;并将结果返回给客户端。 上述两步称为:Round Trip Time(简称RTT,数据包往返…

STM32基于STM32CubeMX DMA + EXTI读取DS1307数据

STM32基于STM32CubeMX DMA EXTI读取DS1307数据✨申明&#xff1a;本文章仅发表在CSDN网站&#xff0c;任何其他网站&#xff0c;未注明来源&#xff0c;见此内容均为盗链和爬取&#xff0c;请多多尊重和支持原创!&#x1f341;对于文中所提供的相关资源链接将作不定期更换。&a…

JVM垃圾回收机制简介

内存管理 Java的内存管理很大程度指的就是对象的管理&#xff0c;其中包括对象空间的分配和释放。 对象空间的分配&#xff1a;使用new关键字创建对象即可 对象空间的释放&#xff1a;将对象赋值null即可。垃圾回收器将负责所有“不可达”对象的内存空间。 垃圾回收过程 任…

蓝桥杯web备赛——Node.js

node.js之前只能说是略有了解&#xff0c;这次好好了解一下吧&#xff01; 东西还是比较多的。 目前来看就了解比赛会用到的http模块就可以了&#xff0c;其他的暂且不做了解 const http require("http");//1.引入http模块const app http.createServer();//2.创建…

华为OD机试题【狼羊过河 or 羊、狼、农夫过河】用 C++ 编码,速通

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:狼羊过河 or 羊、狼、农夫过河…

Stata 回归结果详解

目录一、数据信息二、指标1.上半部分2.下半部分三、详细解释SSM - 模型平方和SSR - 残差平方和SST - 总平方和R-squared - R方 - 拟合系数Adj R-squared - 调整后的拟合系数df - 自由度MS - 均方差F - 总体显著性检验Prob > F - P值Root MSECoef.Std. Err.tP > | t |95% …

Python(黄金时代)—— python深入使用

深拷贝和浅拷贝 可变类型与不可变类型 可变对象是指&#xff0c;一个对象在不改变其引用的前提下&#xff0c;可以修改其所指向的地址中的值 不可变对象是指&#xff0c;一个对象引用指向的值是不能修改的 浅拷贝 浅拷贝是对于一个对象的顶层拷贝&#xff1b; 简单理解就是&…

蓝桥杯基础8:BASIC-7试题 特殊的数字

资源限制 内存限制&#xff1a;512.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 153是一个非常特殊的数&#xff0c;它等于它的每位数字的立方和&#xff0c;即1531*1*15*5*53*3*3。编程求所有满足这种条件…

JavaSE——运算符

目录 一.运算符 二.赋值运算符 三.算术运算符 1.四则运算 2.加法运算 3.除法运算 4.取模运算 5.增量运算符 6.自增自减 四.关系运算符 五.逻辑运算符 1.逻辑与 && 2.逻辑或 || 3.逻辑非 &#xff01; 4. 短路求值 六.位运算符 1.按位与& 2.按位…

6款无版权可商用的图片网站

今天给大家分享几个无版权可商用的图片网站&#xff0c;无论是网页设计、还是数字营销、商业海报制作等等都可以非常简单方便的查询到自己想要的图片。 免费可商用图标库 unDraw https://undraw.co/illustrations unDraw是作者Katerina Limpitsouni一手创作的扁平化图标库&…

国内IT软件外包公司汇总(2023 最新版)!

大环境不行&#xff0c;面试太少了&#xff0c;很多本科生想进外包都没机会。非常时期&#xff0c;不需要在意那么多&#xff0c;外包作为过渡也是没问题的&#xff0c;很多外包其实比小公司还要好多了。 也不要太担心去外包会污染自己的简历&#xff0c;只要接触的项目还可以…

电商平台API接口,店铺所有商品

item_search_shop-店铺的所有商品 名称类型必须描述keyString是调用key&#xff08;必须以GET方式拼接在URL中&#xff09;secretString是调用密钥&#xff08;注册Key和secret接入&#xff1a; https://o0b.cn/anzexi&#xff09;api_nameString是API接口名称&#xff08;包括…

动态规划-构造最优二叉树的解路径_20230403

动态规划-最优二叉搜索树的解路径&#xff08;算法导论) 前言 本文将探索递归的先序和后续对信息表达的影响&#xff0c;通过考察最优二叉搜索树的解roo[i][j]的解&#xff0c;我们可以分析先序和后续遍历之间的互相转换关系&#xff0c;以及为了转换&#xff0c;所付出的空间…

蓝桥杯第23天(Python)(疯狂刷题第6天)

题型&#xff1a; 1.思维题/杂题&#xff1a;数学公式&#xff0c;分析题意&#xff0c;找规律 2.BFS/DFS&#xff1a;广搜&#xff08;递归实现&#xff09;&#xff0c;深搜&#xff08;deque实现&#xff09; 3.简单数论&#xff1a;模&#xff0c;素数&#xff08;只需要…

下一个系统不是Win12,微软要复活Win10X

先是 Windows 三年发布周期回归又是官方 UI 泄露&#xff0c;再到前不久新增的测试频道… 微软将在2024年推出或许名为 Windows 12 的下一代系统基本已经板上钉钉了。 相比过去&#xff0c;小蝾总觉得即便是换代更新能带来的震撼都越来越少了。 当年每一个版本都是划时代的更…

.net C#反编译及脱壳常用工具--小结

1、Reflector --微软自家工具--推荐 Reflector是最为流行的.Net反编译工具。Reflector是由微软员工Lutz Roeder编写的免费程序。Reflector的出现使NET程序员眼前豁然开朗&#xff0c;因为这个免费工具可以将NET程序集中的中间语言反编译成C#或者Visual Basic代码。除了能将IL转…