Go 语言面试题详解之接口 (Interface) 详解一文吃透

自古流传着一个传言...在 Go 语言面试的时候必有人会问接口(interface)的实现原理。这又是为什么?为何对接口如此执着?

实际上,Go 语言的接口设计在整体扮演着非常重要的角色,没有他,很多程序估计都跑的不愉快了。

在 Go 语言的语义上,只要某个类型实现了所定义的一组方法集,则就认为其就是同一种类型,是一个东西。大家常常称其为鸭子类型(Duck typing),因为其与鸭子类型类型的定义相对吻合。

图来自网络后重整

在维基百科中,鸭子类型的谚语定义为 ”If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.“,翻译过来就是 ”如果它看起来像鸭子,像鸭子一样游泳,像鸭子一样嘎嘎叫,那他就可以认为是鸭子“。

回归到 Go 语言,在接口之下,接口又蕴含了怎么样的底层结构,其设计原理和思考又是什么呢?我们不能只看表面,接下来在这一章节中都会进行一一分析和道来。看看其深层到底是何 “物”。

Go 语言的接口(interface)是其核心特性之一。与其他语言的接口不同,Go 的接口没有显式声明的继承和实现关系,而是通过隐式实现来工作。这种设计理念使得 Go 的接口更加灵活和简洁。掌握 Go 的接口不仅是成为优秀 Go 开发者的基础,也是在面试中经常考察的重要部分。

本文将详细解密 Go 语言中的接口(interface),从基础到进阶,提供面试中常见的接口题目解析与示例代码。

一、什么是接口(Interface)?

在 Go 语言中,接口是一种抽象类型,它定义了一组方法,但并不提供方法的实现。任何类型只要实现了接口中定义的所有方法,就隐式地实现了该接口,无需显式声明。

Go 语言的接口使得不同的类型可以通过实现同样的接口来共享相同的行为,从而实现多态性。Go 的接口设计非常灵活,不强制要求显式的继承,类型与接口的关系是隐式的。

本文目录:

什么是 interface

Go 语言中的接口声明:

type Human interface {Say(s string) error
}

关键字主体为 type xxx interface,紧接着可以在方括号中编写方法集,用于声明和定义该接口所包含的方法集。

更进一步的代码演示:

type Human interface {Say(s string) error
}type TestA stringfunc (t TestA) Say(s string) error {fmt.Printf("煎鱼:%s\n", s)return nil
}func main() {var h Humanvar t TestA_ = t.Say("炸鸡翅")h = t_ = h.Say("烤羊排")
}

输出结果:

煎鱼:炸鸡翅
煎鱼:烤羊排

我们在上述代码中,声明了一个名为 Humaninterface,其包含一个 Say 方法。同时我们声明了一个 TestA 类型,也有自己的一个 Say 方法。他们两者的方法入参和出参类型均为一样。

而与此同时,我们在主函数 main 中通过声明和赋值,成功将类型为 TestA 的变量 t 赋给了类型为 Human 的变量 h,也就是说两者只因有了个 Say 方法,在 Go 语言的编译器中就认为他们是 “一样” 的了,这也就是业界中常说的鸭子类型。

数据结构

通过上面的功能代码一看,似乎 Go 语言非常优秀。一个接口,不同的类型,2 个包含相同的方法,也能够对标到一起。

接口到底是怎么实现的呢?底层数据结构又是什么?带着问题,我们开始深挖细节之路。

在 Go 语言中,接口的底层数据结构在运行时一共分为两类结构体(struct),分别是:

  • runtime.eface 结构体:表示不包含任何方法的空接口,也称为 empty interface。
  • runtime.iface 结构体:表示包含方法的接口。

runtime.eface

首先我们来介绍 eface,看看 “他” 到底是何许人也。源码如下:

type eface struct {_type *_typedata  unsafe.Pointer
}

其表示不包含任何方法的空接口。在结构上来讲 eface 非常简单,就两个属性,分别是 _typedata 属性,分别代表底层的指向的类型信息和指向的值信息指针。

再进一步到 type 属性里看看,其包含的类型信息更多:

type _type struct {size       uintptrptrdata    uintptr hash       uint32tflag      tflagalign      uint8fieldAlign uint8kind       uint8equal func(unsafe.Pointer, unsafe.Pointer) boolgcdata    *bytestr       nameOffptrToThis typeOff
}

  • size:类型的大小。
  • ptrdata:包含所有指针的内存前缀的大小。
  • hash:类型的 hash 值。此处提前计算好,可以避免在哈希表中计算。
  • tflag:额外的类型信息标志。此处为类型的 flag 标志,主要用于反射。
  • align:对应变量与该类型的内存对齐大小。
  • fieldAlign:对应类型的结构体的内存对齐大小。
  • kind:类型的枚举值。包含 Go 语言中的所有类型,例如:kindBoolkindIntkindInt8kindInt16 等。
  • equal:用于比较此对象的回调函数。
  • gcdata:存储垃圾收集器的 GC 类型数据。

总结一句,就是类型信息所需的信息都会存储在这里面,其中包含字节大小、类型标志、内存对齐、GC 等相关属性。而在 eface 来讲,其由于没有方法集的包袱,因此只需要存储类型和值信息的指针即可,非常简单。

runtime.iface

其次就是我们日常在应用程序中应用的较多的 iface,源码如下:

type iface struct {tab  *itabdata unsafe.Pointer
}

eface 结构体类型一样,主要也是分为类型和值信息,分别对应 tabdata 属性。但是我们再加思考一下,为什么 iface 能藏住那么多的方法集呢,难道施了黑魔法?

为了解密,我们进一步深入看看 itab 结构体。源码如下:

type itab struct {inter *interfacetype_type *_typehash  uint32 _     [4]bytefun   [1]uintptr 
}

  • inter:接口的类型信息。
  • _type:具体类型信息
  • hash_type.hash 的副本,用于目标类型和接口变量的类型对比判断。
  • fun:底层数组,存储接口的方法集的具体实现的地址,其包含一组函数指针,实现了接口方法的动态分派,且每次在接口发生变更时都会更新。

对应 func 属性会在后面的章节进一步展开讲解,便于大家对于接口中的函数指针管理的使用和理解,在此可以先行思考长度为 1 的 uintptr 数组是如何做到存储多方法的?

接下来我们进一步展开 interfacetype 结构体。源码如下:

type nameOff int32
type typeOff int32type imethod struct {name nameOffityp typeOff
}type interfacetype struct {typ     _typepkgpath namemhdr    []imethod
}

  • _type:接口的具体类型信息。
  • pkgpath:接口的包(package)名信息。
  • mhdr:接口所定义的函数列表。

而相对应 interfacetype,还有各种类型的 type。例如:maptypearraytypechantypeslicetype 等,都是针对具体的类型做的具体类型定义:

type arraytype struct {typ   _typeelem  *_typeslice *_typelen   uintptr
}type chantype struct {typ  _typeelem *_typedir  uintptr
}
...

若有兴趣自行翻看 runtime 里相应源码即可,都是一些基本数据结构信息的存储和配套方法,就不在此一一展开讲解了。

小结

总结来讲,接口的数据结构基本表示形式比较简单,就是类型和值描述。再根据其具体的区别,例如是否包含方法集,具体的接口类型等进行组合使用。

值接收者和指针接收者

在接口的具体应用使用场景中,有一个是大家常常会碰到,甚至会对其产生较大纠结心里的东西。那就是到底用值接收者,又或是用指针接收者来声明。

演示说明

演示代码如下:

type Human interface {Say(s string) errorEat(s string) error
}type TestA struct{}func (t TestA) Say(s string) error {fmt.Printf("说煎鱼:%s\n", s)return nil
}func (t *TestA) Eat(s string) error {fmt.Printf("吃煎鱼:%s\n", s)return nil
}func main() {var h Human = &TestA{}_ = h.Say("催更")_ = h.Eat("真香")
}

Human 接口中,其包含 SayEat 方法,并且在 TestA 结构体中我们进行了针对性的实现。

具体的区别就是:

  • Say 方法中是值接收对象,如:(t TestA)
  • Eat 方法中是指针接收对象,如:(t *TestA)

最终的输出结果:

说煎鱼:催更
吃煎鱼:真香

值和指针

如果我们将演示代码的主函数 main 改成下述这样:

func main() {var h Human = TestA{}_ = h.Say("催更")_ = h.Eat("真香")
}

你觉得这段代码还能正常运行吗?在编译时会出现如下报错信息:

# command-line-arguments
./main.go:23:6: cannot use TestA literal (type TestA) as type Human in assignment:TestA does not implement Human (Eat method has pointer receiver)

显然是不能的。因为接口校验不对,编译器过不了。其根本原因在于 Eat 是指针接收者。而当声明改为 TestA{} 后,其就会变成值对象,所以不匹配。

