Go属于那种极简的语言[1],从诞生到现在语言自身特性变化很小,不会像其他主流语言那样走“你有的我也要有”的特性融合路线。因此新语言特性对于Gopher来说属于“稀缺品”,属于“供不应求”那类事物^_^。这也直接导致了每次Go新版本发布,我们都要首先看看语言特性是否有变更,每个新加入语言的特性都值得我们去投入更多关注,去深入研究。下面我们就来深入Go 1.17版本中语言规范的一些变化!

1. 支持将切片转换为数组指针

在Go 1.17版本之前,我们可以将数组转换为切片,数组将成为转换后的切片底层存储数组,因此,通过切片可以直接改变数组中的元素,就像下面代码这样:

// github.com/bigwhite/experiments/tree/master/go1.17-examples/lang/slice2arrayptr/main.go
func array2slice() {var a = [5]int{11, 12, 13, 14, 15}var b = a[0:len(a)] // or var b = a[:]b[1] += 10fmt.Printf("%v\n", b) // [11 22 13 14 15]
}

但反过来则不行,Go不支持将切片再转换回数组类型,编译器会报下面错误信息:

// github.com/bigwhite/experiments/tree/master/go1.17-examples/lang/slice2arrayptr/main.go
func slice2array() {var b = []int{11, 12, 13}var a = [3]int(b) // cannot convert b (type []int) to type [3]intfmt.Printf("%v\n", a)
}

那么在Go中我们就没法将切片转换为数组了么?也不是绝对的。我们可以通过unsafe包以hack的方式实现这样的转换,如下面代码所示:

// github.com/bigwhite/experiments/tree/master/go1.17-examples/lang/slice2arrayptr/main.go
func slice2arrayWithHack() {var b = []int{11, 12, 13}var a = *(*[3]int)(unsafe.Pointer(&b[0]))a[1] += 10fmt.Printf("%v\n", b) // [11 12 13]
}

上面代码中,我们实际上得到是切片底层数组的一份拷贝,修改该拷贝中的元素值,切片中的元素将不会受到影响。如果想通过数组修改切片中元素,我们还得通过获取数组指针的方式,如下面代码所示。

// github.com/bigwhite/experiments/tree/master/go1.17-examples/lang/slice2arrayptr/main.go
func slice2arrayptrWithHack() {var b = []int{11, 12, 13}var p = (*[3]int)(unsafe.Pointer(&b[0]))p[1] += 10fmt.Printf("%v\n", b) // [11 22 13]
}

但是使用unsafe,一如其名,其安全性没有编译器和runtime层的保证,只能由开发者自己保证,Gopher在通常情况下应该避免使用。

于是在2009年末,也就是Go语言宣布开源[2]后不久(那时Go 1.0版本尚未发布),Roger Peppe[3]便提出一个issue(那时go的开发还没有如今这么规范,没有proposal流程):“spec: use (*[4]int)(x) to convert slice x into array pointer”[4]。最初该issue的提出仅仅是因为语法层面缺失了从切片到数组的转换语法,同时希望这种转换以及转换后的数组使用时的下标边界能得到编译器和runtime的协助检查。这个issue得到了当时Go核心开发组成员的支持,Russ Cox还提出将Roger Peppe提议的语法形式做如下变动:

从
b := a.[0:4]变为 b := (*[4]int)(a[0:4])

但不知何故,该issue始终没有被纳入Go主干中,直到Go 1.17版本,该issue又被重新提出来了。Go 1.17直接**支持将切片转换为数组指针[5]**,我们可以在Go 1.17中编写和运行如下面这样的代码,而无需再借助unsafe的hack:

// github.com/bigwhite/experiments/tree/master/go1.17-examples/lang/slice2arrayptr/main.go
func slice2arrayptr() {var b = []int{11, 12, 13}var p = (*[3]int)(b)p[1] = p[1] + 10fmt.Printf("%v\n", b) // [11 22 13]
}

