Go语言反射

news/2024/7/6 20:48:53

Go语言反射

Go语言提供了一种机制在运行时更新和检查变量的值、调用变量的方法和变量支持的内在操作,但是在编译时并

不知道这些变量的具体类型,这种机制被称为反射。反射也可以让我们将类型本身作为第一类的值类型处理。

反射是指在程序运行期对程序本身进行访问和修改的能力,程序在编译时变量被转换为内存地址,变量名不会被编

译器写入到可执行部分,在运行程序时程序无法获取自身的信息。

支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件

中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。

Go语言提供了 reflect 包来访问程序的反射信息。

本节主要介绍反射 reflect 包里面的基本数据结构和反射操作的入口函数,和 Go 类型系统。

1、reflect 包

Go语言中的反射是由 reflect 包提供支持的,它定义了两个重要的类型 Type 和 Value,任意接口值在反射中都可

以理解为由 reflect.Type 和 reflect.Value 两部分组成,并且 reflect 包提供了 reflect.TypeOf 和 reflect.ValueOf 两

个函数来获取任意对象的 Value 和 Type。

1.1 reflect.Type

reflect.Type,反射包里面有一个通用的描述公共信息的结构 rtype。

rtype 实现了接口 relfect.Type,Go 的 refelct 包通过函数 refelct.Typeof() 返回一个 Type 类型的接口,使用者通

过接口来获取对象的类型信息。

为什么反射接口返回的是一个Type接口类型,而不是直接返回rtype?

一是因为类型信息是一个只读的信息,不可能动态地修改类型的相关信息,那太不安全了;二是因为不同的类型,

类型定义也不一样,使用接口这一抽象数据结构能够进行统一的抽象,所以 refelct 包通过 reflect.TypeOf() 函数

返回一个 Type 的接口变量,通过接口抽象出来的方法访问具体类型的信息。

reflect.TypeOf() 的函数原型如下:

func TypeOf(i interface{}) Type

形参是一个空接口类型,返回值是一个 Type 接口类型。

package main

import (
	"fmt"
	"reflect"
)

type myInt int64

func reflectType(x interface{}) {
	v := reflect.TypeOf(x)
	fmt.Printf("type:%v\n", v)
}

func main() {
	var a float32 = 3.14
	// type:float32
	reflectType(a)
	var b int64 = 100
	// type:int64
	reflectType(b)
	var c string = "你好"
	//type:string
	reflectType(c)
	var d *float32 = &a
	// type:*float32
	reflectType(d)
	var e myInt = 100
	// type:main.myInt
	reflectType(e)
	var f rune
	// type:int32
	reflectType(f)
}

接下来介绍 reflect.Type 接口的主要方法。

1.1.1 所有类型通用的方法

type Type interface {

  	// 通过索引值访问方法,索引值必须属于[0,NumMethod()),否则引发panic
	Method(int) Method

  	// 通过方法名获取Method
	MethodByName(string) (Method, bool)

  	// 返回一个类型的方法的个数
	NumMethod() int

    // 返回包含包名的类型名字,对于未命名类型返回的是空
	Name() string

  	// 返回类型的包路径,如果类型是预声明类型或未命名类型,则返回空字符串
	PkgPath() string

  	// 返回存放该类型的实例需要多大的字节空间
	Size() uintptr
	
  	// Kind返回该类型的底层基础类型
	Kind() Kind

    // 确定当前类型是否实现了u接口类型
    // 注意这里的u必须是接口类型的Type
	Implements(u Type) bool

  	// 判断当前类型的实例是否能赋值给type为u的类型变量
	AssignableTo(u Type) bool

  	// 判断当前类型的实例是否能强制类型转换为u类型变量
	ConvertibleTo(u Type) bool

  	// 判断当前类型是否支持比较(等于或不等于)
	// 支持等于的类型可以作为map的key
	Comparable() bool
}

1.1.2 不同基础类型的专有方法

这些方法是某种类型特有的,如果不是某种特定类型却调用了该类型的方法,则会引发panic。所以为了避免

