当前位置: 首页 > news >正文

Go语言设计与实现 -- 反射

Go的反射有哪些应用?

  • IDE中代码的自动补全
  • 对象序列化
  • fmt函数的相关实现
  • ORM框架

什么情况下需要使用反射?

  • 不能明确函数调用哪个接口,需要根据传入的参数在运行时决定。
  • 不能明确传入函数的参数类型,需要在运行时处理任意对象。

反射对性能有消耗,而且可读性低,能不用就不要用反射。

如何比较两个对象完全相同?

Go中提供了一个函数可以实现这个功能:

func DeepEqual(x, y interface{}) bool

DeepEqual 函数的参数是两个 interface,实际上也就是可以输入任意类型,输出 true 或者 flase 表示输入的两个变量是否是“深度”相等。

先明白一点,如果是不同的类型,即使是底层类型相同,相应的值也相同,那么两者也不是“深度”相等。

例如这个代码:

type MyInt int
type YourInt intfunc main() {m := MyInt(1)y := YourInt(1)fmt.Println(reflect.DeepEqual(m, y))
}

这个代码的结果是false。

上面的代码中,m, y 底层都是 int,而且值都是 1,但是两者静态类型不同,前者是 MyInt,后者是 YourInt,因此两者不是“深度”相等。

来看一下源码:

func DeepEqual(x, y any) bool {if x == nil || y == nil {return x == y}v1 := ValueOf(x)v2 := ValueOf(y)if v1.Type() != v2.Type() {return false}return deepValueEqual(v1, v2, make(map[visit]bool))
}

首先查看两者是否有一个是 nil 的情况,这种情况下,只有两者都是 nil,函数才会返回 true

接着,使用反射,获取x,y 的反射对象,并且立即比较两者的类型,根据前面的内容,这里实际上是动态类型,如果类型不同,直接返回 false。

最后,最核心的内容在子函数 deepValueEqual 中。

然后我们来看一下deepValueEqual的源码:

// Tests for deep equality using reflected types. The map argument tracks
// comparisons that have already been seen, which allows short circuiting on
// recursive types.
func deepValueEqual(v1, v2 Value, visited map[visit]bool) bool {if !v1.IsValid() || !v2.IsValid() {return v1.IsValid() == v2.IsValid()}if v1.Type() != v2.Type() {return false}// We want to avoid putting more in the visited map than we need to.// For any possible reference cycle that might be encountered,// hard(v1, v2) needs to return true for at least one of the types in the cycle,// and it's safe and valid to get Value's internal pointer.hard := func(v1, v2 Value) bool {switch v1.Kind() {case Pointer:if v1.typ.ptrdata == 0 {// go:notinheap pointers can't be cyclic.// At least, all of our current uses of go:notinheap have// that property. The runtime ones aren't cyclic (and we don't use// DeepEqual on them anyway), and the cgo-generated ones are// all empty structs.return false}fallthroughcase Map, Slice, Interface:// Nil pointers cannot be cyclic. Avoid putting them in the visited map.return !v1.IsNil() && !v2.IsNil()}return false}if hard(v1, v2) {// For a Pointer or Map value, we need to check flagIndir,// which we do by calling the pointer method.// For Slice or Interface, flagIndir is always set,// and using v.ptr suffices.ptrval := func(v Value) unsafe.Pointer {switch v.Kind() {case Pointer, Map:return v.pointer()default:return v.ptr}}addr1 := ptrval(v1)addr2 := ptrval(v2)if uintptr(addr1) > uintptr(addr2) {// Canonicalize order to reduce number of entries in visited.// Assumes non-moving garbage collector.addr1, addr2 = addr2, addr1}// Short circuit if references are already seen.typ := v1.Type()v := visit{addr1, addr2, typ}if visited[v] {return true}// Remember for later.visited[v] = true}switch v1.Kind() {case Array:for i := 0; i < v1.Len(); i++ {if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {return false}}return truecase Slice:if v1.IsNil() != v2.IsNil() {return false}if v1.Len() != v2.Len() {return false}if v1.UnsafePointer() == v2.UnsafePointer() {return true}// Special case for []byte, which is common.if v1.Type().Elem().Kind() == Uint8 {return bytealg.Equal(v1.Bytes(), v2.Bytes())}for i := 0; i < v1.Len(); i++ {if !deepValueEqual(v1.Index(i), v2.Index(i), visited) {return false}}return truecase Interface:if v1.IsNil() || v2.IsNil() {return v1.IsNil() == v2.IsNil()}return deepValueEqual(v1.Elem(), v2.Elem(), visited)case Pointer:if v1.UnsafePointer() == v2.UnsafePointer() {return true}return deepValueEqual(v1.Elem(), v2.Elem(), visited)case Struct:for i, n := 0, v1.NumField(); i < n; i++ {if !deepValueEqual(v1.Field(i), v2.Field(i), visited) {return false}}return truecase Map:if v1.IsNil() != v2.IsNil() {return false}if v1.Len() != v2.Len() {return false}if v1.UnsafePointer() == v2.UnsafePointer() {return true}for _, k := range v1.MapKeys() {val1 := v1.MapIndex(k)val2 := v2.MapIndex(k)if !val1.IsValid() || !val2.IsValid() || !deepValueEqual(val1, val2, visited) {return false}}return truecase Func:if v1.IsNil() && v2.IsNil() {return true}// Can't do better than this:return falsecase Int, Int8, Int16, Int32, Int64:return v1.Int() == v2.Int()case Uint, Uint8, Uint16, Uint32, Uint64, Uintptr:return v1.Uint() == v2.Uint()case String:return v1.String() == v2.String()case Bool:return v1.Bool() == v2.Bool()case Float32, Float64:return v1.Float() == v2.Float()case Complex64, Complex128:return v1.Complex() == v2.Complex()default:// Normal equality sufficesreturn valueInterface(v1, false) == valueInterface(v2, false)}
}

