Go——面向对象

一. 匿名字段

        go支持只提供类型而不写字段名的方式,也就是匿名字段,也称为嵌入字段。

  • 同名字段的情况

  • 所以自定义类型和内置类型都可以作为匿名字段使用

  • 指针类型匿名字段

二.接口

        接口定义了一个对象的行为规范,但是定义规范不实现,由具体的对象实现规范的细节。

        2.1 接口类型

        在Go语言中接口(interface)是一种类型,一种抽象类型。

        interface是一组method的集合,接口做的事情就像定义一个协议,不关心属性(数据),只关心行为(方法)。

        2.2 为什么使用接口

        查看下面的图片,只是由于类型不同,就需要定义两个逻辑一样的函数。如果后面出现了其它动物,也会需要定义函数。

        Go语言为了解决类似上面的问题,就设计了接口这个概念。接口区别于我们之前所有的具体类型,接口是一个抽象类型。当你看到一个接口类型是,你不知道他是什么,唯一知道的是通过它的方法能做什么。 

        2.3 接口定义

        Go语言提倡面向接口编程。

  • 接口在底层实现上包含两部分,即类型(type)和数据(data)。
  • 接口是一个或多个方法签名的集合
  • 任何类型的方法集中只要拥有该接口对应的全部方法,就表示它实现了该接口,无须在该类型上显示声明实现了那个接口。这称为Structural Typing。
  • 所谓对应方法,是指有相同名称,参数列表(不包括参数名)以及返回值。
  • 当然,该类型还可以有其它方法。
  • 接口只有方法声明,没有实现,没有数据字段(属性)
  • 接口可以匿名嵌入其它接口,或者嵌入到其它结构中。
  • 对象赋值给接口时,会发生拷贝,而接口内部存储的是指向这个复制品的指针,即无法修改复制品的状态,也无法获取指针。即如果对象是结构体或者基本数据类型,它会被值拷贝到接口中。如果对象是指针类型,这个指针指向的结构体实现了接口,那么接口中存储的是指针的副本,而不是指针本身。
  • 只有当接口存储的类型和对象都为nil时,接口才为nil。
  • 接口调用不会做receiver的自动转换。
  • 接口同样支持匿名字段方法。
  • 接口可以实现类似面向对象编程(OOP)的多态。
  • 空接口可以作为任何类型数据的容器。空接口是没有声明任何方法的接口。但是,也无法通过空接口来调用对象的方法或访问其属性。
  • 一个类型可以实现多个接口。
  • 接口命名习惯以er结尾。

        每一个接口由数个方法组成,接口的定义格式如下:

type 接口类型名 interface{方法1(参数列表1)返回值列表1方法2(参数列表2)返回值列表2...
}

        其中:

  • 接口名:使用type将接口名定义为自定义的类型名。Go语言的接口在命名时,一般会在单词后面添加er,接口名最好要能突出该接口的类型含义。
  • 方法名:当方法名首字母是大写且接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。
  • 参数列表和返回值列表:参数列表和返回值列表中的参数变量名可以省略。

        举个例子:

type writer interface{Write([]byte) error
}

        当看到这个接口的时候,你不知道他是什么,唯一知道的是可以通过它的Write方法来做一些事情。

        2.4 实现接口的条件

        一个对象只要全部实现了接口的方法,那么就实现了这个接口。换句话说,接口就是一个需要实现的方法列表。

package mainimport "fmt"type Sayer interface {Say()
}type Cat struct{}//实现了Sayer接口
func (c *Cat) Say() {fmt.Println("喵喵喵...")
}type Dog struct{}//实现了Sayer接口
func (d *Dog) Say() {fmt.Println("汪汪汪...")
}

        2.5 接口类型变量

        接口类型变量能够存储所有实现了该接口的实例。

        需要指针传入是因为方法的receiver为指针类型,对象的指针类型(*T)的方法集为值类型(T)和指针类型(*T)。

        2.6 值接收者和指针接收者实现接口的区别

        值接收者和指针接收者实现接口区别在于:方法集的不同。接口接收值类型(T)对象方法集为值接收者(T)实现的方法。接口接收指针类型对象(*T)方法集为值接收者(T)和指针接收者(*T)现象的方法。

  • 值接收者

  •  指针接收者

        2.7 类型与接口的关系

        2.7.1 一个类型实现多个接口

        一个类型可以同时实现多个接口,而接口间彼此独立,不知道对方的实现。

                2.7.2 多个类型实现同一个接口

        Go语言中不同的类型还可以实现同一接口。

        一个接口的方法不一定需要由一个类型完全实现,接口的方法可以通过在类型中嵌入其它类型或者结构体来实现。

        2.7.3 接口嵌套

         接口与接口之间可以通过嵌套创造出新的接口。