panic,在调用特定类型的专有方法前,要清楚地知道该类型是什么,如果不确定类型,则要先调用Kind()方法确

定类型后再调用类型的专有方法。

type Type interface {

	Align() int

	FieldAlign() int

	String() string

    // Int*,Uint*,Float*,Complex*
  	// 返回数值型类型内存占用的位数
	Bits() int

  	// Chan
	ChanDir() ChanDir

  	// Func
  	// func类型专用的方法
	// 函数是否是不定参数函数
	IsVariadic() bool
	
  	// Array,Chan,Map,Ptr,Slice
  	// 返回类型的元素类型,该方法只适合Array、Chan、Map、Ptr、Slice类型
	Elem() Type

  	// Struct
  	// 通过整数索引获取struct字段
	Field(i int) StructField

  	// Struct
  	// 获取嵌入字段获取struct字段
	FieldByIndex(index []int) StructField

  	// Struct
  	// 通过名字查找获取struct字段
	FieldByName(name string) (StructField, bool)

  	// Struct
	FieldByNameFunc(match func(string) bool) (StructField, bool)

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

  	// Map
  	// map类型专用的方法
	// 返回map key的 type
	Key() Type

    // Array
	Len() int

  	// Struct
  	// struct类型专用的方法
	// 返回字段数目
	NumField() int

  	// Func
	// 输入参数个数
	NumIn() int

  	// Func
  	// 返回值个数
	NumOut() int
  
  	// Func
  	// 返回第立个返回值类型
	Out(i int) Type

	common() *rtype
  
	uncommon() *uncommonType
}

1.1.3 常用函数示例

下面通过一个具体的示例来看一下 reflect.TypeOf() 函数的基本功能。

package main

import (
	"fmt"
	"reflect"
)

type Student struct {
	Name string `name`
	Age  int    `age1:"virtual" age2:"full"`
}

func main() {
	s := Student{}
	rt := reflect.TypeOf(s)
	// 获取字段
	fieldName, ok := rt.FieldByName("Name")
	// 取tag数据
	if ok {
		// Name.Tag: name
		fmt.Println("Name.Tag:", fieldName.Tag)
	}
	fieldAge, ok := rt.FieldByName("Age")
	if ok {
		// Age.Tag.age1: virtual
		fmt.Println("Age.Tag.age1:", fieldAge.Tag.Get("age1"))
		// Age.Tag.age2: full
		fmt.Println("Age.Tag.age2:", fieldAge.Tag.Get("age2"))
	}
	// TypeName: Student
	fmt.Println("TypeName:", rt.Name())
	// TypeNumField: 2
	fmt.Println("TypeNumField:", rt.NumField())
	// TypePkgPath: main
	fmt.Println("TypePkgPath:", rt.PkgPath())
	// TypeString: main.Student
	fmt.Println("TypeString:", rt.String())
	// Type.Kind.String: struct
	fmt.Println("TypeKindString:", rt.Kind().String())
	// 获取结构类型那个的字段名称
	// Type.Field[0].Name:=Name
	// Type.Field[1].Name:=Age
	for i := 0; i < rt.NumField(); i++ {
		fmt.Printf("Type.Field[%d].Name:=%v \n", i, rt.Field(i).Name)
	}
	sc := make([]int, 5)
	sc = append(sc, 1, 2, 3, 4, 5)
	sct := reflect.TypeOf(sc)
	// 获取slice元素的Type
	scet := sct.Elem()
	// slice element type.Kind(): int
	fmt.Println("slice element type.Kind():", scet.Kind())
	// slice element type.Kind(): 2
	fmt.Printf("slice element type.Kind(): %d\n", scet.Kind())
	// slice element type.String(): int
	fmt.Println("slice element type.String():", scet.String())
	// slice element type.Name(): int
	fmt.Println("slice element type.Name():", scet.Name())
	// slice type.NumMethod(): 0
	fmt.Println("slice type.NumMethod():", scet.NumMethod())
	// slice type.PkgPath():
	fmt.Println("slice type.PkgPath():", scet.PkgPath())
}