这个代码的思路很清晰,就是分别递归调用deepValueEqual函数,一直递归到最进本的数据类型,比较int, string等可以直接得出true或者false,再一层层的返回,最终得到深度相等的比较结果。

Go语言是如何实现反射的?

interface,它是 Go 语言实现抽象的一个非常强大的工具。当向接口变量赋予一个实体类型的时候,接口会存储实体的类型信息,反射就是通过接口的类型信息实现的,反射建立在类型的基础上。

Go 语言在 reflect 包里定义了各种类型,实现了反射的各种函数,通过它们可以在运行时检测类型的信息、改变类型的值。

type和interface

我们需要先介绍一下什么叫做静态类型,什么叫做动态类型。

静态类型

所谓的静态类型(即 static type),就是变量声明的时候的类型。

var age int   // int 是静态类型
var name string  // string 也是静态类型

它是你在编码时,肉眼可见的类型。

动态类型

所谓的 动态类型(即 concrete type,也叫具体类型)是 程序运行时系统才能看见的类型。

这是什么意思呢?

我们都知道 空接口 可以承接什么问题类型的值,什么 int 呀,string 呀,都可以接收。

比如下面这几行代码

var i interface{}   i = 18  
i = "Go编程时光"  

第一行:我们在给 i 声明了 interface{} 类型,所以 i 的静态类型就是 interface{}

第二行:当我们给变量 i 赋一个 int 类型的值时,它的静态类型还是 interface{},这是不会变的,但是它的动态类型此时变成了 int 类型。

第三行:当我们给变量 i 赋一个 string 类型的值时,它的静态类型还是 interface{},它还是不会变,但是它的动态类型此时又变成了 string 类型。

从以上,可以知道,不管是 i=18 ,还是 i="Go编程时光",都是当程序运行到这里时,变量的类型,才发生了改变,这就是我们最开始所说的 动态类型是程序运行时系统才能看见的类型。

Go 语言中,每个变量都有一个静态类型,在编译阶段就确定了的,比如 int, float64, []int 等等。注意,这个类型是声明时候的类型,不是底层数据类型。

Go官方的博客里面就举过一个例子:

type MyInt int 
var i int 
var j MyInt

尽管 i,j 的底层类型都是 int,但我们知道,他们是不同的静态类型,除非进行类型转换,否则,i 和 j 不能同时出现在等号两侧。j 的静态类型就是 MyInt

反射跟interface{}的关系十分密切,因此我们需要先学习一下interface{}的原理。

interface{}

非空interface{}

type iface struct {tab  *itabdata unsafe.Pointer
}type itab struct {inter *interfacetype_type *_typehash  uint32 // copy of _type.hash. Used for type switches._     [4]bytefun   [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
}type interfacetype struct {typ     _typepkgpath namemhdr    []imethod
}type _type struct {size       uintptrptrdata    uintptr // size of memory prefix holding all pointershash       uint32tflag      tflagalign      uint8fieldAlign uint8kind       uint8// function for comparing objects of this type// (ptr to object A, ptr to object B) -> ==?equal func(unsafe.Pointer, unsafe.Pointer) bool// gcdata stores the GC type data for the garbage collector.// If the KindGCProg bit is set in kind, gcdata is a GC program.// Otherwise it is a ptrmask bitmap. See mbitmap.go for details.gcdata    *bytestr       nameOffptrToThis typeOff
}

