golang拾遗:内置函数len的小知识
len是很常用的内置函数,可以测量字符串、slice、array、channel以及map的长度/元素个数。
不过你真的了解len吗?也许还有一些你不知道的小知识。
我们来看一道GO101的题目,这题也被GO语言爱好者周刊转载:
package main
import "fmt"
func main() {
var x *struct {
s [][32]byte
}
fmt.Println(len(x.s[99]))
}
题目问你这段代码的运行结果,选项有编译错误、panic、32和0。
我们分析一下,别看x的声明定义一大长串,实际上就是定义了一个有个[][32]byte的结构体,然后x是这个结构体的指针。
然后我们没有初始化x,所以x是一个值为nil的指针。看到这里你也许以及有答案了,对nil指针解引用访问它的成员s,那不就是panic嘛。即使引用x的成员合法,我们的s也没有初始化,访问没有初始化的slice也会panic。
然而这么想你就错了,代码的实际运行结果是32!
为什么呢?我们看看len的帮助文档:
For some arguments, such as a string literal or a simple array expression, the result can be a constant. See the Go language specification's "Length and capacity" section for details.
这句话很重要,对于结果是数组的表达式,len可能会是一个编译期常量,而且数组类型的长度在编译期是可知的,所以熟悉c++的朋友大概会立刻想到这样的常量是不需要进行实际求值的,简单类型推导即可获得。不过口说无凭,我们看看spec里的描述:
The expression len(s) is constant if s is a string constant. The expressions len(s) and cap(s) are constants if the type of s is an array or pointer to an array and the expression s does not contain channel receives or (non-constant) function calls; in this case s is not evaluated. Otherwise, invocations of len and cap are not constant and s is evaluated.
如果表达式是字符串常量那么len(s)也是常量。如果表达式s的类型是array或者array的指针,且表达式不是channel的接收操作或是函数调用,那么len(s)是常量,且表达式s不会被求值;否则len和cap会对s进行求值,其计算结果也不是一个常量。
其实说的很清楚了,但还有三点需要说明。
第一个是视为常量的表达式里为什么不能含有chan的接收操作和函数调用?
这个答案很简单,因为这两个操作都是使用这明确希望发生“副作用”的。特别是从chan里接收数据,还会导致goroutine阻塞,而我们的常量len表达式不会进行求值,这些你期望会发生的副作用便不会产生,会引发一些隐蔽的bug。
第二个是我们注意到了函数调用前用non-constant修饰了,这是什么意思?
按字面意思,一部分函数调用其实是可以在编译期完成计算被当成常量处理的,而另一些不可以。
在进一步深入之前我们先要看看golang里哪些东西是常量/常量表达式。
- 首先是各种字面量以及对字面量的类型转换产生的值了,无需多说。
- 一部分内置函数:len、cap、imag、real、complex,它们在参数是常量的时候本身也是常量表达式。
unsafe.Sizeof,因为类型的大小也是编译期就能确定的,所以它是常量表达式也很好理解。- 所有的常量之间的运算(加减乘除位运算等,除了非常量表达式函数的调用)都是常量表达式。
从上面的描述里可以看出两点,内置函数和unsafe.Sizeof的调用我们可以看成是constant function calls,所有常量表达式除了浮点数和复数表达式都可以在编译期完成计算。而其他函数比如用户自定义函数的调用,虽然仍然有可能在编译期被求值优化,但本身不属于常量表达式。所以语言标准会加以区分。比如下面这个:
func add(x, y int) int {
return x + y
}
func main() {
fmt.Println(add(512, 513)) // 1025
}
如果我们看生成的汇编,会发现求值已经完成,不需要调用add:
MOVQ $1025, (SP)
PCDATA $1, $0
CALL runtime.convT64(SB)
MOVQ 8(SP), AX
XORPS X0, X0
MOVUPS X0, ""..autotmp_16+64(SP)
LEAQ type.int(SB), CX
MOVQ CX, ""..autotmp_16+64(SP)
MOVQ AX, ""..autotmp_16+72(SP)
NOP
MOVQ os.Stdout(SB), AX
LEAQ go.itab.*os.File,io.Writer(SB), CX
MOVQ CX, (SP)
MOVQ AX, 8(SP)
LEAQ ""..autotmp_16+64(SP), AX
MOVQ AX, 16(SP)
MOVQ $1, 24(SP)
MOVQ $1, 32(SP)
NOP
CALL fmt.Fprintln(SB)
MOVQ 80(SP), BP
ADDQ $88, SP
RET
很明显的,1025已经在编译期求值了,然而add的调用不是常量表达式,所以下面的代码会报错:
const number = add(512, 513) // error!!!
// example.go:11:7: const initializer add(512, 513) is not a constant
spec给出的实例是调用的内置函数,内置函数也只有在参数是常量的情况下被调用才算做常量表达式:
const (
c4 = len([10]float64{imag(2i)}) // imag(2i) is a constant and no function call is issued
c5 = len([10]float64{imag(z)}) // invalid: imag(z) is a (non-constant) function call
)
var z complex128
所以len的表达式里如果用了non-constant的函数调用,那么就len本身不能算是常量表达式了。
这就有了最后一个疑问,题目中的x不是常量,为什么len的结果是常量呢?
标准只说表达式里不能有chan的接收和非常量表达式的函数调用,没说其他的不可以。你也可以这么理解,表达式都有结果值,任何值除了无类型常量(这里显然不是)都是要有一个确定的类型的,只要这个类型是数组或者数组的指针,那len就能获得数组的长度,而这一切不需要s一定是常量表达式,编译器可以简单推导出表达式的值的类型。不能包含non-constant function calls和chan接收是我在第一点里解释的,杜绝所有可能的副作用发生从而保证即使不对表达式求值程序也是正确的,不包含这两个操作的表达式既可以是常量的也可以不是,所以这里我们能用x.s[99]作为len的参数。
说了这么多,只要len的参数类型为array或者array的指针并且符合要求,就不会进行求值,而题目里的表达式正好满足这点,所以虽然我们看起来是会导致panic的代码,但是本身并未进行实际求值,因此程序可以正常运行。另外cap也遵循同样的规则。
最后,还有个小测验,检验一下自己的学习吧:
// 以下哪些语句是正确的,哪些是错误的
var slice [][]*[10]int
const (
a = len(slice[10000000000000][4]) // 1
b = len(slice[1]) // 2
c = len(slice) // 3
d = len([1]int{1024}) // 4
e = len([1]int{add(512, 512)}) // 5
g = len([unsafe.Sizeof(slice)]int{}) // 6
g = len([unsafe.Sizeof(slice)]int{int(unsafe.Sizeof(slice))}) // 7
)
参考
https://golang.org/ref/spec#Length_and_capacity
golang拾遗:内置函数len的小知识的更多相关文章
- 【原创】Matlab.NET混合编程技巧之找出Matlab内置函数
本博客所有文章分类的总目录:[总目录]本博客博文总目录-实时更新 Matlab和C#混合编程文章目录 :[目录]Matlab和C#混合编程文章目录 Matlab与.N ...
- Matlab.NET混编技巧之——找出Matlab内置函数
原文 http://www.cnblogs.com/asxinyu/p/3295309.html Matlab与.NET的混合编程,掌握了基本过程,加上一定的开发经验和算法基础,肯 定不难.反之,有时 ...
- Matlab.NET混合编程技巧之——找出Matlab内置函数
原文:[原创]Matlab.NET混合编程技巧之--找出Matlab内置函数 Matlab与.NET的混合编程,掌握了基本过程,加上一定的开发经验和算法基础,肯定不难.反之,有时候一个小错误,可能抓破 ...
- python 内置函数 进制转换
4.内置函数 自定义函数 内置函数 len Open id() type() range() 输入输出 print() input() 强制转换 int() float() list() tuple( ...
- python基础(10)-匿名函数&内置函数
匿名函数 例子 返回两个数的和 def add(x, y): return x + y # 等价于 add = lambda x, y: x + y 返回字典中值最大的key dic = {'a': ...
- python 全栈开发,Day14(列表推导式,生成器表达式,内置函数)
一.列表生成式 生成1-100的列表 li = [] for i in range(1,101): li.append(i) print(li) 执行输出: [1,2,3...] 生成python1期 ...
- Python内置函数和匿名函数
内容回顾: 1.列表推导式 [变量(加工后的变量) for 变量 in iterable] 循环模式 [变量(加工后的变量) for 变量 in iterable if 条件] 筛选模式 生成器表达式 ...
- Python内置函数系列
Python内置(built-in)函数随着python解释器的运行而创建.在Python的程序中,你可以随时调用这些函数,不需要定义. 作用域相关(2) locals() :以字典类型返回当前位置 ...
- python常见内置函数总结
简单的内置函数 len 求长度 min 求最小值 max 求最大值 sorted 排序 reversed 反向 sum 求和 进制转换 bin 转为二进制 oct 转为八 ...
随机推荐
- 如何在TVM上集成Codegen(上)
如何在TVM上集成Codegen(上) 许多常用的深度学习内核,或者提供DNNL或TensorRT等框架和图形引擎,让用户以某种方式描述他们的模型,从而获得高性能.此外,新兴的深度学习加速器也有自己的 ...
- Eclipse修改默认的语言编码设置,处理乱码
Eclipse中设置:
- CSS3-过渡、动画、2D与3D
过渡 过渡属性transition,可适用于大部分属性间变化的过渡,例如长度的.宽度和透明度等等. 语法: transition: property duration timing-function ...
- NX二次开发-将3X3矩阵修正为正交且长度为单位长度的矩阵
函数:UF_MTX3_ortho_normalize() 函数说明:将矩阵修正为正交且xyz长度为单位长度的矩阵.下图中输入的矩阵为三条线段的端点,经过修正后,生成一个坐标系. 1 #include ...
- TensorFlow入门实操课程第一章教程笔记
神经元网络深度学习的起步程序 Hello World 第一个应用程序总是应该从超级简单的东西开始,这样可以看到代码如何产生和运作的整体框架. 就创建神经网络而言,我喜欢使用的例子是一个能够学习两组数字 ...
- LuatOS | 全新在线模拟器,随时随地发挥创意
LuatOS --运行在嵌入式硬件的实时操作系统,开启全新物联网生态. 聚焦嵌入式应用生态,内置功能可支持绝大多数物联网应用场景.深度整合Lua语言,只需少量内存和Flash空间就能运行.不局限于合宙 ...
- csps前小结
冒着题没改完颓废被发现的风险来写博客 好像离csps只剩两天了,然而没啥感觉 最近考试有时考得还算可以,有时也会很炸 今天考试事实上心态啥崩,因为T1结论题一直没思路,想了一个小时连暴力都没打 过了一 ...
- [Docker核心之容器、数据库文件的导入导出、容器镜像的导入导出]
[Docker核心之容器.数据库文件的导入导出] 使用 Docker 容器 在 Docker 中,真正对外提供服务的还是容器,容器是对外提供服务的实例,容器的本质是进程. 运行一个容器 docker ...
- 番外篇:使用nssm工具将ES、Kibana、Logstash或者其他.bat文件部署为Windows后台服务的方法
使用NSSM工具安装bat文件为Windows服务 nssm是一个可以把bat批处理文件部署为Windows服务的小工具.例如很多.net项目可能还是在Windows服务器上面跑的,但是很多组件只提供 ...
- hive学习笔记之五:分桶
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...