对于 reflect.TypeOf(a),传进去的实参 a 有两种类型,一种是接口变量,另一种具体类型变量。如果 a 是具体类

型变量,则 reflect.TypeOf() 返回的是具体的类型信息;如果 a 是一个接口变量,则函数的返回结果又分两种情

况。如果 a 绑定了具体类型实例,则返回的是接口的动态类型,也就是 a 绑定的具体实例类型的信息,如果 a 没

有绑定具体类型实例,则返回的是接口自身的静态类型信息。下面以一个示例来看一下这种特性。

package main

import (
	"reflect"
)

type INT int

type A struct {
	a int
}

type B struct {
	b string
}

type Ita interface {
	String() string
}

func (b B) String() string {
	return b.b
}

func main() {
	var a INT = 12
	var b int = 14
	// 对于实参是具体类型,reflect.TypeOf返回是其静态类型
	ta := reflect.TypeOf(a)
	tb := reflect.TypeOf(b)
	// INT
	println(ta.Name())
	// int
	println(tb.Name())
	// INT和int是两个类型,二者不相等
	// ta!=tb
	if ta == tb {
		println("ta==tb")
	} else {
		println("ta!=tb")
	}
	// 底层基础类型
	// int
	println(ta.Kind().String())
	// int
	println(tb.Kind().String())
	s1 := A{1}
	s2 := B{"tata"}
	// 对于实参是具体类型,reflect.TypeOf返回是其静态类型
	// A
	println(reflect.TypeOf(s1).Name())
	// B
	println(reflect.TypeOf(s2).Name())
	// Type的Kind()方法返回的是基础类型,类型A和B的底层基础类型都是struct
	// struct
	println(reflect.TypeOf(s1).Kind().String())
	// struct
	println(reflect.TypeOf(s2).Kind().String())
	ita := new(Ita)
	var itb Ita = s2
	// 对于实参是未绑定具体变量的接口类型,reflect.TypeOf返回的是接口类型本身
	// 也就是接口的静态类型
	// Ita
	println(reflect.TypeOf(ita).Elem().Name())
	// interface
	println(reflect.TypeOf(ita).Elem().Kind().String())
	// 对于实参是绑定了具体变量的接口类型,reflect.TypeOf返回的是绑定的具体类型
	// 也就是接口的动态类型
	// B
	println(reflect.TypeOf(itb).Name())
	// struct
	println(reflect.TypeOf(itb).Kind().String())
}

1.2 reflect.Value

reflect.ValueOf() 返回的是 reflect.Value 类型,其中包含了原始值的值信息。reflect.Value 与原始值之间可以互相

转换。reflect.Value 表示实例的值信息,reflect.Value 是一个struct,并提供了一系列的 method 给使用者。

refelct.Value 总共有三个字段,一个是值的类型指针 tvp,另一个是指向值的指针 ptr,最后一个是标记字段

flag。

反射包中通过 reflect.ValueOf() 函数获取实例的值信息,reflect.ValueOf() 的原型如下:

func ValueOf(i interface{}) Value

输入参数是空接口,输出是一个 Value 类型的变量。Value 本身提供了丰富的 API 给用户使用,先来看一个简单示

例。

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Id   int
	Name string
	Age  int
}

func Info(o interface{}) {
	// 获取Value信息
	v := reflect.ValueOf(o)
	// 通过Value获取Type
	t := v.Type()
	// 类型名称
	// Type: User
	println("Type:", t.Name())
	// 访问接口字段名,字段类型和字段值信息
	// Fields:
	println("Fields:")
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		value := v.Field(i).Interface()
		// 类型查询
		switch value := value.(type) {
		case int:
			fmt.Printf(" %6s: %v = %d\n", field.Name, field.Type, value)
		case string:
			fmt.Printf(" %6s: %v = %s\n", field.Name, field.Type, value)
		default:
			fmt.Printf(" %6s: %v = %s\n", field.Name, field.Type, value)
		}
	}
}