type Sayer interface {Say()
}type Mover interface {Move()
}type Animal interface {SayerMover
}

        嵌套的接口的使用和普通接口一样,这里实现嵌套的接口。

        2.8 空接口

        2.8.1 空接口的定义

        空接口是指没有定义任何方法的接口。因此任何类型都实现了空接口。

        空接口类型变量可以存储任意类型的变量。

        2.8.2 空接口的应用

  • 空接口作为函数参数

        使用空接口实现可以接收任意类型的函数参数。

  • 空接口作为map的值

        使用空接口实现可以保存任意值的字典。

         2.8.3 类型断言

        空接口可以存储任意类型的值,那我们如何获取其存储的具体数据呢?

  • 接口值

        一个接口的值(简称接口值)是由一个具体类型和具体类型的值俩个部分组成。这两部分分别称为接口的动态类型和动态值。

        我们来看一个例子:

package mainimport ("bytes""io""os"
)func main() {var w io.Writerw = os.Stdoutw = new(bytes.Buffer)w = nil
}

        图解: 

        想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:

x.(T)

        其中:

  • x:表示类型为interface{}的变量
  • T :表示断言x可能是的类型

        该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个是布尔值,若为true表示断言成功,为false则表示断言失败。

        举个例子:

        自定义类型: 

        我们还可以使用switch语句来实现多个类型的断言:

        因为空接口可以存储任意类型值的特点,所以空接口在Go语言中使用十分广泛。

         但是接口需要注意的是,只有当有两个或者两个以上的具体类型必须以相同的方式进行处理时才需要定义接口。不要为了接口而写接口,那样会增加不必要的抽象,导致不必要的运行时消耗。

三. 接口的底层实现

  • 案例
package maintype EInterface interface{}
type IInterface interface {Do()
}type IInterfaceImpl struct{}func (imp1 IInterfaceImpl) Do() {}func main() {var impl1 EInterface = IInterfaceImpl{}var impl2 IInterface = IInterfaceImpl{}println(impl1)println(impl2)
}

        3.1 数据结构

        golang中的接口非为带方法的接口和不带方法的空接口,带方法的接口在底层使用iface表示,空接口的底层则是eface表示。

  • eface

        eface是空接口类型的底层实现,源码如下:

type eface struct {_type *_typedata  unsafe.Pointer
}

两个字段都是指针类型,含义分别是:

  • _type:指向实际的类型。上面案例是IInterfaceImpl
  • data:指向实际的值。上面案例是IInterfaceImpl结构体的值

        注意:var v interface{} = (*int)nil,变量v其实使用的eface结构表示。其中_type的类型对应的是int类型的指针,而data部分为nil,所以整体变量v != nil。 

  • iface

        iface是非空接口类型的底层实现,源码如下:

type iface struct {tab  *itabdata unsafe.Pointer
}

        上面的案例impl2就是非空接口类型的变量,两个字段也是指针类型,含义分别是:

  • tab:指向itab结构体,itab结构体存储了接口所有方法列表。
  • data:指向对应的值。上面案例为IInterfaceImpl结构体的值。 

  • _type结构

        该结构于golang的类型系统有关,无论是内置类型还是自定义数据类型,都用_type结构表示其元信息。

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
}

        以int32类型的指针为例,具体的字段含义及其作用如下:

  • size:类型的大小(字节数)。*int32类型的大小是64位系统下8字节,32位系统下4字节。
  • ptrdata:所有指针内存前缀大小,指向int32类型的实例。
  • hash:类型的哈希值,即_type.hash。
  • tflag:类型标记,表示类型的特性。
  • align:类型的对齐方式,*int32类型32位系统下按4字节对齐,64位系统下8字节对齐。
  • fieldAlign:字段对齐方式,*int32类型的字段对齐方式 32位系统下按4字节对齐,64位系统下8字节对齐。
  • kind:类型的种类。用于区分基本类型,结构体,接口等。
  • equal:比较函数,用于区分两个*int32类型的实例是否相等。
  • gcdata:与GC有关。
  • str:类型名称的偏移。
  • ptrToThis:指向该类型的指针。

         _type是一个很复杂的结构,这里只需要知道,通过该结构能获取到结构体实现的所有方法。

        下面是iface.go中的init方法中的一段代码,_type结构的uncommon方法会返回一个指针,在此基础上加一个偏移量(moff)就能得到实际结构体实现的方法列表。