Go通过运行时对这类切片到数组指针的转换代码做检查,如果发现越界行为,就会通过运行时panic予以处理。Go运行时实施检查的一条原则就是“转换后的数组长度不能大于原切片的长度”,注意这里是切片的长度(len),而不是切片的容量(cap),于是下面的转换有些合法,有些非法:

// github.com/bigwhite/experiments/tree/master/go1.17-examples/lang/slice2arrayptr/main.govar b = []int{11, 12, 13}
var p = (*[4]int)(b) // cannot convert slice with length 3 to pointer to array with length 4
var p = (*[0]int)(b) // ok,*p = []
var p = (*[1]int)(b) // ok,*p = [11]
var p = (*[2]int)(b) // ok,*p = [11, 12]
var p = (*[3]int)(b) // ok,*p = [11, 12, 13]
var p = (*[3]int)(b[:1]) // cannot convert slice with length 1 to pointer to array with length 3

关于这个语言特性的应用场合,目前还待Go社区挖掘,不过已经有人提出提出利用该特性优化go编译器的可行性评估[6]了。

2. unsafe包新增了两个“语法糖”函数

Go 1.17中增加了两个“语法糖”函数:Add[7]和Slice[8]。这两个函数原型如下:

// $GOROOT/src/unsafe.go
func Add(ptr Pointer, len IntegerType) Pointe
func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType

之所以这两个函数能进入unsafe包,和其他已经存在于unsafe包中的函数的目的是一样的,那就是将Go开发人员一些经常使用的“代码片段模式”升级为unsafe包内置的函数,这样不仅可以降低开发人员误用的比例,还可以让Go runtime提供一些检查,增加类型安全性。

unsafe.Add函数

由于go原生不允许指针加减操作,因此我们在特定场景下不得不使用unsafe包来做指针加减,比如下面代码:

// github.com/bigwhite/experiments/tree/master/go1.17-examples/lang/unsafe/add/main.go
const intLen = unsafe.Sizeof(int(8))func foo() {var a = [5]int{11, 12, 13, 14, 15}for i := 0; i < 5; i++ {p := (*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&a[0])) + uintptr(uintptr(i)*intLen)))*p = *p + 10}fmt.Println(a)// [21 22 23 24 25]
}

上面代码中间变量p声明同时赋值那行是在Go 1.17之前unsafe包最常见的一种用法和代码模式。大家都这么用,但用起来还那么繁琐,于是便有了unsafe.Add。如果用unsafe.Add改造上面代码,便能简略一些,如下面代码所示:

// github.com/bigwhite/experiments/tree/master/go1.17-examples/lang/unsafe/add/main.go
const intLen = unsafe.Sizeof(int(8))func bar() {var a = [5]int{11, 12, 13, 14, 15}for i := 0; i < 5; i++ {p := (*int)(unsafe.Add(unsafe.Pointer(&a[0]), uintptr(i)*intLen))*p = *p + 10}fmt.Println(a)
}

本质上unsafe.Add(ptr, len) 就等价于unsafe.Pointer(uintptr(ptr) + uintptr(len))。在之前版本中,runtime的stubs.go中也有个类似的实现:

$GOROOT/src/runtime/stubs.go// Should be a built-in for unsafe.Pointer?//go:nosplitfunc add(p unsafe.Pointer, x uintptr) unsafe.Pointer {return unsafe.Pointer(uintptr(p) + x)}

Go 1.17有了这个Add函数后,建议大家就多多使用该函数,而尽量不要自己去拼那个“大长串”了。

unsafe.Slice函数

unsafe.Slice函数支持基于一个数组创建一个切片,该数组将作为切片的底层存储,它也可以理解为等价于下面常用“代码片段”语法糖函数:

func Slice(ptr *ArbitraryType, len IntegerType) []ArbitraryType<=>(*[len]ArbitraryType)(unsafe.Pointer(ptr))[:]

下面是unsafe.Slice的一个应用例子:

// github.com/bigwhite/experiments/tree/master/go1.17-examples/lang/unsafe/slice/main.go
func main() {var a = [5]int{11, 12, 13, 14, 15}s1 := a[:]s2 := unsafe.Slice(&a[0], 5)fmt.Println(s1) // [11 12 13 14 15]fmt.Println(s2) // [11 12 13 14 15]fmt.Printf("the type of s2 is %T\n", s2)s2[2] += 10fmt.Println(a)  // [11 12 23 14 15]fmt.Println(s1) // [11 12 23 14 15]fmt.Println(s2) // [11 12 23 14 15]
}

我们看到基于unsafe.Slice与基于数组进行切片得到的两个切片一样的,它们的底层数组都是数组a。因此,无论通过修改哪个切片元素,都会反映到另外一个切片中并反映到底层数组上。

3. 小结

在本文中,我们了解到了Go 1.17新增的很少的语言特性,这些个性更多从语言的易用性、安全性等方面考虑才添加的,相较于以往版本,这些新增特性算是不少了。如果要期待语言特性的巨大变更,那还是一起等Go 1.18吧。Go 1.18保证让你爽歪歪。泛型(类型参数)的加入必然让go代码变得比以前更烧脑一些。

本文涉及代码可以在这里[9]下载:https://github.com/bigwhite/experiments/tree/master/go1.17-examples/lang


“Gopher部落”知识星球正式转正(从试运营星球变成了正式星球)!“gopher部落”旨在打造一个精品Go学习和进阶社群!高品质首发Go技术文章,“三天”首发阅读权,每年两期Go语言发展现状分析,每天提前1小时阅读到新鲜的Gopher日报,网课、技术专栏、图书内容前瞻,六小时内必答保证等满足你关于Go语言生态的所有需求!部落目前虽小,但持续力很强。在2021年上半年,部落将策划两个专题系列分享,并且是部落独享哦:

  • Go技术书籍的书摘和读书体会系列

  • Go与eBPF系列

Gopher Daily(Gopher每日新闻)归档仓库 - https://github.com/bigwhite/gopherdaily

我的联系方式:

  • 微博:https://weibo.com/bigwhite20xx

  • 微信公众号:iamtonybai

  • 博客:tonybai.com

  • github: https://github.com/bigwhite

  • “Gopher部落”知识星球:https://public.zsxq.com/groups/51284458844544

商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。

参考资料

[1] Go属于那种极简的语言: https://www.imooc.com/read/87/article/2321

[2] Go语言宣布开源: https://www.imooc.com/read/87/article/2320

[3] Roger Peppe: https://github.com/rogpeppe

[4] “spec: use (*[4]int)(x) to convert slice x into array pointer”: https://github.com/golang/go/issues/395

[5] 支持将切片转换为数组指针: https://tip.golang.org/ref/spec#Conversions_from_slice_to_array_pointer

[6] 利用该特性优化go编译器的可行性评估: https://github.com/golang/go/issues/46529

[7] Add: https://github.com/golang/go/issues/40481

[8] Slice: https://github.com/golang/go/issues/19367

[9] 这里: https://github.com/bigwhite/experiments/tree/master/go1.17-examples/lang

[10] 改善Go语⾔编程质量的50个有效实践: https://www.imooc.com/read/87

[11] Kubernetes实战:高可用集群搭建、配置、运维与应用: https://coding.imooc.com/class/284.html

[12] 链接地址: https://m.do.co/c/bff6eed92687