func main() {
	u := User{1, "Tom", 30}
	Info(u)
}
Type: User
Fields:
     Id: int = 1
   Name: string = Tom
    Age: int = 30

1.2.1通过反射获取值

package main

import (
	"fmt"
	"reflect"
)

func reflectValue(x interface{}) {
	v := reflect.ValueOf(x)
	k := v.Kind()
	switch k {
	case reflect.Int64:
		// v.Int()从反射中获取整型的原始值,然后通过int64()强制类型转换
		fmt.Printf("type is int64, value is %d\n", int64(v.Int()))
	case reflect.Float32:
		// v.Float()从反射中获取浮点型的原始值,然后通过float32()强制类型转换
		fmt.Printf("type is float32, value is %f\n", float32(v.Float()))
	case reflect.Float64:
		// v.Float()从反射中获取浮点型的原始值,然后通过float64()强制类型转换
		fmt.Printf("type is float64, value is %f\n", float64(v.Float()))
	}
}

func main() {
	var a float32 = 3.14
	// type is float32, value is 3.140000
	reflectValue(a)
	var b int64 = 100
	// type is int64, value is 100
	reflectValue(b)
	// 将int类型的原始值转换为reflect.Value类型
	c := reflect.ValueOf(10)
	// value c :10
	fmt.Printf("value c :%d\n", c)
	// type c :reflect.Value
	fmt.Printf("type c :%T\n", c)
}

1.2.2 通过反射设置变量的值

想要在函数中通过反射修改变量的值,需要注意函数参数传递的是值拷贝,必须传递变量地址才能修改变量值,而

反射中使用专有的 Elem() 方法来获取指针对应的值。

package main

import (
	"fmt"
	"reflect"
)

func reflectSetValue1(x interface{}) {
	v := reflect.ValueOf(x)
	if v.Kind() == reflect.Int64 {
		v.SetInt(200)
	}
}

func reflectSetValue2(x interface{}) {
	v := reflect.ValueOf(x)
	// 反射中使用Elem()方法获取指针对应的值
	if v.Elem().Kind() == reflect.Int64 {
		v.Elem().SetInt(200)
	}
}

func main() {
	var a int64 = 100
	reflectSetValue1(&a)
	// 100
	fmt.Println(a)
	var b int64 = 200
	reflectSetValue2(&b)
	// 200
	fmt.Println(b)
}

1.2.3 isNil()和isValid()

IsNil() boo:返回值是否为 nil。如果值类型不是通道(channel)、函数、接口、map、指针或切片时发生

panic,类似于语言层的 v== nil 操作。

IsValid() bool:判断值是否有效。 当值本身非法时,返回 false,例如 reflect Value不包含任何值,值为 nil

等。

IsNil() 常被用于判断指针是否为空;IsValid() 常被用于判定返回值是否有效。

package main

import (
	"fmt"
	"reflect"
)

func main() {

	// *int的空指针
	var a *int
	// var a *int: true
	fmt.Println("var a *int:", reflect.ValueOf(a).IsNil())

	// nil值
	// nil: false
	fmt.Println("nil:", reflect.ValueOf(nil).IsValid())

	// *int类型的空指针
	// (*int)(nil): false
	fmt.Println("(*int)(nil):", reflect.ValueOf((*int)(nil)).Elem().IsValid())

	// 实例化一个结构体
	s := struct{}{}

	// 尝试从结构体中查找一个不存在的字段
	// 不存在的结构体成员: false
	fmt.Println("不存在的结构体成员:", reflect.ValueOf(s).FieldByName("").IsValid())

	// 尝试从结构体中查找一个不存在的方法
	// 不存在的结构体方法: false
	fmt.Println("不存在的结构体方法:", reflect.ValueOf(s).MethodByName("").IsValid())

	// 实例化一个map
	m := map[int]int{}

	// 尝试从map中查找一个不存在的键
	// 不存在的键: false
	fmt.Println("不存在的键:", reflect.ValueOf(m).MapIndex(reflect.ValueOf(3)).IsValid())
}

