<p>Go接口变量赋值时只拷贝类型指针和数据指针:值类型拷贝副本,指针/引用类型拷贝地址;不支持隐式指针转换,T与T是独立类型;大struct赋接口触发堆分配,传 T更高效;接口比较要求动态类型一致且底层值可比较,否则panic。</p>

接口变量赋值时,到底拷贝了什么
Go 接口变量本身只存两个字:一个指向类型信息的指针,一个指向数据的指针。它不拷贝底层值,除非你传的是值类型且没取地址——这时候会拷贝一份副本给接口的 data 字段。
常见错误现象:func modify(s string) { s = "new" } 传进接口后改不动原值;但 func modify(p *string) { *p = "new" } 就能改,因为接口里存的是指针地址。
- 值类型(如
int、string、struct{})赋值给接口 → 拷贝值本身 - 指针类型(如
*int、*MyStruct)赋值给接口 → 拷贝指针(即地址),不拷贝目标对象 - 切片、map、channel、func 类型本身已含指针语义,赋值给接口时只拷贝其 header 结构(含指针字段),不是深拷贝
为什么 *T 能赋值给 interface{},但 T 不能自动转成 *T 再赋值
接口赋值是静态类型检查,Go 不做隐式指针转换。哪怕 T 实现了某个接口,*T 也实现了,这两者是完全独立的类型,互不自动转换。
使用场景:比如你写了个 type User struct{ Name string },并为 *User 实现了 fmt.Stringer,那么 fmt.Println(&u) 可以调用 String(),但 fmt.Println(u) 就不行——因为 User 没实现该接口。
立即学习“go语言免费学习笔记(深入)”;
- 错误写法:
var i fmt.Stringer = u(u是User值)→ 编译报错:User does not implement fmt.Stringer - 正确写法:
var i fmt.Stringer = &u,或提前声明func (u User) String() string - 参数传递时同理:函数接收
fmt.Stringer,传u和&u的行为完全不同,别凭感觉传
空接口 interface{} 赋值后的底层结构变化
interface{} 是最泛化的接口,但它在内存里仍是两个机器字长:一个指向 runtime._type,一个指向数据。对小对象(如 int64),数据直接塞进 data 字段;对大对象(如大 struct),data 存的是堆上地址。
性能影响:频繁把大 struct 赋给 interface{} 会触发堆分配,比传指针慢;而传 *T 则几乎零开销。
- 示例:
var i interface{} = struct{ x [1024]byte }{}→ 触发一次 malloc - 等价但更优:
s := struct{ x [1024]byte }{}; var i interface{} = &s→ 避免拷贝和分配 - 反射、
json.Marshal、fmt.Printf("%v")内部都依赖interface{},所以传参时留意值大小
接口比较时 panic 的真实原因
两个接口变量用 == 比较,只有当它们的动态类型完全一致、且底层值可比较时才合法。否则运行时报 panic: runtime error: comparing uncomparable type。
容易踩的坑:以为“接口能装一切,那比较也应该通用”,其实 Go 在运行时会检查类型是否支持 ==,比如 map、slice、func、包含不可比较字段的 struct,一旦出现在接口中,就禁止比较。
- 错误示例:
var a, b interface{} = []int{1}, []int{1}; println(a == b)→ panic - 安全做法:先用
reflect.DeepEqual,或确保接口里装的是可比较类型(int、string、struct{ int; string }等) - 注意:即使类型相同,如果其中一个是 nil 接口,另一个是非 nil 但 data 为 nil 的指针(如
*int),比较结果也可能不符合直觉