itab主要由具体类型_type和结构类型interfacetype组成。

我们可以用一张图来理顺中间的关系:

img

空interface{}

type eface struct {_type *_typedata  unsafe.Pointer
}

相比 ifaceeface 就比较简单了。只维护了一个 _type 字段,表示空接口所承载的具体的实体类型。data 描述了具体的值。

接口变量可以存储任何实现了接口定义的所有方法的变量。

Go中常见的接口ReaderWriter接口:

type Reader interface {Read(p []byte) (n int, err error)
}type Writer interface {Write(p []byte) (n int, err error)
}
var r io.Reader
tty, err := os.OpenFile("./", os.O_RDWR, 0)
if err != nil {return nil, err
}
r = tty

首先声明 r 的类型是 io.Reader,注意,这是 r 的静态类型,此时它的动态类型为 nil,并且它的动态值也是 nil

之后,r = tty 这一语句,将 r 的动态类型变成 *os.File,动态值则变成非空,表示打开的文件对象。这时,r 可以用<value, type>对来表示为: <tty, *os.File>

img

注意看上图,此时虽然 fun 所指向的函数只有一个 Read 函数,其实 *os.File 还包含 Write 函数,也就是说 *os.File 其实还实现了 io.Writer 接口。因此下面的断言语句可以执行:

var w io.Writer
w = r.(io.Writer)

之所以用断言,而不能直接赋值,是因为 r 的静态类型是 io.Reader,并没有实现 io.Writer 接口。断言能否成功,看 r 的动态类型是否符合要求。

这样,w 也可以表示成 <tty, *os.File>,仅管它和 r 一样,但是 w 可调用的函数取决于它的静态类型 io.Writer,也就是说它只能有这样的调用形式: w.Write()w 的内存形式如下图:

img

最后,我们再来一个赋值:

var empty interface{}
empty = w

由于 empty 是一个空接口,因此所有的类型都实现了它,w 可以直接赋给它,不需要执行断言操作。

img

从上面的三张图可以看到,interface包含三部分的信息:_type是类型信息,*data指向实际类型的实际值,itab包含实际类型的信息,包括大小,包路径,还包含绑定在类型上的各种方法。

反射的基本函数

reflect包里面定义了一个接口和一个结构体,reflect.Type是一个接口,reflect.Value是一个结构体,它们提供很多函数来存储在接口里面的类型信息。

reflect.Type 主要提供关于类型相关的信息,所以它和 _type 关联比较紧密;reflect.Value 则结合 _typedata 两者,因此程序员可以获取甚至改变类型的值。

reflect包中提供了两个基础的关于反射的函数来获取上述的接口和结构体:

func TypeOf(i interface{}) Type 
func ValueOf(i interface{}) Value

TypeOf函数用来提取一个接口中值的类型信息。由于它的输入参数是一个空的interface{},调用这个函数的时候,实参会先被转化为interface{}类型。这样,实参的类型信息,方法集,值信息都存储到interface{}变量里了。

func TypeOf(i interface{}) Type {eface := *(*emptyInterface)(unsafe.Pointer(&i))return toType(eface.typ)
}

这里的 emptyInterface 和上面提到的 eface 是一回事(字段名略有差异,字段是相同的),并且在不同的源码包:前者在 reflect 包,后者在 runtime 包。 eface.typ 就是动态类型。

type emptyInterface struct {typ  *rtypeword unsafe.Pointer
}

然后toType函数只是做了一个类型转换而已:

func toType(t *rtype) Type {if t == nil {return nil}return t
}

注意看,返回值Type实际上是一个接口,定义了很多方法,用来获取类型的相关的各种信息,而*rtype实现了Type接口。