1.2.4 结构体反射

任意值通过 reflect.TypeOf() 获得反射对象信息后,如果它的类型是结构体,可以通过反射值对象 reflect.Type 的

NumField() 和 Field() 方法获得结构体成员的详细信息。

package main

import (
	"fmt"
	"reflect"
)

type student struct {
	Name  string `json:"name"`
	Score int    `json:"score"`
}

// 给student添加两个方法 Study和Sleep
func (s student) Study() string {
	msg := "Study"
	fmt.Println(msg)
	return msg
}

func (s student) Sleep() string {
	msg := "Sleep"
	fmt.Println(msg)
	return msg
}

func printMethod(x interface{}) {
	t := reflect.TypeOf(x)
	v := reflect.ValueOf(x)
	// 2
	fmt.Println(t.NumMethod())
	// method name:Sleep
	// method:func() string
	// Sleep
	// method name:Study
	// method:func() string
	// Study
	for i := 0; i < v.NumMethod(); i++ {
		methodType := v.Method(i).Type()
		fmt.Printf("method name:%s\n", t.Method(i).Name)
		fmt.Printf("method:%s\n", methodType)
		// 通过反射调用方法传递的参数必须是[]reflect.Value类型
		var args = []reflect.Value{}
		v.Method(i).Call(args)
	}
}

func main() {
	stu := student{
		Name:  "Tom",
		Score: 90,
	}
	t := reflect.TypeOf(stu)
	// student struct
	fmt.Println(t.Name(), t.Kind())
	// 通过for循环遍历结构体的所有字段信息
	// name:Name index:[0] type:string json tag:name
	// name:Score index:[1] type:int json tag:score
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		fmt.Printf("name:%s index:%d type:%v json tag:%v\n", field.Name, field.Index, field.Type, field.Tag.Get("json"))
	}
	// 通过字段名获取指定结构体字段信息
	// name:Score index:[1] type:int json tag:score
	if scoreField, ok := t.FieldByName("Score"); ok {
		fmt.Printf("name:%s index:%d type:%v json tag:%v\n", scoreField.Name, scoreField.Index, scoreField.Type, scoreField.Tag.Get("json"))
	}
	printMethod(stu)
}

2、基础类型

Type 接口有一个 Kind() 方法,返回的是一个整数枚举值,不同的值代表不同的类型。这里的类型是一个抽象的概

念,我们暂目称之为"基础类型",比如所有的结构都归结为一种基础类型 struct,所有的函数都归结为一种基础类

型 func。基础类型是根据编译器、运行时构建类型的内部数据结构不同来划分的,不同的基础类型,其构建的最

终内部数据结构不一样。

Go 的具体类型可以定义成千上万种,单单一个 struct 就可以定义出很多新类型,但是它们都归结为一种基础类型

struct。基础类型是抽象的,其种类有限。Go总共定义了26种基础类型,具体如下:

// Kind用来表示特定的类型,每个枚举值代表一种类型
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
	Slice
	String
	Struct
	UnsafePointer
)

2.1 底层类型和基础类型

底层类型和基础类型的区别在于,基础类型是抽象的类型划分,底层类型是针对每一个具体的类型来定义的,比如

不同的 struct 类型在基础类型上都划归为 struct 类型,但不同的 struct 底层类型是不一样的。示例如下:

type A struct{
a int
}

type Aa A

type B struct{
    b int
}

A、Aa、B的基础类型都是 struct,B的底层类型是其自身,A和Aa的底层类型都是A。

3、反射规则

前面讲解了 Value 和 Type 的基本概念,本节重点讲解反射对象 Value 和 Type 和类型实例之间的相互转化。

3.1 反射API

反射 API 的分类总结如下。

3.1.1 从实例到Value

通过实例获取 Value对象,直接使用 reflect.ValueOf() 函数。例如:

func ValueOf(i interface{}) Value
a := 10
ra := reflect.ValueOf(a)
// 10
fmt.Println(ra)

3.1.2 从实例到Type