func (m *itab) init() string {inter := m.intertyp := m._typex := typ.uncommon()// both inter and typ have method sorted by name,// and interface names are unique,// so can iterate over both in lock step;// the loop is O(ni+nt) not O(ni*nt).ni := len(inter.mhdr)nt := int(x.mcount)// 实际类型的方法数组xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]...
}
  • itab结构

        itab结构是golang非空接口iface中一个非常重要的字段,类型的赋值,断言等都离不开该字段。

type itab struct {// 接口类型的指针,比如对于 io.Reader 接口,记录的是接口类型的信息(如接口定义的方法,Read 方法)inter *interfacetype// 实际结构类型的指针,记录的是实际类型的信息,比如 os.File 类型,实现了 io.Reader 接口_type *_typehash  uint32 // copy of _type.hash. Used for type switches._     [4]byte// 变长数组,fun[0]==0 表示 _type 没有实现 inter 接口fun   [1]uintptr
}type interfacetype struct {// 接口类型元信息typ     _type// 包路径pkgpath name// 接口的所有方法列表mhdr    []imethod
}

        itab结构的字段含义:

  • inter记录的是非空接口类型的元信息,其中mhdr是接口的方法表
  • _type记录的是实际类型的指针,即实现接口的类型
  • fun保存的是实际类型中实现的方法的地址。当fun[0] == 0时,表示该类型没有实现该接口;当fun[0]!=0时,则代表该类型实现了接口的所有方法,这时候就可以通过偏移量调用具体类型对象的方法。

        3.2 itab关键方法

        通过上述对itab结构的描述,不难理解,itab其实就是一个缓存,用于快速判断具体类型是否实现了某个接口。

        一般情况下,如果要判断需要对接口的具体类型的方法集进行比较。当如果每次都这样比较,效率会很低。通过将比较结果缓存起来,下次再判断的时候,就能直接根据itab快速得出结论了。

        go源码的runtime里定义了全局变量itebTable,用户缓存itab。

// 用于缓存 itab
itabTable     = &itabTableInit
itabTableInit = itabTableType{size: itabInitSize}// 全局的 itab 表
type itabTableType struct {size    uintptr             // entries 的长度count   uintptr             // 当前 entries 的数量,即 itab 数量entries [itabInitSize]*itab // 保存 itab 的哈希表
}

        这里其实是一个全局的哈希表,哈希表的key就是interfacetype + _type,value就是对应的itab。

        判断某个类型是否实现了接口时,只需要传入接口的接口类型interfacetype和实际类型_type即可:

  • 如果在itabTable中没有找到对应的itab,则需要依次比较方法集,生成itab并缓存到itabTable中
  • 如果找到了对应的itab,则判断func[0],如果等于0则说明该类型没有实现该接口 

        有了哈希表,还要考虑如何向其中添加数据和获取数据,也就是下面两个方法。

  • getitab

        该函数的作用是:通过interfacetype和_type,也就是接口类型和实际结构类型,从表中获取对应itab。