这时候又会出现新的问题,为什么在上面代码声明为 &TestA{} 时,那肯定是指针引用了,那为什么 Say 方法又能正常运行,不会报错呢?

其实 TestA{} 实现了 Say 方法,那么 &TestA{} 也能自动拥有该方法。显然,这是 Go 语言自身在背后做了一些事情。

因此如果我们实现了一个值对象的接收者时,也会相应拥有了一个指针接收者。两者并不会互相影响,因为值对象会产生值拷贝,对象会独立开来。

而指针对象的接收者不行,因为指针引用的对象,在应用上是期望能够直接对源接收者的值进行修改,若又支持值接收者,显然是不符合其语义的。

两者怎么用

既然支持值接收,又支持指针接收。那平时在工程应用开发中,到底用谁?还是说随便用?

其实问题的答案,在前面就有提到。本质上还是要看你业务逻辑所期望修改的是什么?还是说程序很严谨,每次都重新 new 一个,是值又或是指针引用对于程序逻辑的结果都没有任何的影响。

总结一下,如果你想使用指针接收者,可以想想是否有以下诉求:

  • 期望接收者直接修改能够直接修改源值。
  • 期望在大结构体的情况下,性能更好,可以在理论上避免每次值拷贝,但也会有增加别的开销,需要具体情况具体权衡。

但若应用场景没什么区别,只是个人习惯问题就不用过于纠结了,适度统一也是很重要的一环。

类型断言

在 Go 语言中使用接口,必搭配一个 “技能”。那就是进行类型断言(type assertion):

var i interface{} = "吃煎鱼"// 进行变量断言,若不判断容易出现 panic
s := i.(string)// 进行安全断言
s, ok := i.(string)

switch case 中,还有另外一种写法:

var i interface{} = "炸煎鱼"// 进行 switch 断言
switch i.(type) {
case string:// do something...
case int:// do something...
case float64:// do something...
}

采取的是 (变量).(type) 的调用方式,再给予 case 不同的类型进行判断识别。在 Go 语言的背后,类型断言其实是在编译器翻译后,根据 ifaceeface 分别对应了下述方法:

func assertI2I2(inter *interfacetype, i iface) (r iface, b bool) {tab := i.tabif tab == nil {return}if tab.inter != inter {tab = getitab(inter, tab._type, true)if tab == nil {return}}r.tab = tabr.data = i.datab = truereturn
}
func assertI2I(inter *interfacetype, i iface) (r iface)func assertE2I2(inter *interfacetype, e eface) (r iface, b bool)
func assertE2I(inter *interfacetype, e eface) (r iface)

主要是根据接口的类型信息进行一轮判断和识别,基本就完成了。主要核心在于 getitab 方法,会在后面进行统一介绍和说明。

类型转换

演示代码如下:

func main() {x := "煎鱼"var v interface{} = xfmt.Println(v)
}

查看汇编代码:

0x0021 00033 (main.go:9) LEAQ go.string."煎鱼"(SB), AX0x0028 00040 (main.go:9) MOVQ AX, (SP)0x002c 00044 (main.go:9) MOVQ $6, 8(SP)0x0035 00053 (main.go:9) PCDATA $1, $00x0035 00053 (main.go:9) CALL runtime.convTstring(SB)0x003a 00058 (main.go:9) MOVQ 16(SP), AX0x003f 00063 (main.go:10) XORPS X0, X0

主要对应了 runtime.convTstring 方法。同时很显然其是根据类型来区分来方法:

func convTstring(val string) (x unsafe.Pointer) {if val == "" {x = unsafe.Pointer(&zeroVal[0])} else {x = mallocgc(unsafe.Sizeof(val), stringType, true)*(*string)(x) = val}return
}func convT16(val uint16) (x unsafe.Pointer)
func convT32(val uint32) (x unsafe.Pointer)
func convT64(val uint64) (x unsafe.Pointer)
func convTstring(val string) (x unsafe.Pointer) 
func convTslice(val []byte) (x unsafe.Pointer)
func convT2Enoptr(t *_type, elem unsafe.Pointer) (e eface)
func convT2I(tab *itab, elem unsafe.Pointer) (i iface)
...

动态分派

前面有提到接口中的 fun [1]uintptr 属性会可以存储接口的方法集,但不知道为什么。

接下来我们将进行具体的分析,演示代码:

type Human interface {Say(s string) errorEat(s string) errorWalk(s string) error
}type TestA stringfunc (t TestA) Say(s string) error {fmt.Printf("煎鱼:%s\n", s)return nil
}
func (t TestA) Eat(s string) error {fmt.Printf("煎鱼:%s\n", s)return nil
}func (t TestA) Walk(s string) error {fmt.Printf("煎鱼:%s\n", s)return nil
}func main() {var h Humanvar t TestAh = t_ = h.Eat("烤羊排")_ = h.Say("炸鸡翅")_ = h.Walk("去炸鸡翅")
}

存储方式

执行 go build -gcflags '-l' -o awesomeProject . 编译后,再次执行 go tool objdump -s "main" awesomeProject

查看具体的汇编代码:

LEAQ go.itab.main.TestA,main.Human(SB), AX TESTB AL, 0(AX)     MOVQ 0x10(SP), AX    MOVQ AX, 0x28(SP)    MOVQ go.itab.main.TestA,main.Human+32(SB), CX MOVQ AX, 0(SP)     LEAQ go.string.*+3048(SB), DX   MOVQ DX, 0x8(SP)    MOVQ $0x9, 0x10(SP)    CALL CX      MOVQ go.itab.main.TestA,main.Human+24(SB), AX MOVQ 0x28(SP), CX    MOVQ CX, 0(SP)     LEAQ go.string.*+3057(SB), DX   MOVQ DX, 0x8(SP)    MOVQ $0x9, 0x10(SP)    CALL AX      MOVQ go.itab.main.TestA,main.Human+40(SB), AX MOVQ 0x28(SP), CX    MOVQ CX, 0(SP)     LEAQ go.string.*+4973(SB), CX   MOVQ CX, 0x8(SP)    MOVQ $0xc, 0x10(SP)    CALL AX

结合来看,虽然 fun 属性的类型是 [1]uintptr,只有一个元素,但其实就是存放了接口方法集的首个方法的地址信息,接着根据顺序往后计算并获取就好了。也就是说其是存在一定规律的。在存入方法时就决定了,所以获取也能明确。

我们进一步展开,看看 itab hash table 是如何获取和新增的。

获取 itab 元素

getitab 方法的主要作用是获取 itab 元素,若不存在则新增。源码如下:

func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {// 省略一些边界、异常处理var m *itabt := (*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}m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))m.inter = interm._type = typm.hash = 0m.init()itabAdd(m)unlock(&itabLock)
finish:if m.fun[0] != 0 {return m}panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
}

  • 调用 atomic.Loadp 方法加载并查找现有的 itab hash table,看看是否是否可以找到所需的 itab 元素。
  • 若没有找到,则调用 lock 方法对 itabLock 上锁,并进行重试(再一次查找)。
    • 若找到,则跳到 finish 标识的收尾步骤。
    • 若没有找到,则新生成一个 itab 元素,并调用 itabAdd 方法新增到全局的 hash table 中。
  • 返回 fun 属性的首位地址,继续后续业务逻辑。

新增 itab 元素

itabAdd 方法的主要作用是将所生成好的 itab 元素新增到 itab hash table 中。源码如下:

func itabAdd(m *itab) {// 省略一些边界、异常处理t := itabTableif t.count >= 3*(t.size/4) { // 75% load factort2 := (*itabTableType)(mallocgc((2+2*t.size)*sys.PtrSize, nil, true))t2.size = t.size * 2iterate_itabs(t2.add)if t2.count != t.count {throw("mismatched count during itab table copy")}atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))t = itabTable}t.add(m)
}

  • 检查 itab hash table 的容量情况,查看容量情况是否已经满足大于或等于 75%。
  • 若满足扩容策略,则调用 mallocgc 方法申请内存,按既有 size 大小扩容双倍容量。
  • 若不满足扩容策略,则直接新增 itab 元素到 hash table 中。

深度理解

在本文中,我们先介绍了 Go 语言接口的 runtime.efaceruntime.iface 两个基本数据结构,其代表了一切的开端。

随后针对值接受者和指针接收者进行了详细的说明,同时日常用的较多的类型断言和转换也一一进行了描述。

最后对接口的多方法这个神秘的地方进行了基本分析和了解,相信这一番轮流吸收下来,能够打开大家对接口的一个新的理解。

面试题示例:

  • 问题:接口的零值是什么?未初始化的接口如何工作?
  • 回答:接口的零值是 nil。如果接口没有赋值或赋值为 nil,它将不指向任何类型的实例。尝试调用其方法时会导致运行时错误。

三、接口的面试题解析

1. 接口与多态