通过实例获取反射对象的Type,直接使用 reflect.TypeOf() 函数。例如:

func Typeof(i interface{}) Type
b := 20
rb := reflect.TypeOf(b)
// int
fmt.Println(rb)

3.1.3 从Type到Value

Type 里面只有类型信息,所以直接从一个Type接口变量里面是无法获得实例的 Value的,但可以通过该 Type 构

建一个新实例的 Value。reflect 包提供了两种方法,示例如下:

// New返回的是一个Value,该Value的type为PtrTo(typ),即Value的Type是指定typ的指针类型
func New(typ Type) Value
c := reflect.New(rb)
// *int
fmt.Println(c.Type())
// Zero返回的是一个typ类型的零值,注意返回的Value不能寻址,值不可改变
func Zero(typ Type) Value
d := reflect.Zero(rb)
// int
fmt.Println(d.Type())

如果知道一个类型值的底层存放地址,则还有一个函数是可以依据type 和该地址值恢复出Value的。例如:

func NewAt(typ Type,p unsafe.Pointer) Value

3.1.4 从Value到Type

从反射对象 Value 到 Type 可以直接调用 Value 的方法,因为Value内部存放着到Type类型的指针。例如:

func (v Value) Type() Type
e := reflect.ValueOf(&a).Elem()
// int
fmt.Println(e.Type())

3.1.5 从Value到实例

Value本身就包含类型和值信息,reflect提供了丰富的方法来实现从Value到实例的转换。例如:

// 该方法最通用,用来将Value转换为空接口 
// 该空接口内部存放具体类型实例
// 可以使用接口类型查询去还原为具体的类刑
func(v Value) Interface()(i interface{})

// Value自身也提供丰富的方法,直接将Value转换为简单类型实例,如果类型不匹配,则直接引起 panic
func(v Value) Bool() bool
func(V Value) Float() float64
func(v Value) Int() int64
func(V Value) Uint() uint64
f := ra.Int()
// 10
fmt.Println(f)

3.1.6 从Value的指针到值

从一个指针类型的 Value 获得值类型 Value 有两种方法,示例如下。

//如果类型是接口,则E1em()返回接口绑定的实例的Value,如果V类型是指针,则返回指针值的Value,否则引起panic
func(v Value) Elem() Value

//如果v是指针,则返回指针值的Value,否则返回了自身,该函数不会引起panic
func Indirect(v Value) Value
g := reflect.ValueOf(&a).Elem()
// 10
fmt.Println(g)
h := reflect.Indirect(ra)
// 10
fmt.Println(h)

3.1.7 Type指针和值的相互转换

(1)、指针类型 Type 到值类型 Type。例如:

// t必须是Array、Chan、Map、Ptr、Slice,否则会引起panic
// Elem返回的是其内部元素的Type
t.Elem() Type
i := reflect.TypeOf(&b).Elem()
// int
fmt.Println(i)

(2)、值类型Type到指针类型Type。例如:

// PtrTo返回的是指向t的指针型Type
func PtrTo(t Type) Type
j := reflect.PtrTo(i)
//  *int
fmt.Println(j)

3.1.8 Value 值的可修改性

Value 值的修改涉及如下两个方法:

//通过CanSet 判断是否能修改
func(V Value) CanSet()bool

//通过 Set 进行修改
func(v Value) Set(x Value)

Value值在什么情况下可以修改?我们知道实例对象传递给接口的是一个完全的值拷贝,如果调用反射的方法

reflect.ValueOf() 传进去的是一个值类型变量,则获得的 Value 实际上是原对象的一个副本,这个 Value 是无

论如何也不能被修改的。如果传进去的是一个指针,虽然接口内部转换的也是指针的副本,但通过指针还是可以访

问到最原始的对象,所以此种情况获得的Value是可以修改的。下面来看一个简单的示例。

package main

import (
	"fmt"
	"reflect"
)

type User struct {
	Id   int
	Name string
	Age  int
}