type Type interface {// Methods applicable to all types.// 此类型的变量对齐后占用的字节数Align() int// 如果是struct自动,对齐后占用的字节数FieldAlign() int// 返回类型方法集李的第`i`(传入的参数)个方法Method(int) Method// 通过名称获取方法MethodByName(string) (Method, bool)// 获取类型方法集里导出的方法个数NumMethod() int// 类型名称Name() string// 返回类型所在的路径,如: encoding/base64PkgPath() string// 返回类型的大小,和unsafe.Sizeof功能类似Size() uintptr// 返回类型的字符串表示形式String() string// 返回类型的类型值Kind() Kind// 类型是否实现了接口 uImplements(u Type) bool// 是否可以赋值给 uAssignableTo(u Type) bool// 是否可以类型转换成 uConvertibleTo(u Type) bool// 类型是否可以比较Comparable() bool// 类型占据的位数Bits() int// 返回通道的方向,只能是chan类型调用ChanDir() ChanDir// 返回类型是否是可变参数,只能是func类型调用IsVariadic() bool// 返回内部子元素类型, 只能由类型Array, Chan, Map, Ptr, or Slice调用Elem() Type// 返回结构体类型的第i个字段,只能是结构体类型调用// 如果i超过了字段数,就会panicField(i int) StructField// 返回嵌套的结构体的字段FieldByIndex(index []int) StructField// 通过字段名获取字段FieldByName(name string) (StructField, bool)// 返回名称符合func函数的字段FieldByNameFunc(match func(string) bool) (StructField, bool)// 获取函数类型的第i个参数的类型In(i int) Type// 返回map的key类型,只能由类型map调用Key() Type// 返回Array的长度,只能由Array调用Len() int// 返回类型字段的数量,只能由类型Struct调用NumField() int// 返回函数类型的输入参数个数NumIn() int// 返回函数类型的返回值个数NumOut() int// 返回函数类型的第i个值的类型Out(i int) Type// 返回类型结构体的相同部分common() *rtype// 返回类型结构体的不同部分uncommon() *uncommonType
}

可见Type定义了非常多的方法,通过它们可以获取到类型的所有信息。

注意到Type方法集的倒数第二个方法common返回的rtype类型,它和_type是一回事,而且源代码里面也注释了,两边要保持同步。

type rtype struct {size       uintptrptrdata    uintptrhash       uint32tflag      tflagalign      uint8fieldAlign uint8kind       uint8alg        *typeAlggcdata     *bytestr        nameOffptrToThis  typeOff
}

所有的类型都会包含 rtype 这个字段,表示各种类型的公共信息;另外,不同类型包含自己的一些独特的部分。

比如下面的 arrayTypechanType 都包含 rytpe,而前者还包含 slice,len 等和数组相关的信息;后者则包含 dir 表示通道方向的信息。

// arrayType represents a fixed array type.
type arrayType struct {rtype `reflect:"array"`elem  *rtype // array element typeslice *rtype // slice typelen   uintptr
}// chanType represents a channel type.
type chanType struct {rtype `reflect:"chan"`elem  *rtype  // channel element typedir   uintptr // channel direction (ChanDir)
}

注意到,Type 接口实现了 String() 函数,满足 fmt.Stringer 接口,因此使用 fmt.Println 打印的时候,输出的是 String() 的结果。另外,fmt.Printf() 函数,如果使用 %T 来作为格式参数,输出的是 reflect.TypeOf 的结果,也就是动态类型。例如:

fmt.Printf("%T", 3) // int

TypeOf函数讲完了,我们接下来来看一下ValueOf函数。返回值reflect.Value表示interface{}里面存储的实际变量,它能提供实际变量的各种信息。

源码如下:

func ValueOf(i interface{}) Value {if i == nil {return Value{}}// ……return unpackEface(i)
}// 分解 eface
func unpackEface(i interface{}) Value {e := (*emptyInterface)(unsafe.Pointer(&i))t := e.typif t == nil {return Value{}}f := flag(t.Kind())if ifaceIndir(t) {f |= flagIndir}return Value{t, e.word, f}
}

从源码看,比较简单:将先将 i 转换成 *emptyInterface 类型, 再将它的 typ 字段和 word 字段以及一个标志位字段组装成一个 Value 结构体,而这就是 ValueOf 函数的返回值,它包含类型结构体指针、真实数据的地址、标志位。

Value 结构体定义了很多方法,通过这些方法可以直接操作 Value 字段 ptr 所指向的实际数据:

// 设置切片的 len 字段,如果类型不是切片,就会panicfunc (v Value) SetLen(n int)// 设置切片的 cap 字段func (v Value) SetCap(n int)// 设置字典的 kvfunc (v Value) SetMapIndex(key, val Value)// 返回切片、字符串、数组的索引 i 处的值func (v Value) Index(i int) Value// 根据名称获取结构体的内部字段值func (v Value) FieldByName(name string) Value// ……// 用来获取 int 类型的值
func (v Value) Int() int64// 用来获取结构体字段(成员)数量
func (v Value) NumField() int// 尝试向通道发送数据(不会阻塞)
func (v Value) TrySend(x reflect.Value) bool// 通过参数列表 in 调用 v 值所代表的函数(或方法
func (v Value) Call(in []Value) (r []Value) // 调用变参长度可变的函数
func (v Value) CallSlice(in []Value) []Value