Go 中的接口是实现多态的关键。当一个类型实现了某个接口后,可以通过该接口来调用类型的方法,达到不依赖于具体类型的效果。多个不同的类型如果实现了同样的接口,就能以相同的方式进行处理。

面试题示例:

  • 问题:如何在 Go 中实现多态?
  • 回答:Go 通过接口实现多态。不同类型实现了相同的接口后,能够通过该接口调用相同的方法,而不依赖于具体类型。
2. 空接口和类型断言

空接口在 Go 中非常常见,尤其在处理通用数据时。在实际应用中,常常需要使用类型断言来从空接口中提取具体类型。

面试题示例:

  • 问题:什么场景下使用空接口?如何从空接口中提取具体类型?
  • 回答:空接口 interface{} 用于存储任意类型的数据,常用于通用数据容器或函数参数。通过类型断言可以从空接口中提取具体类型,使用 i.(T) 来执行类型断言。
3. 接口的设计原则

在 Go 中设计接口时,应该遵循一些原则:尽量小巧且具体的接口,避免使用大而全的接口。此外,接口应该只暴露类型的行为而非状态,尽量避免通过接口传递大量的内部数据。

面试题示例:

  • 问题:在设计接口时,应该遵循哪些原则?
  • 回答:设计接口时应遵循:1) 接口应该尽量小,暴露的仅为必要的行为;2) 尽量避免使用空接口或非常大的接口;3) 接口方法应该是类型的行为而非状态。

四、总结

Go 语言的接口是一个强大而灵活的特性,能够有效地实现多态和抽象。通过掌握接口的基本使用方法、空接口的应用、类型断言和接口的设计原则,你不仅能够提高代码的可扩展性和可维护性,还能够应对面试中关于接口的常见问题。通过深入理解接口,你将能够编写更加灵活和高效的 Go 代码,在面试中获得加分。

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

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

相关文章

ansible循环+判断(with,loop,when,if,for)

一、文档核心定位 本文档聚焦Ansible自动化运维中的两大核心功能——循环与判断,通过“功能说明完整Playbook代码”的形式,覆盖循环迭代场景(列表、字典、文件等)、数据处理过滤器(字符串、数字、加密等)、…

在linux下使用MySQL常用的命令集合

1. 数据库查看和选择-- 查看所有数据库 SHOW DATABASES;-- 选择使用某个数据库(需要修改:your_database_name) USE your_database_name;-- 查看当前正在使用的数据库 SELECT DATABASE();说明:your_database_name 替换为你要操作的…

mysy2使用

参考链接 https://blog.csdn.net/qq_36525177/article/details/115279468 介绍 要把linux程序在windows上编译,且最好兼容posix标准,就用msys2。 使用 1、先下载安装,我装在D:\mysy2 2、打开vscode,不要切换目录,…

【Protues仿真】基于AT89C52单片机的温湿度测量

目录 0案例视频效果展示 1DHT11温度湿度传感器 1.1传感器简介 1.2引脚定义(从左到右,面对网格面) 1.3时序 & 校验(原理速览) 1.4常见故障排查 2 DHT11温度湿度传感器数据 2.1 DHT11温度湿度传感器数据格式…

JavaScript箭头函数与普通函数:两种工作方式的深度解析

文章目录JavaScript箭头函数与普通函数:两种"工作方式"的深度解析 🏹🆚👨💼引言:为什么需要箭头函数?核心区别全景图对比表格:箭头函数 vs 普通函数关系示意图一、this绑定…

蓝光三维扫描技术赋能内衣胸垫设计:从精准制造到个性化体验的革新之旅

在竞争激烈的内衣市场中,产品设计的精准性、舒适度和个性化已成为品牌制胜的关键。传统内衣设计依赖主观经验与样品反复调整,不仅周期长、成本高,且难以实现对复杂胸型的精准适配。为应对这一挑战,某知名内衣品牌采用新拓三维XTOM…

内存保护单元MPU

一、介绍内存保护单元 是一种硬件模块,通常集成在处理器内核中,用于管理和管理对内存的访问,以提高系统的可靠性和安全性。它的核心任务是保护。想象一下,一个操作系统中有多个任务在运行:* 任务A的代码 bug 可能会错误…

【Kubernetes知识点】监控升级,备份及Kustomize管理

目录 1.举例说明K8s中都有哪些常规的维护管理操作。 2.如何升级K8s到新的版本?在升级过程中应该注意哪些事项? 3.解释ETCD及其备份和恢复的过程。 4.Kustomization在Kubernetes中的作用 1.举例说明K8s中都有哪些常规的维护管理操作。 集群状态监控…

