【Golang】Golang进阶系列教程--为什么 Go 不支持 []T 转换为 []interface
文章目录
- 前言
- 官方解释
- 内存布局
- 程序运行中的内存布局
- 通用方法
前言
在 Go 中,如果 interface{} 作为函数参数的话,是可以传任意参数的,然后通过类型断言来转换。
举个例子:
package mainimport "fmt"func foo(v interface{}) {if v1, ok1 := v.(string); ok1 {fmt.Println(v1)} else if v2, ok2 := v.(int); ok2 {fmt.Println(v2)}
}func main() {foo(233)foo("666")
}
不管是传 int 还是 string,最终都能输出正确结果。
那么,既然是这样的话,我就有一个疑问了,拿出我举一反三的能力。是否可以将 []T 转换为 []interface 呢?
比如下面这段代码:
func foo([]interface{}) { /* do something */ }func main() {var a []string = []string{"hello", "world"}foo(a)
}
很遗憾,这段代码是不能编译通过的,如果想直接通过 b := []interface{}(a) 的方式来转换,还是会报错:
cannot use a (type []string) as type []interface {} in function argument
正确的转换方式需要这样写:
b := make([]interface{}, len(a), len(a))
for i := range a {b[i] = a[i]
}
本来一行代码就能搞定的事情,却非要让人写四行,是不是感觉很麻烦?那为什么 Go 不支持呢?我们接着往下看。
官方解释
这个问题在官方 Wiki 中是有回答的,我复制出来放在下面:
The first is that a variable with type []interface{} is not an interface! It is a slice whose element type happens to be interface{}. But even given this, one might say that the meaning is clear.
Well, is it? A variable with type []interface{} has a specific memory layout, known at compile time.
Each interface{} takes up two words (one word for the type of what is contained, the other word for either the contained data or a pointer to it). As a consequence, a slice with length N and with type []interface{} is backed by a chunk of data that is N2 words long.
This is different than the chunk of data backing a slice with type []MyType and the same length. Its chunk of data will be Nsizeof(MyType) words long.
The result is that you cannot quickly assign something of type []MyType to something of type []interface{}; the data behind them just look different.
大概意思就是说,主要有两方面原因:
- []interface{} 类型并不是 interface,它是一个切片,只不过碰巧它的元素是 interface;
- []interface{} 是有特殊内存布局的,跟 interface 不一样。
下面就来详细说说,是怎么个不一样。
内存布局
首先来看看 slice 在内存中是如何存储的。在源码中,它是这样定义的:
// src/runtime/slice.gotype slice struct {array unsafe.Pointerlen intcap int
}
- array 是指向底层数组的指针;
- len 是切片的长度;
- cap 是切片的容量,也就是 array 数组的大小。
举个例子,创建如下一个切片:
is := []int64{0x55, 0x22, 0xab, 0x9}
那么它的布局如下图所示:

假设程序运行在 64 位的机器上,那么每个「正方形」所占空间是 8 bytes。上图中的 ptr 所指向的底层数组占用空间就是 4 个「正方形」,也就是 32 bytes。
接下来再看看 []interface{} 在内存中是什么样的。
回答这个问题之前先看一下 interface{} 的结构,Go 中的接口类型分成两类:
- iface 表示包含方法的接口;
- eface 表示不包含方法的空接口。
源码中的定义分别如下:
type iface struct {tab *itabdata unsafe.Pointer
}
type eface struct {_type *_typedata unsafe.Pointer
}
具体细节我们不去深究,但可以明确的是,每个 interface{} 包含两个指针, 会占据两个「正方形」。第一个指针指向 itab 或者 _type;第二个指针指向实际的数据。
所以它在内存中的布局如下图所示:

因此,不能直接将 []int64 直接传给 []interface{}。
程序运行中的内存布局
接下来换一个更形象的方式,从程序实际运行过程中,看看内存的分布是怎么样的?
看下面这样一段代码:
package mainvar sum int64func addUpDirect(s []int64) {for i := 0; i < len(s); i++ {sum += s[i]}
}func addUpViaInterface(s []interface{}) {for i := 0; i < len(s); i++ {sum += s[i].(int64)}
}func main() {is := []int64{0x55, 0x22, 0xab, 0x9}addUpDirect(is)iis := make([]interface{}, len(is))for i := 0; i < len(is); i++ {iis[i] = is[i]}addUpViaInterface(iis)
}
我们使用 Delve 来进行调试,可以点击这里进行安装。
dlv debug slice-layout.go
Type 'help' for list of commands.
(dlv) break slice-layout.go:27
Breakpoint 1 set at 0x105a3fe for main.main() ./slice-layout.go:27
(dlv) c
> main.main() ./slice-layout.go:27 (hits goroutine(1):1 total:1) (PC: 0x105a3fe)22: iis := make([]interface{}, len(is))23: for i := 0; i < len(is); i++ {24: iis[i] = is[i]25: }26:
=> 27: addUpViaInterface(iis)28: }
打印 is 的地址:
(dlv) p &is
(*[]int64)(0xc00003a740)
接下来看看 slice 在内存中都包含了哪些内容:
(dlv) x -fmt hex -len 32 0xc00003a740
0xc00003a740: 0x10 0xa7 0x03 0x00 0xc0 0x00 0x00 0x00
0xc00003a748: 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a750: 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a758: 0x00 0x00 0x09 0x00 0xc0 0x00 0x00 0x00
每行有 8 个字节,也就是上文说的一个「正方形」。第一行是指向数据的地址;第二行是 4,表示切片长度;第三行也是 4,表示切片容量。
再来看看指向的数据到底是怎么存的:
(dlv) x -fmt hex -len 32 0xc00003a710
0xc00003a710: 0x55 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a718: 0x22 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a720: 0xab 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a728: 0x09 0x00 0x00 0x00 0x00 0x00 0x00 0x00
这就是一片连续的存储空间,保存着实际数据。
接下来用同样的方式,再来看看 iis 的内存布局。
(dlv) p &iis
(*[]interface {})(0xc00003a758)
(dlv) x -fmt hex -len 32 0xc00003a758
0xc00003a758: 0x00 0x00 0x09 0x00 0xc0 0x00 0x00 0x00
0xc00003a760: 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a768: 0x04 0x00 0x00 0x00 0x00 0x00 0x00 0x00
0xc00003a770: 0xd0 0xa7 0x03 0x00 0xc0 0x00 0x00 0x00
切片的布局和 is 是一样的,主要的不同是所指向的数据:
(dlv) x -fmt hex -len 64 0xc000090000
0xc000090000: 0x00 0xe4 0x05 0x01 0x00 0x00 0x00 0x00
0xc000090008: 0xa8 0xee 0x0a 0x01 0x00 0x00 0x00 0x00
0xc000090010: 0x00 0xe4 0x05 0x01 0x00 0x00 0x00 0x00
0xc000090018: 0x10 0xed 0x0a 0x01 0x00 0x00 0x00 0x00
0xc000090020: 0x00 0xe4 0x05 0x01 0x00 0x00 0x00 0x00
0xc000090028: 0x58 0xf1 0x0a 0x01 0x00 0x00 0x00 0x00
0xc000090030: 0x00 0xe4 0x05 0x01 0x00 0x00 0x00 0x00
0xc000090038: 0x48 0xec 0x0a 0x01 0x00 0x00 0x00 0x00
仔细观察上面的数据,偶数行内容都是相同的,这个是 interface{} 的 itab 地址。奇数行内容是不同的,指向实际的数据。
打印地址内容:
(dlv) x -fmt hex -len 8 0x010aeea8
0x10aeea8: 0x55 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(dlv) x -fmt hex -len 8 0x010aed10
0x10aed10: 0x22 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(dlv) x -fmt hex -len 8 0x010af158
0x10af158: 0xab 0x00 0x00 0x00 0x00 0x00 0x00 0x00
(dlv) x -fmt hex -len 8 0x010aec48
0x10aec48: 0x09 0x00 0x00 0x00 0x00 0x00 0x00 0x00
很明显,通过打印程序运行中的状态,和我们的理论分析是一致的。
通用方法
通过以上分析,我们知道了不能转换的原因,那有没有一个通用方法呢?因为我实在是不想每次多写那几行代码。
也是有的,用反射 reflect,但是缺点也很明显,效率会差一些,不建议使用。
func InterfaceSlice(slice interface{}) []interface{} {s := reflect.ValueOf(slice)if s.Kind() != reflect.Slice {panic("InterfaceSlice() given a non-slice type")}// Keep the distinction between nil and empty slice inputif s.IsNil() {return nil}ret := make([]interface{}, s.Len())for i := 0; i < s.Len(); i++ {ret[i] = s.Index(i).Interface()}return ret
}
还有其他方式吗?答案就是 Go 1.18 支持的泛型,这里就不过多介绍了,大家有兴趣的话可以继续研究。
相关文章:
【Golang】Golang进阶系列教程--为什么 Go 不支持 []T 转换为 []interface
文章目录 前言官方解释内存布局程序运行中的内存布局通用方法 前言 在 Go 中,如果 interface{} 作为函数参数的话,是可以传任意参数的,然后通过类型断言来转换。 举个例子: package mainimport "fmt"func foo(v inter…...
两数相加 II——力扣445
题目描述 法一 栈 本题旨在从后往前加,为了逆序处理所有数位,利用栈,把数字压入栈中,再依次取出相加,注意进位!进位是/10,另外需要注意栈的常用函数,push()、pop()、top()࿰…...
js获取上传视频的封面第一帧
代码如下:粘贴到这个在线编辑器里,可以测试效果。 菜鸟教程在线编辑器 <div><div style"flex: 1;border: 1px solid #999; position:relative;color: #333;background-color:#FFF2B8;"><span style"position: absolute…...
Nginx 高可用负载均衡(三种模式)
一、nginx普通集群负载均衡 1、安装keepalived (1)下载 https://www.keepalived.org/download.html(2)解压 tar -zxvf keepalived-2.0.18.tar.gz(3)使用configure命令配置安装目录与核心配置文件所在位置: ./configure --prefix/usr/local/keepalived --sysconf/e…...
Linux tail命令
在Linux中,tail命令用于查看文件的末尾内容。它可以显示文件的最后几行,默认情况下显示最后10行。 以下是一些常见的使用方式和示例: 显示文件的最后10行: tail filename将会显示名为filename的文件的最后10行内容。 显示文件…...
【屏幕适配发展介绍 Objective-C语言】
一、接下来,我们花一天时间,给大家介绍这个屏幕适配 1.那么,屏幕适配,是什么意思啊 我们说,写程序的时候,我们有时候要做 1)系统适配 2)屏幕适配 1)系统适配:是指的你写的这个代码,在iOS6、iOS7、iOS8,在不同的iOS系统下,是不是运行的效果,一致吧 这个指的是…...
linux中ls命令详解
ls 显示目录内容列表 补充说明 ls命令 就是list的缩写,用来显示目标列表,在Linux中是使用率较高的命令。ls命令的输出信息可以进行彩色加亮显示,以分区不同类型的文件。 语法 ls [选项] [文件名...][-1abcdfgiklmnopqrstuxABCDFGLNQRSUX…...
大盗阿福(记忆化搜索板子)
提供核心代码:(经典的记忆化搜索套路) int dfs(int pos){if(f[pos]!-1) return f[pos];//记忆化if(pos>n) return 0;//边界,越界int sum0;//模板int f10,f20;f1dfs(pos1);f2dfs(pos2)w[pos];summax(f1,f2);//模板f[pos]sum;//模…...
打卡力扣题目八
#左耳听风 ARST 打卡活动重启# 目录 一、问题 二、解题方法一 三、解题方法二 四、两种方法的区别 关于 ARTS 的释义 —— 每周完成一个 ARTS: ● Algorithm: 每周至少做一个 LeetCode 的算法题 ● Review: 阅读并点评至少一篇英文技术文章 ● Tips: 学习至少一…...
matlab使用教程(5)—矩阵定义和基本运算
本博客介绍如何在 MATLAB 中创建矩阵和执行基本矩阵计算。 MATLAB 环境使用矩阵来表示包含以二维网格排列的实数或复数的变量。更广泛而言,数组为向量、矩阵或更高维度的数值网格。MATLAB 中的所有数组都是矩形,在这种意义上沿任何维度的分量向量的长度…...
用HTML写一个简单的静态购物网站
实现代码: <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>购物网站</title> &l…...
如何在go中实现程序的优雅退出,go-kratos源码解析
使用kratos这个框架有近一年了,最近了解了一下kratos关于程序优雅退出的具体实现。 这部分逻辑在app.go文件中,在main中,找到app.Run方法,点进入就可以了 它包含以下几个部分: App结构体:包含应用程序的配置选项和运行时状态。 …...
Appium+python自动化(二十八)- 高级滑动(超详解)
高级溜冰的滑动 滑动操作一般是两点之间的滑动,这种滑动在这里称其为低级的溜冰滑动;就是上一节给小伙伴们分享的。然而实际使用过程中用户可能要进行一些多点连续滑动操作。如九宫格滑动操作,连续拖动图片移动等场景。那么这种高级绚丽的溜…...
github token使用方法
git remote set-url origin https://<githubtoken>github.com/<username>/<repositoryname>.git 在私有仓库的HTTPS的url上加入<githubtoken>即为token url,可以免ssh key登录...
Spring属性注解对配置项名称的自动转换
一、前言 在Spring中,我们经常需要将配置文件中的属性值注入到Java类中。Spring提供了两个主要的注解来实现这一功能:Value 和 ConfigurationProperties。其中 ConfigurationProperties支持将配置项名称与Java类中的属性名进行自动转换,包括…...
Docker 安全 Docker HTTPS请求过程与配置
Docker 容器安全注意点 尽量别做的事 尽量不用 --privileged 运行容器(授权容器root用户拥有宿主机的root权限) 尽量不用 --network host 运行容器(使用 host 网络模式共享宿主机的网络命名空间) 尽量不在容器中运行 ssh 服务 尽…...
DevOps(三)
CD(二) 1. 整体流程2. 环境准备1. jenkins安装2. 编译安装git3. docker安装4. docker-compose安装5. sonarqube安装6. harbor安装7. gitlab私服8. maven安装9. Nexus部署10. K8s部署3. 安装java及编写代码3.1 安装java3.2 安装IntelliJ IDEA3.3 安装tomcat3.4 安装maven3.5 c…...
AOP的妙用
一、改代码 自定义注解用于提示该代码已经在AOP中重构了 public interface ReviseToAop {// 用于记录修改状态String value() default ""; }使用注解(无意义,只是表名被修改) ReviseToAop("修改于:2023/7/30&quo…...
CAN转ETHERCAT网关将CAN 总线和 ETHERCAT 网络连接方法
由于好多现场会出现将CAN总线的设备接到EtherCAT网络中,由于协议的不相同,不能直接进行连接,现需一种能同时兼容CAN 总线和ETHERCAT网络的一种设备,由此捷米JM-ECT-CAN 是自主研发的一款 ETHERCAT 从站功能的通讯网关。该产品主要…...
【大数据趋势】7月30日 汇率,恒指期货的大数据趋势概率分析。
1. 数据源头之一 : 汇率变化 从程序模拟趋势来看,美元在持续弱势状态,周线上正在构建一个新的下跌趋势,而且正在反抽过程中,即将完成,如果没有外部干预,会顺势往下。从月线来看,高点逐步降低&a…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换
目录 关键点 技术实现1 技术实现2 摘要: 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式(自动驾驶、人工驾驶、远程驾驶、主动安全),并通过实时消息推送更新车…...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...
Python常用模块:time、os、shutil与flask初探
一、Flask初探 & PyCharm终端配置 目的: 快速搭建小型Web服务器以提供数据。 工具: 第三方Web框架 Flask (需 pip install flask 安装)。 安装 Flask: 建议: 使用 PyCharm 内置的 Terminal (模拟命令行) 进行安装,避免频繁切换。 PyCharm Terminal 配置建议: 打开 Py…...
STM32标准库-ADC数模转换器
文章目录 一、ADC1.1简介1. 2逐次逼近型ADC1.3ADC框图1.4ADC基本结构1.4.1 信号 “上车点”:输入模块(GPIO、温度、V_REFINT)1.4.2 信号 “调度站”:多路开关1.4.3 信号 “加工厂”:ADC 转换器(规则组 注入…...
用鸿蒙HarmonyOS5实现国际象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的国际象棋小游戏的完整实现代码,使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├── …...