Go 1.17新特性详解:支持将切片转换为数组指针相关推荐

  1. 还在用JDK6的同学,来看看JDK13新特性详解吧

    点击上方"搜云库技术团队"关注,选择"设为星标" 回复"面试题"或"1024"获取 4T 学习资料 在 JDK 版本的世 ...

  2. laya龙骨换装_DragonBones 5.3 新特性详解

    本帖最后由 superlancelot 于 2017-7-12 13:40 编辑 DragonBones 5.3新特性详解 本次DragonBones5.3 相对上一个版本5.2提供了很多新增功能和用 ...

  3. java11 新特性 详解

    为什么80%的码农都做不了架构师?>>>    引言: 点击-->java10 新特性 详解 点击-->java9 新特性 详解 点击-->java8 新特性 详解 ...

  4. Java9 新特性 详解

    目录 Java9 新特性 详解 1.Java9新特性之---目录结构 2.Java9新特性之---JShell工具 3.Java9新特性之---模块化 4.Java9新特性之---多版本兼容Jar包 ...

  5. oracle dataguard详解,Oracle 19c 新特性详解:DataGuard 中ADG的自动DML重定向

    Oracle 19c 新特性详解:DataGuard 中ADG的自动DML重定向 在前面的文章<Oracle 19c 十大新特性一览>中,我们曾经提到 Oracle 19c的一个重要增强, ...

  6. Java EE 8的五大新特性详解

    Java EE 8的五大新特性详解 2018.4.3 版权声明:本文为博主chszs的原创文章,未经博主允许不得转载. Java EE 8带来了很多新特性,其中最好的新特性有下面五个. 备受期待的Ja ...

  7. CSS3新特性详解(三):CSS3 2D转换和3D转换 transform 变形使用详解

      关于CSS3新特性,在上篇博文中"CSS3新特性详解(二):CSS3 字体@font-face详解.如何创建和修改woff字体文件及text-shadow等文本效果",讨论了C ...

  8. Android 4.1-Jelly Bean新特性详解

    Android 4.1Jelly Bean新特性详解 发布会已经结束,Android新一代的4.1版本,代号Jelly Bean(果冻豆)的新系统已经正式问世,除了新架构.全新通知栏和搜索功能之外,实 ...

  9. python3.9性能_Python3.9新特性详解

    本文主要介绍Python3.9的一些新特性,如:更快速的进程释放,性能的提升,简便的新字符串函数,字典并集运算符以及更兼容稳定的内部API,详细如下: 字典并集和可迭代更新 字符串方法 类型提示 新的 ...

最新文章

  1. sizeof 和strlen的区别
  2. 5、修改视图(ALTER VIEW)
  3. 【快乐水题】1716. 计算力扣银行的钱
  4. ldap配置系列二:jenkins集成ldap
  5. Visual Basic团队透露将为VB添加迭代器
  6. 靠手速!华为新旗舰今晚发布:价格破万
  7. python画中国的轮廓_利用python绘制中国地图(含省界、河流等)
  8. 固体核磁共振技术简介
  9. Spark学习笔记1
  10. 投屏索尼电视显示访问服务器,索尼电视投屏如何设置 苹果手机投屏索尼电视...
  11. mysql修改frm,MySQL 修改.frm文件来更新字段
  12. Navicat 查看密码 破解保存的密码 + 密码解密
  13. (3) 二分频VHDL描述
  14. ThingsBoard 使用
  15. 黄聪:【转】C# 对称加密解密算法
  16. TCP中的粘包、拆包问题产生原因及解决方法
  17. 2022基于微信小程序的图书馆座位预约管理系统.rar(论文+程序设计源码+数据库)毕业设计
  18. 获取安卓设备的有线网卡的MAC地址(安卓TV、安卓系统的广告机等)
  19. 写一些给程序员世界的话(不是什么所谓的正能量,但是的确是发自内心的实话)
  20. 机器人工厂参观心得_机器人工厂的建立

热门文章

  1. cad抛物线曲线lisp_如何用CAD画正弦曲线????有lisp的最好。。。谢谢了
  2. static作用(修饰函数、局部变量、全局变量)
  3. 电脑固态硬盘分区后安装系统却无法进入系统原因记录
  4. Postman之接口测试
  5. Linux下WPS的安装、卸载以及相关问题总结
  6. Docker运维常用命令
  7. 【文献阅读12】:稀疏ReRAM引擎:基于ReRAM的高效稀疏神经网络加速架构
  8. 【华为Mate10】缓解手机自带存储空间压力可以转到SD的内容
  9. Overload和Override的区别。Overloaded的方法是否可以改变返回值的类型?
  10. 绿平衡(Green Balance)