另外,通过 Type() 方法和 Interface() 方法可以打通 interfaceTypeValue 三者。Type() 方法也可以返回变量的类型信息,与 reflect.TypeOf() 函数等价。Interface() 方法可以将 Value 还原成原来的 interface。

img

总结一下:TypeOf() 函数返回一个接口,这个接口定义了一系列方法,利用这些方法可以获取关于类型的所有信息; ValueOf() 函数返回一个结构体变量,包含类型信息以及实际值。

img

上图中,rtye 实现了 Type 接口,是所有类型的公共部分。emptyface 结构体和 eface 其实是一个东西,而 rtype 其实和 _type 是一个东西,只是一些字段稍微有点差别,比如 emptyface 的 word 字段和 eface 的 data 字段名称不同,但是数据型是一样的。

反射的三大的定律

根据 Go 官方关于反射的博客,反射有三大定律:

  1. Reflection goes from interface value to reflection object.
  2. Reflection goes from reflection object to interface value.
  3. To modify a reflection object, the value must be settable.

第一条是最基本的:反射是一种检测存储在 interface 中的类型和值机制。这可以通过 TypeOf 函数和 ValueOf 函数得到。

第二条实际上和第一条是相反的机制,它将 ValueOf 的返回值通过 Interface() 函数反向转变成 interface 变量。

前两条就是说 接口型变量反射类型对象 可以相互转化,反射类型对象实际上就是指的前面说的 reflect.Typereflect.Value

第三条不太好懂:如果需要操作一个反射变量,那么它必须是可设置的。反射变量可设置的本质是它存储了原变量本身,这样对反射变量的操作,就会反映到原变量本身;反之,如果反射变量不能代表原变量,那么操作了反射变量,不会对原变量产生任何影响,这会给使用者带来疑惑。所以第二种情况在语言层面是不被允许的。

举一个经典例子:

var x float64 = 3.4v := reflect.ValueOf(x)v.SetFloat(7.1) // Error: will panic.

执行上面的代码会产生 panic,原因是反射变量 v 不能代表 x 本身,为什么?因为调用 reflect.ValueOf(x) 这一行代码的时候,传入的参数在函数内部只是一个拷贝,是值传递,所以 v 代表的只是 x 的一个拷贝,因此对 v 进行操作是被禁止的。

可设置是反射变量 Value 的一个性质,但不是所有的 Value 都是可被设置的。

就像在一般的函数里那样,当我们想改变传入的变量时,使用指针就可以解决了。

var x float64 = 3.4p := reflect.ValueOf(&x)fmt.Println("type of p:", p.Type())fmt.Println("settability of p:", p.CanSet())

输出是这样的:

type of p: *float64settability of p: false

p 还不是代表 xp.Elem() 才真正代表 x,这样就可以真正操作 x 了:

v := p.Elem()v.SetFloat(7.1)fmt.Println(v.Interface()) // 7.1fmt.Println(x) // 7.1

关于第三条,记住一句话:如果想要操作原变量,反射变量 Value 必须要 hold 住原变量的地址才行。

参考自:码神桃花源的博客。

相关文章:

Go语言设计与实现 -- 反射

Go的反射有哪些应用&#xff1f; IDE中代码的自动补全对象序列化fmt函数的相关实现ORM框架 什么情况下需要使用反射&#xff1f; 不能明确函数调用哪个接口&#xff0c;需要根据传入的参数在运行时决定。不能明确传入函数的参数类型&#xff0c;需要在运行时处理任意对象。 …...

利用5G工业网关实现工业数字化的工业互联网解决方案

5G工业网关是一种用于将工业生产环境中的数据连接到工业互联网的解决方案。它可以利用高带宽、高速率、低时延的5G网络连接工业现场的PLC、传感器、工业设备和云端数据中心&#xff0c;从而实现工业数字化。 物通博联工业互联网解决方案 物通博联5G工业网关的使用步骤&#x…...

朋友当上项目测试组长了,我真的羡慕了