《Effective Java》第4条:通过私有构造器强化不可实例化的能力

说明: 关于本博客使用的书籍,源代码Gitee仓库 和 其他的相关问题,请查看本专栏置顶文章:《Effective Java》第0条:写在前面,用一年时间来深度解读《Effective Java》这本书 正文: 原文P15&am…

20.Linux进程信号(一)

信号: 产生->保存->处理一、预备知识信号vs信号量->没有任何关系什么叫做信号?中断我们正在做的事情,是一种事件的异步通知机制。同步和异步理解:同步指事件发生具有一定的顺序性(如命名管道中服务端读方式打开会阻塞&am…

【C++】Vector核心实现:类设计到迭代器陷阱

vector 模拟实现代码的核心下面从类设计、核心接口、内存安全、常见陷阱、测试场景5 个维度,提炼需重点掌握的知识点,覆盖面试高频考点与实践易错点:一、类结构与成员变量(基础框架)vector 的核心是通过三个迭代器&…

并发编程指南 内存模型

文章目录5.1 内存模型5.1.1 对象和内存位置5.1.2 对象、内存位置和并发5.1.3 修改顺序5.1 内存模型 内存模型:一方面是内存布局,另一方面是并发。并发的基本结构很重要,特别是低层原子操作。因为C所有的对象都和内存位置有关,所以…

血缘元数据采集开放标准:OpenLineage Integrations Compatibility Tests Structure

OpenLineage 是一个用于元数据和血缘采集的开放标准,专为在作业运行时动态采集数据而设计。它通过统一的命名策略定义了由作业(Job)、运行实例(Run)和数据集(Dataset) 组成的通用模型&#xff0…

执行一条select语句期间发生了什么?

首先是连接器的工作,嗯,与客户端进行TCP三次握手建立连接,校验客户端的用户名和密码,如果用户名和密码都对了,那么就会检查该用户的权限,之后执行的所有SQL语句都是基于该权限接着客户端就可以向数据库发送…

element el-select 默认选中数组的第一个对象

背景&#xff1a;在使用element组件的时候&#xff0c;我们期望默认选中第一个数值。这里我们默认下拉列表绑定的lable是中文文字&#xff0c;value绑定的是数值。效果展示&#xff1a;核心代码&#xff1a;<template><el-select v-model"selectValue" plac…

【论文阅读】LightThinker: Thinking Step-by-Step Compression (EMNLP 2025)

论文题目&#xff1a;LightThinker: Thinking Step-by-Step Compression 论文来源&#xff1a;EMNLP 2025&#xff0c;CCF B 论文作者&#xff1a; 论文链接&#xff1a;https://arxiv.org/abs/2502.15589 论文源码&#xff1a;https://github.com/zjunlp/LightThinker 一、…

ABAQUS多尺度纤维增强混凝土二维建模

本案例是通过ABAQUS对论文Study on the tensile and compressive mechanical properties of multi-scale fiber-reinforced concrete: Laboratory test and mesoscopic numerical simulation&#xff08;https://doi.org/10.1016/j.jobe.2024.108852&#xff09;中纤维增强混凝…

C++ ---- 模板的半特化与函数模板的偏特化

在 C 中&#xff0c;模板提供了一种强大的泛型编程方式&#xff0c;使得我们能够编写类型无关的代码。然而&#xff0c;在实际使用中&#xff0c;有时我们需要根据具体的类型或类型组合对模板进行定制&#xff0c;这时就需要用到模板的特化。本文将介绍半模板特化和函数模板的偏…

为何 React JSX 循环需要使用 key

key 是 React 用于识别列表中哪些子元素被改变、添加或删除的唯一标识符 它帮助 React 更高效、更准确地更新和重新渲染列表 1、核心原因&#xff1a;Diff算法与性能优化 React 的核心思想之一是通过虚拟 DOM (Virtual DOM) 来减少对真实 DOM 的直接操作&#xff0c;从而提升性…

Jetson AGX Orin平台R36.3.0版本1080P25fps MIPI相机图像采集行缺失调试记录

1.前言 主板:AGX Orin 官方开发套件 开发版本: R36.3.0版本 相机参数如下: 相机硬件接口: 2. 梳理大致开发流程 核对线序/定制相机转接板 编写camera driver驱动 编写camera dts配置文件 调camera参数/测试出图 前期基本流程就不多介绍了直接讲正题 3. 问题描述 …