func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {...var m *itab// 尝试从 itabTable 表中获取 itab,获取到直接返回t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))if m = t.find(inter, typ); m != nil {goto finish}// 没有找到,获取锁再次查找lock(&itabLock)if m = itabTable.find(inter, typ); m != nil {unlock(&itabLock)goto finish}// 如果 itabTable 中没有找到,则新建一个 itab,并调用 itabAdd 将其缓存到 itabTable 中m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*goarch.PtrSize, 0, &memstats.other_sys))m.inter = interm._type = typm.hash = 0m.init()itabAdd(m)unlock(&itabLock)
finish:// m.fun[0] != 0 表示该类型实现了接口的所有方法,可以返回 itabif m.fun[0] != 0 {return m}//canfail 用于控制类型转换失败的行为。比如 v := s.(Dst),这里的 canfail == false,那么在断言失败时,会 panicif canfail {return nil}panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}

        这里主要的逻辑是:

  • 更具interfacetype和_type,尝试从itabTable中获取itab
  • 如果itabTable没有找到itab,则新创建一个itab并将其缓存到itabTable中
  • 判断该类型是否实现了接口的所有方法(m.fun[0]!=0)
  • 如果该类型没有实现接口的所有方法,则根据canfail判断是否panic,canfail为false则会panic。比如类型断言时,如果不接受第二个返回值,则断言失败会panic

        首次调用getitab方法获取时,哈希表中是没有对应数据的。此时不仅要创建itab结构,还要对其涉及到的接口和类型的方法集进行判断,初始化等。相关代码在init中。

func (m *itab) init() string {inter := m.intertyp := m._typex := typ.uncommon()// 接口定义的方法数量ni := len(inter.mhdr)// 实际类型的方法数量nt := int(x.mcount)// 实际类型的方法数组xmhdr := (*[1 << 16]method)(add(unsafe.Pointer(x), uintptr(x.moff)))[:nt:nt]j := 0// 保存接口的第i个方法对应的实际类型的方法的地址methods := (*[1 << 16]unsafe.Pointer)(unsafe.Pointer(&m.fun[0]))[:ni:ni]var fun0 unsafe.Pointer
imethods:// 遍历接口方法列表for k := 0; k < ni; k++ {// 接口的方法i := &inter.mhdr[k]// 接口的方法类型itype := inter.typ.typeOff(i.ityp)// 接口的方法名称name := inter.typ.nameOff(i.name)// 接口的方法名iname := name.name()// 接口的包路径ipkg := name.pkgPath()if ipkg == "" {ipkg = inter.pkgpath.name()}// 根据接口方法查找实际类型的方法for ; j < nt; j++ {// 实际类型的方法t := &xmhdr[j]// 实际类型的方法名tname := typ.nameOff(t.name)// 比较接口的方法名和实际类型的方法是否一致,包括名称和类型if typ.typeOff(t.mtyp) == itype && tname.name() == iname {pkgPath := tname.pkgPath()if pkgPath == "" {pkgPath = typ.nameOff(x.pkgpath).name()}// 如果是导出方法或在同一个包,则将将方法保存到 itab 中if tname.isExported() || pkgPath == ipkg {if m != nil {// 实际类型的方法指针,通过该指针可以调用实际类型的方法ifn := typ.textOff(t.ifn)if k == 0 {fun0 = ifn // we'll set m.fun[0] at the end} else {methods[k] = ifn}}continue imethods}}}// 该类型没有实现接口// 如果每个接口方法都被实现了,则每次都会走到 continue 的逻辑,不会将 fuc[0] 置为 0m.fun[0] = 0return iname}m.fun[0] = uintptr(fun0)return ""
}

        主要逻辑是,依次遍历接口的所有方法,并在实际类型的接口列表中查找对应的实现,只要有一个接口方法没有被实现,则将itab的fun[0]置为0,表示该类型没有实现该接口。

  • itabadd

        getitab方法如果没有找到itab,会新建一个itab并调用itabAdd方法将其缓存到itabTable中。

func itabAdd(m *itab) {...t := itabTable// 容量超过 75% 时会触发扩容if t.count >= 3*(t.size/4) { // 75% load factor// 扩容为原哈希表的 2 倍大小t2 := (*itabTableType)(mallocgc((2+2*t.size)*goarch.PtrSize, nil, true))t2.size = t.size * 2// 将原哈希表的元素复制到新哈希表// 复制过程中,其他的线程可能会尝试从原哈希表中获取 itab,但找不到。此时会尝试获取锁(会阻塞)后再次获取。iterate_itabs(t2.add)-】if t2.count != t.count {throw("mismatched count during itab table copy")}// 使用原子操作将 itabTable 的引用指向新扩容的内存atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))// Adopt the new table as our own.t = itabTable// Note: the old table can be GC'ed here.}// 将新的 itab 缓存到 itabTable 中t.add(m)
}

        3.3 接口赋值

        将某一具体类型赋值给接口类型时,本质其实时如何填充eface和iface结构体。

        对eface结构体来说,由于只有_type和data字段,因此只需要进行字段赋值即可。

        对于iface结构体来说,需要通过itab判断类型值是否实现了接口的所有方法(itab可能不存在,会走一遍getitab的流程),然后初始化iface结构的tab和data字段。

        底层会调用runtime.convTXXX转换为iface或eface的data字段。

func convT(t *_type, v unsafe.Pointer) unsafe.Pointer {...// 分配`_type`所需的内存x := mallocgc(t.size, t, true)// 将v的值复制到刚分配的内存typedmemmove(t, x, v)return x
}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

强大的压缩和解压缩工具 Keka for Mac

Keka for Mac是一款功能强大的压缩和解压缩工具&#xff0c;专为Mac用户设计。它支持多种压缩格式&#xff0c;包括7z、Zip、Tar、Gzip和Bzip2等&#xff0c;无论是发送电子邮件、备份文件还是节省磁盘空间&#xff0c;Keka都能轻松满足用户需求。 这款软件的操作简单直观&…

用c++实现串匹配问题、选择排序

5.2.2 串匹配问题 【问题】 给定两个字符串S和T&#xff0c;在主串S中查找子串T的过程称为串匹配(string matching,也称模式匹配&#xff09;&#xff0c;T称为模式。在文本处理系统、操作系统、编译系统、数据库系统以及 Internet 信息检索系统中&#xff0c;串匹配是使用最频…

分布式系统:缓存与数据库一致性问题

前言 缓存设计是应用系统设计中重要的一环&#xff0c;是通过空间换取时间的一种策略&#xff0c;达到高性能访问数据的目的&#xff1b;但是缓存的数据并不是时刻存在内存中&#xff0c;当数据发生变化时&#xff0c;如何与数据库中的数据保持一致&#xff0c;以满足业务系统…

Linux shell编程学习笔记46:awk命令的由来、功能、格式、选项说明、版权、版本

0 前言 在编写Linux Shell脚本的过程中&#xff0c;我们经常要对Linux命令执行的结果进行分析和提取&#xff0c;Linux也在文本分析和提取这方面提供了不少的命令。比如我们之前研究过的cut命令。 Linux shell编程学习笔记43&#xff1a;cut命令https://blog.csdn.net/Purple…

史上最全excel导入功能测试用例设计(以项目为例)

web系统关于excel的导入导出功能是很常见的&#xff0c;通常为了提高用户的工作效率&#xff0c;在维护系统中的一些数据的时候&#xff0c;批量导入往往比一个一个添加或者修改快很多。针对导入功能的测试&#xff0c;往往会有很多种情况&#xff0c;现在针对平时项目中遇到的…

Excel·VBA二维数组S形排列

与之前的文章《ExcelVBA螺旋数组函数》将一维数组转为二维螺旋数组 本文将数组转为S形排列的二维数组&#xff0c;类似考场座位S形顺序 Function S形排列(ByVal arr, ByVal num_rows&, ByVal num_cols&, Optional ByVal mode$ "row")将数组arr转为num_rows…

Home Assistant OS转 Hassio Supervisor(docker 版本)

这是一个失败案例&#xff0c;请忽略。 原因 HAOS缺点&#xff1a;系统不是很好用&#xff0c;无法满足我在上面使用python开发插件的小需求&#xff08;或许有方法满足&#xff0c;但是我没找到&#xff09;。 HAOS优点&#xff1a;方便安装&#xff0c;配置非常方便。 数据…

UE5学习日记——实现自定义输入及监听输入,组合出不同的按键输入~

UE5的自定义按键和UE4有所不同&#xff0c;在这里记录一下。 本文主要是记录如何设置UE5的自定义按键&#xff0c;重点是学会原理&#xff0c;实际开发时结合实际情况操作。 输入映射 1. 创建输入操作 输入操作并不是具体的按键映射&#xff0c;而是按键的激活方式&#xff0…

面试官:说一说CyclicBarrier的妙用!我:这个没用过...

写在开头 面试官&#xff1a;同学&#xff0c;AQS的原理知道吗&#xff1f; 我&#xff1a;学过一点&#xff0c;抽象队列同步器&#xff0c;Java中很多同步工具都是基于它的… 面试官&#xff1a;好的&#xff0c;那其中CyclicBarrier学过吗&#xff1f;讲一讲它的妙用吧 我&…

音乐文件逆向破解

背景 网易云等在线音乐文件的加密源码都按照一定的规则加密&#xff0c;通过对音乐文件的源码分析转化&#xff0c;有望实现对加密文件的解密 实现内容 实现对加密音乐文件的解密 实现对无版权的音乐文件的转化 实现环境 010editor 010 Editor是一个专业的文本编辑器和十六…

FFmpeg: 自实现ijkplayer播放器--04消息队列设计

文章目录 播放器状态转换图播放器状态对应的消息&#xff1a; 消息对象消息队列消息队列api插入消息获取消息初始化消息插入消息加锁初始化消息设置消息参数消息队列初始化清空消息销毁消息启动消息队列终止消息队列删除消息 消息队列&#xff0c;用于发送&#xff0c;设置播放…

破译验证码reCAPTCHA 之 打码平台

由于登录需要验证码&#xff0c;除了日常的字符串&#xff0b;数字&#xff0c;此时就需要用第三方插件进行破译。 reCaptcha是Google公司的验证码服务&#xff0c;方便快捷&#xff0c;改变了传统验证码需要输入n位失真字符的特点。 1. reCAPTCHA 初识 reCaptcha是Google公司…

1、IPEX-LLM(原名BigDL-LLM)环境配置

IPEX-LLM 是一个为Intel XPU (包括CPU和GPU) 打造的轻量级大语言模型加速库&#xff0c;在Intel平台上具有广泛的模型支持、最低的延迟和最小的内存占用。 您可以使用 IPEX-LLM 运行任何 PyTorch 模型&#xff08;例如 HuggingFace transformers 模型&#xff09;。在运行过程中…

结合创新!ResNet+Transformer,高性能低参数,准确率达99.12%

今天给各位介绍一个发表高质量论文的好方向&#xff1a;ResNet结合Transformer。 ResNet因其深层结构和残差连接&#xff0c;能够有效地从图像中提取出丰富的局部特征。同时&#xff0c;Transformer的自注意力机制能够捕捉图像中的长距离依赖关系&#xff0c;为模型提供全局上…

世界需要和平--中介者模式

1.1 世界需要和平 "你想呀&#xff0c;国与国之间的关系&#xff0c;就类似于不同的对象与对象之间的关系&#xff0c;这就要求对象之间需要知道其他所有对象&#xff0c;尽管将一个系统分割成许多对象通常可以增加其可复用性&#xff0c;但是对象间相互连接的激增又会降低…

MySQL中的存储过程详解(上篇)

使用语言 MySQL 使用工具 Navicat Premium 16 代码能力快速提升小方法&#xff0c;看完代码自己敲一遍&#xff0c;十分有用 拖动表名到查询文件中就可以直接把名字拉进来中括号&#xff0c;就代表可写可不写 目录 1.认识存储过程 1.1 存储过程的作用 1.2 存储过程简介…

【word】文档标题如何自动编号

我在写一个word文档的时候&#xff0c;每一级标题的格式都设置好了&#xff0c;包括字体&#xff0c;大小等等&#xff0c;但是如何自动编号呢&#xff1f; 在写中期报告的时候&#xff0c;我对每一级标题的格式都创建了一个单独的样式&#xff0c;像这样&#xff1a; 对于每一…

数据类型知识

1&#xff0c;介绍 根据数据所占的空间不同&#xff0c;把数据分为不同的数据类型 js的变量数据类型是在程序运行中&#xff0c;靠等号右边数值的值来判断的 js动态变量&#xff0c;里面的数据类型是可以变化的 2.数据类型 1.简单数据类型 程序里面&#xff0c;数字前面有…

plotly绘图——热力图

文章目录 介绍热力图基础热力图代码解释 多热力图代码解释 显示数字的热力图代码解释 介绍 plotly是一个易于使用&#xff0c;功能强大的python绘图库&#xff0c;用于构建可交互式的图表&#xff08;可以自行运行后使用鼠标拖拽图片试试&#xff09;&#xff0c;本系列文章将介…

基于springboot+vue的企业人事管理设计与实现

前言 基于Java的企业人事管理设计与实现&#xff0c;可以让用户在最短的时间里享受到最好的服务&#xff1b;而开发本系统&#xff0c;又能够提高系统整体工作水平&#xff0c;简化工作程序&#xff0c;这对管理员和员工来说都是一件非常乐意的事情。 本系统针对基于Java的企…