最近我发现一个神奇的事情&#xff0c;我一个朋友居然已经当上了测试项目组长&#xff0c;据我所知他去年还是在深圳的一家创业公司做苦逼的测试狗&#xff0c;短短8个月&#xff0c;到底发生了什么&#xff1f; 于是我立刻私聊他八卦一番。 原来他所在的公司最近正在裁员&am…...

element-ui实现动态添加表单项并实现事件触发验证验证

需求分析&#xff1a;点击新增后新增一个月度活动详情&#xff0c;提交时可同时提交多个月度活动详情。点击某一个月度活动信息的删除后可删除对应月度活动信息 H5部分&#xff1a; <el-dialog :title"title" :visible.sync"open" append-to-body>…...

ThreadLocal 内存泄漏问题

1. 认识ThreadLocal java中提高了threadlocal&#xff0c;为每个线程保存其独有的变量&#xff0c;threadlocal使用的一个小例子是&#xff1a; public class ThreadLocalTest {public static void main(String[] args) {ThreadLocal<String> threadIds new ThreadLoc…...

【算法】两道算法题根据提供字母解决解码方法和城市的天际线天际线问题

算法目录解码方法Java解答参考&#xff1a;天际线问题Java解答参考&#xff1a;大家好&#xff0c;我是小冷。 上一篇了解了项目相关的知识点 接下来看下两道算法题吧&#xff0c;用Java解答&#xff0c;可能更能激发一下大脑思考。 解码方法 题目要求&#xff1a; 一条包含…...

Python-TCP网络编程基础以及客户端程序开发

文章目录一. 网络编程基础- 什么是IP地址?- 什么是端口和端口号?- TCP介绍- socket介绍二. TCP客户端程序开发三. 扩展一. 网络编程基础 - 什么是IP地址? IP地址就是标识网络中设备的一个地址 IP地址分为 IPv4 和 IPv6 IPv4使用十进制, IPv6使用十六进制 查看本机IP地址: l…...

超低成本DDoS攻击来袭,看WAF如何绝地防护

一、DDoS攻击&#xff0c;不止于网络传输层 网络世界里为人们所熟知的DDoS攻击&#xff0c;多数是通过对带宽或网络计算资源的持续、大量消耗&#xff0c;最终导致目标网络与业务的瘫痪&#xff1b;这类DDOS攻击&#xff0c; 工作在OSI模型的网络层与传输层&#xff0c;利用协…...

CF1795E Explosions? (单调栈)

传送门 题意&#xff1a; 有 n 个怪兽需要消灭&#xff0c;它们的生命值分别是 h [1],h [2]......h [n]. 我们可以使用两种技能&#xff1a; 技能 1&#xff1a;选择任意一个怪兽&#xff0c;使其生命值降低 1 点&#xff0c;并且需要 1 点能量值. 技能 2&#xff1a;选择任意…...

C++——二叉树排序树

文章目录1 二叉搜索树概念2 二叉搜索树操作与模拟实现2.1 二叉搜索树的查找非递归版本递归版本2.2 二叉搜索树的插入非递归版本递归版本2.3 二叉搜索树的删除非递归版本递归版本3 二叉搜索树的应用&#xff08;K模型、KV模型&#xff09;4 二叉搜索树的性能分析1 二叉搜索树概念…...

深拷贝浅拷贝的区别?如何实现一个深拷贝?

一、数据类型存储 JavaScript中存在两大数据类型&#xff1a; 基本类型 Number String null Undefined Boolean symbol引用类型 array object function 基本类型数据保存在在栈内存中 引用类型数据保存在堆内存中&#xff0c;引用数据类型的变量是一个指向堆内存中实际对象的…...

Linux应用编程下连接本地数据库进行增删改查系列操作

文章目录前言一、常用SQL操作语句二、相关函数解析三、连接本地数据库四、编译运行五、程序源码前言 本篇为C语言应用编程下连接Linux本地数据库进行增删改查系列操作。 在此之前&#xff0c;首先当然是你需要具备一定的数据库基础&#xff0c;所以下面我先列出部分常用的SQL…...

图论学习03

图神经网络模型介绍 将图神经网络分为基于谱域上的模型和基于空域上的模型&#xff0c;并按照发展顺序详解每个类别中的重要模型。 基于谱域的图神经网络 谱域上的图卷积在图学习迈向深度学习的发展历程上起到了关键性的作用。三个具有代表性的谱域图神经网络 谱图卷积网络切…...

解决qt中cmake单独存放 .ui, .cpp, .h文件