func main() {

	u := User{Id: 1, Name: "andes", Age: 20}
	va := reflect.ValueOf(u)
	vb := reflect.ValueOf(&u)
	// 值类型是不可修改的
	// false false
	fmt.Println(va.CanSet(), va.FieldByName("Name").CanSet())
	// 指针类型是可修改的
	// false true
	fmt.Println(vb.CanSet(), vb.Elem().FieldByName("Name").CanSet())
	// &{1 andes 20}
	fmt.Printf("%v\n", vb)
	name := "shine"
	vc := reflect.ValueOf(name)
	// 通过Set函数修改变量的值
	vb.Elem().FieldByName("Name").Set(vc)
	// &{1 shine 20}
	fmt.Printf("%v\n", vb)
}

3.2 反射三定律

(1)、反射可以从接口值得到反射对象。

(2)、反射可以从反射对象获得接口值。

(3)、若要修改一个反射对象,则其值必须可以修改。


http://lihuaxi.xjx100.cn/news/1139375.html

相关文章

为什么我掌握了大量软测知识,却还是找不到工作?

很多朋友都在疑惑&#xff0c;为什么随着对于软件测试了解的加深&#xff0c;不断掌握更多测试知识与技巧&#xff0c;找工作貌似越来越难了&#xff1f; 不免让人联想到最近偶然间看到一句话&#xff1a;“软件测试是整个 IT 行业中最差的岗位”。 打工人的问题出在哪&#xf…

ANR基础 - Input系统

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录 系列文章目录前言一、Input系统概述二、整体框架1.整体框架类图2.核心启动过程2.1 initialize2.1 I…

redis从零开始(1)----基本类型:string/hash/list

认识redis NoSQL Nosql not only sql&#xff0c;泛指非关系型数据库&#xff0c;与之相对的是RDBMS(Relational Database Management System)&#xff0c;即关系型数据库 关系型数据库&#xff1a;列行&#xff0c;同一个表下数据的结构是一样的。 非关系型数据库&#xff…

【Linux】常见指令

&#x1f307;个人主页&#xff1a;平凡的小苏 &#x1f4da;学习格言&#xff1a;别人可以拷贝我的模式&#xff0c;但不能拷贝我不断往前的激情 &#x1f6f8;C专栏&#xff1a;Linux修炼内功基地 家人们更新不易&#xff0c;你们的&#x1f44d;点赞&#x1f44d;和⭐关注⭐…

微信小程序 录音+播放组件封装

展示 长按录音 松开结束录音 点击播放 再次点击暂停 再次点击继续播放 展示效果&#xff1a; 录音功能 录音功能&#xff08;手指按下开始录音 手指松开结束录音&#xff09;&#xff1a; 使用wx原生录音功能在 component 外新建 wx.getRecorderManager() RecorderManager…

「AI之劫」:当机器超越人类底线,正在侵犯我们的创造力和道德

随着AI技术的不断发展&#xff0c;AI生成内容&#xff08;AIGC&#xff09;已经成为数字娱乐、商业营销和学术研究等领域的热门话题。随着人工智能技术的不断发展越来越多的领域开始应用AI技术&#xff0c;其中之一就是内容生成领域。 AIGC全称为AI-Generated Content, 指基于生…

SpringCloud_服务调用_Ribbon概述以及使用(一)

SpringCloud_负载均衡_Ribbon(一) 概述 Ribbbon负载均衡演示 Ribbbon核心组件IRule Ribbbon负载均衡算法 概述 Ribbbon是一套客户端 负载均衡的工具 提供客户端的软件负载均衡算法和服务调用 地址&#xff1a; https://github.com/Netflix/ribbon/wiki/Getting-Started 目前这几…

基于Web的智慧储能电站3D可视化管理平台

电能作为现代社会的运转和发展的基础&#xff0c;是民生最基本的保障&#xff0c;其稳定性对国家经济发展至关重要。 建设背景 电力系统是一个稳态平衡系统&#xff0c;发电站的总发电功率需要等于用户端的总发电功率。如果两者不一致&#xff0c;就会导致整个电力系统的不稳…