golang内置函数len的小技巧
来源:脚本之家
时间:2023-01-07 12:07:51 318浏览 收藏
对于一个Golang开发者来说,牢固扎实的基础是十分重要的,golang学习网就来带大家一点点的掌握基础知识点。今天本篇文章带大家了解《golang内置函数len的小技巧》,主要介绍了内置函数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的知识介绍,希望对你的学习有所帮助!若是受益匪浅,那就动动鼠标收藏这篇《golang内置函数len的小技巧》文章吧,也可关注golang学习网公众号了解相关技术文章。
-
505 收藏
-
502 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
216 收藏
-
284 收藏
-
103 收藏
-
315 收藏
-
218 收藏
-
185 收藏
-
100 收藏
-
222 收藏
-
111 收藏
-
125 收藏
-
474 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 508次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习
-
- 谦让的月饼
- 真优秀,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢博主分享文章内容!
- 2023-03-24 05:14:04
-
- 心灵美的老虎
- 很详细,mark,感谢老哥的这篇文章,我会继续支持!
- 2023-03-24 00:55:08
-
- 酷炫的蜻蜓
- 这篇博文出现的刚刚好,很详细,很有用,码起来,关注楼主了!希望楼主能多写Golang相关的文章。
- 2023-02-28 02:59:22
-
- 饱满的小懒猪
- 这篇文章内容太及时了,太全面了,真优秀,已收藏,关注作者了!希望作者能多写Golang相关的文章。
- 2023-02-08 03:37:44
-
- 精明的凉面
- 受益颇多,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,帮助很大,总算是懂了,感谢大佬分享博文!
- 2023-02-07 21:56:23
-
- 粗犷的西装
- 这篇技术贴真是及时雨啊,好细啊,很好,码住,关注楼主了!希望楼主能多写Golang相关的文章。
- 2023-02-06 09:56:45
-
- 风中的画笔
- 很有用,一直没懂这个问题,但其实工作中常常有遇到...不过今天到这,看完之后很有帮助,总算是懂了,感谢大佬分享技术文章!
- 2023-02-03 12:50:49
-
- 单薄的长颈鹿
- 这篇技术贴真是及时雨啊,大佬加油!
- 2023-01-25 12:50:38
-
- 自觉的黑米
- 这篇文章内容太及时了,太细致了,很有用,已加入收藏夹了,关注作者大大了!希望作者大大能多写Golang相关的文章。
- 2023-01-17 08:25:18
-
- 舒服的大地
- 这篇文章内容真及时,作者大大加油!
- 2023-01-15 10:40:23
-
- 丰富的曲奇
- 太详细了,已加入收藏夹了,感谢老哥的这篇文章内容,我会继续支持!
- 2023-01-14 16:16:29