设想 项目文件较多&#xff0c;全部放在一个目录下就像依托答辩。 希望能将头文件放入include&#xff0c;ui文件放入ui&#xff0c;源文件放入src。 为了将Qt代码和一般非Qt代码分离开&#xff0c;进一步地&#xff1a; 将Qt源文件放入qt_src&#xff0c;普通源文件放入sr…...

操作系统(day12)-- 基本分段存储,段页式存储

基本分段存储管理方式 不会产生内部碎片&#xff0c;会产生外部碎片 分段 按照程序自身的逻辑关系划分为 若干个段&#xff0c;每个段都有一个段名&#xff0c;每段从0开始编址 分段存储管理方式中一个段表项由段号&#xff08;隐含&#xff09;、段长、基地址 分段的段表项固…...

疯狂弹出请插入多卷集的最后一张磁盘窗口

整个人嘛了&#xff0c;今天插上U盘&#xff0c;跟tmd中了病毒一样&#xff0c; 屏幕疯狂弹出窗口&#xff0c; 提示请插入多卷集的最后一张磁盘&#xff01; 点确定之后他继续弹出&#xff0c;点取消它也继续弹出&#xff0c; 关掉一个又弹出来一个&#xff0c;妈的&#x…...

Spark12: SparkSQL入门

一、SparkSQL Spark SQL和我们之前讲Hive的时候说的hive on spark是不一样的。hive on spark是表示把底层的mapreduce引擎替换为spark引擎。而Spark SQL是Spark自己实现的一套SQL处理引擎。Spark SQL是Spark中的一个模块&#xff0c;主要用于进行结构化数据的处理。它提供的最核…...

show profile和trance分析SQL

目录 一.show profile分析SQL 二.trance分析优化器执行计划 一.show profile分析SQL Mysql从5.0.37版本开始增加了对show profiles和show profile语句的支持。show profiles能够在做SQL优化时帮助我们了解时间都耗费到哪里去了。。 通过have_profiling参数&#xff0c;能够…...

[AI生成图片] 效果最好的Midjourney 的介绍和使用

Midjourney介绍&#xff1a; 是一个文本生成图片的扩散模型&#xff0c;能够根据输入的任何文本生成令人难以置信的图像&#xff0c;让数十亿人在几秒钟内创造惊人的艺术。为方便用户控制和快速生成图片&#xff0c;打开后在页面底部输入文本内容&#xff0c;稍等一小会&#…...

Vue.use( ) 的核心原理

首先来思考几个问题&#xff1a; Vue.use是什么&#xff1f; vue.use() 是vue提供的一个静态方法&#xff0c;主要是为了注册插件&#xff0c;增加vue的功能。 Vue.use( plugin ) plugin只能是Object 或 Function vue.use()做了什么工作&#xff1f; 该js如果是对象 该对象…...

idea同时编辑多行-winmac都支持

1背景介绍 idea编辑器非常强大&#xff0c;其中一个功能非常优秀&#xff0c;很多程序员也非常喜欢用。这个功能能够大大大提高工作效率-------------多行代码同时编辑 2win 2.1方法1 按住alt鼠标左键上/下拖动即可 这样选中多行后&#xff0c;可以直接多行编辑。 优点&a…...

亿级高并发电商项目-- 实战篇 --万达商城项目 十一(编写商品搜索功能、操作商品同步到ES、安装RabbitMQ与Erlang,配置监听队列与消息队列)

&#x1f44f;作者简介&#xff1a;大家好&#xff0c;我是小童&#xff0c;Java开发工程师&#xff0c;CSDN博客博主&#xff0c;Java领域新星创作者 &#x1f4d5;系列专栏&#xff1a;前端、Java、Java中间件大全、微信小程序、微信支付、若依框架、Spring全家桶 &#x1f4…...

数据结构概述和稀疏数组

数据结构和算法内容介绍 1&#xff09;算法是程序的灵魂&#xff0c;优秀的程序可以在海量数据计算时&#xff0c;仍然保持高速计算 数据结构和算法概述 1&#xff09;程序 数据结构算法 2&#xff09;学好数据结构可以编写出更加漂亮&#xff0c;更加有效率的代码 3&…...

宝塔搭建实战人才求职管理系统adminm前端vue源码(三)

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 上一期给大家分享骑士cms后台admin前端vue在本地运行打包、宝塔发布部署的方式&#xff0c;本期给大家分享&#xff0c;后台adminm移动端后台vue前端怎么在本地运行&#xff0c;打包&#xff0c;实现线上功能更新…...

