len是很经常使用的内置函数,能够测量字符串、slice、array、channel以及map的长度/元素个数。c++

不过你真的了解len吗?也许还有一些你不知道的小知识。golang

咱们来看一道GO101的题目,这题也被GO语言爱好者周刊转载:express

package main

import "fmt"

func main() {
    var x *struct {
        s [][32]byte
    }
    
    fmt.Println(len(x.s[99]))
}

题目问你这段代码的运行结果,选项有编译错误、panic、32和0。c#

[][32]byte
nil

然而这么想你就错了,代码的实际运行结果是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.this

这句话很重要,对于结果是数组的表达式,len可能会是一个编译期常量,并且数组类型的长度在编译期是可知的,因此熟悉c++的朋友大概会马上想到这样的常量是不须要进行实际求值的,简单类型推导便可得到。不过口说无凭,咱们看看spec里的描述:lua

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里哪些东西是常量/常量表达式。

unsafe.Sizeof
unsafe.Sizeof
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的结果是常量呢?

x.s[99]

说了这么多,只要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
)
参考