服务器是干什么用的?

首先&#xff0c;什么是服务器&#xff1f;服务器是提供计算服务器和网络服务的设备。服务器和计算机由CPU、硬盘、内存、系统总线等组成。比如我们访问一个网站&#xff0c;点击这个网站会发出访问请求&#xff0c;服务器会响应服务请求&#xff0c;进行相应的处理&#xff0c…...

C++ 之结构体与共用体

文章目录参考描述结构体使用&#xff08;基本&#xff09;声明初始化先创建后初始化C 11 列表初始化使用&#xff08;进阶&#xff09;结构数组声明&#xff08;拓展&#xff09;声明及创建声明及初始化匿名结构体细节外部声明与内部声明成员赋值共用体存储空间匿名共用体同化尾…...

Java基础知识汇总(良心总结)

1. 前言 本文章是对Java基础知识进行了汇总&#xff0c;方便大家学习。 申明&#xff1a;文章的内容均来自黑马程序员&#xff0c;博主只是将其搬到了CSDN上以共享给大家学习 2. 目录 Day01 Java学习笔记之《开章》 Day02 Java学习笔记之《运算符》 Day03 Java学习笔记之《流程…...

InnoDB之Undo log格式

1. 前言 InnoDB有两大日志模块&#xff0c;分别是redo log和undo log。为了避免磁盘随机写&#xff0c;InnoDB设计了redo log&#xff0c;数据写入时只写缓冲页和redo log&#xff0c;脏页由后台线程异步刷盘&#xff0c;哪怕系统崩溃也能根据redo log恢复数据。但是我们漏了一…...

一问学习StreamAPI终端操作

Java Stream管道流是用于简化集合类元素处理的java API。 在使用的过程中分为三个阶段&#xff1a; 将集合、数组、或行文本文件转换为java Stream管道流管道流式数据处理操作&#xff0c;处理管道中的每一个元素。上一个管道中的输出元素作为下一个管道的输入元素。管道流结果…...

在屎山代码中快速找到想要的代码法-锁表法(C#)

由于本人水平有限&#xff0c;文中有写得不对的地方请指正&#xff0c;本文的方法有些投机取巧&#xff0c;实在是没招的情况下可以酌情使用&#xff0c;如有侵权&#xff0c;请联系删除。 前几天接到一个需求&#xff0c;要在医嘱签署时对检验项目进行分方操作&#xff0c;分…...

个人网站建设方案书模板/营销技巧有哪些

如果你忘记了mysql密码几乎是没有什么好办法可以直接修改密码了&#xff0c;但我们可以在my.ini把加上skip-grant-tables&#xff0c;然后重启mysql就不需要密码了&#xff0c;这时我们再修改root密码&#xff0c;最后再把skip-grant-tables删除重启即可。 首先&#xff0c;很…...

b2c网站的认识/哪里可以代写软文

对于大部分IT人士来说&#xff0c;给电脑重装系统、迁移操作系统、给硬盘分区、磁盘分区克隆等都是经常要做且不得不做的事情&#xff0c;那么使用哪款软件来管理磁盘分区&#xff0c;就成了大多数人比较纠结的问题。究竟有没有什么分区管理软件是比较靠谱又安全的呢&#xff1…...

现在做网站用什么工具/app代理推广合作50元

一.前期基础知识储备 traces.txt系统自动生成的记录anr等异常的文件&#xff0c;只记录java代码产生的异常。 如果是旧版本&#xff08;Android Studio3以下的版本&#xff09;的AS&#xff0c;可以直接通过DDMS的File Explorer直接导出来&#xff1a; 之上的AS由于移除了DD…...

机械厂做网站到底有没有效果/刷关键词排名seo

注意&#xff1a;当你发现你设置了所有改设置的后&#xff0c;下拉刷新还是不能使用的话就去找找你的代码是不是存在俩个下拉刷新的方法 json文件夹 {"enablePullDownRefresh": true,"backgroundTextStyle": "dark" }需要刷新的数据 /** 小程…...

即墨网站建设公司/武汉seo招聘网

text...........................

普通网站做/百度官网进入

vs更新2017最新版本后&#xff0c;项目调试浏览器莫名其妙出现闪退&#xff0c;每次都TMD想打人… 先说下vs更新后出现哪些反人类的操作&#xff1a; –1.每调试一个项目就会单独重启浏览器&#xff0c;而且该调试浏览器关闭后&#xff0c;vs调试也会关闭。 –2.在web中调试&…...