我的编程空间,编程开发者的网络收藏夹
学习永远不晚

golang内置函数len的小技巧

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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的小技巧的文章就介绍到这了,更多相关golang内置函数len内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

golang内置函数len的小技巧

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

优化Python中len函数的性能技巧介绍

了解Python中len函数的性能优化技巧,需要具体代码示例Python是一种简单易学的高级编程语言,被广泛应用于数据处理、科学计算、机器学习等领域。在Python中,len函数是一个常用的函数,用于获取容器(如列表、元组、字符串等)中的
优化Python中len函数的性能技巧介绍
2024-01-13

深入探究Python中len函数的应用技巧

Python中len函数的高级应用技巧在Python编程中,len()函数是一个非常常用的函数,主要用于计算字符串、列表、元组等可迭代对象的长度。然而,除了常规的用法外,len()函数还有一些高级应用技巧,可以帮助我们更好地处理数据和优化代
深入探究Python中len函数的应用技巧
2024-01-29

快速掌握运用Python中len函数的技巧

快速掌握Python中len函数的使用技巧,需要具体代码示例Python是一种简洁、易读的编程语言,广泛应用于数据科学、机器学习、网络开发等领域。在Python中,len函数是一个非常常用且重要的函数之一。它用于返回一个对象(如字符串、列
快速掌握运用Python中len函数的技巧
2024-01-29

golang函数的内置函数

内置函数是 go 核心的预定义函数,可轻松执行常见任务,例如类型转换、字符串处理和数学运算。具体来说,它们包括:类型转换函数,允许在不同类型之间转换,如 string、int 和 float64。字符串处理函数,支持获取长度、提取子字符串、
golang函数的内置函数
2024-04-21

文本处理中利用LEN函数的技巧和要点

使用LEN函数进行文本处理的技巧和注意事项在Excel中,LEN函数是一种非常常用的函数,用于计算一个文本字符串中的字符数。它可以帮助我们快速了解一个文本字符串的长度,进而进行相应的文本处理操作。在本文中,我们将介绍使用LEN函数进行文本
文本处理中利用LEN函数的技巧和要点
2024-01-29

golang函数类型的调试技巧

调试函数类型技巧:使用 type assertion (.(type)) 确定实际类型。使用 fmt.printf 打印函数类型具体类型。实例:使用这些技巧调试函数类型转换,确保转换后的类型符合预期。Go 函数类型的调试技巧函数类型在 G
golang函数类型的调试技巧
2024-04-28

golang函数调用的优化技巧

go 函数调用优化技巧答案:通过优化函数调用技术,可显著提高 go 程序性能。减少函数调用次数: 减少不必要的调用次数,使用位运算替代函数调用。内联函数: 将函数体嵌入调用函数,避免函数调用开销。使用闭包: 闭包允许访问作用域变量,避免在每
golang函数调用的优化技巧
2024-04-29

golang函数动态创建新函数的技巧

go语言提供了两种动态函数创建技术: closures 和反射。closures 允许访问闭包作用域内的变量,而反射可使用funcof函数创建新函数。这些技术在自定义http路由器、实现高度可定制的系统和构建可插拔的组件方面非常有用。Go
golang函数动态创建新函数的技巧
2024-04-25

golang函数中的错误处理技巧

golang 函数中的错误处理技巧:使用 error.error() 将错误转换为字符串。使用 printf 格式化错误消息。使用 wrap 添加错误摘要。定义自定义错误类型以捕获重复错误。使用 wrap 函数在 api 响应中处理错误。G
golang函数中的错误处理技巧
2024-05-04

Golang函数的返回值处理技巧

go 函数可以返回多个值,处理方式包括使用命名返回值、元组、结构体和错误处理。命名返回值允许为每个返回值指定名称,便于访问和操作。元组和结构体可用于存储和组织返回值,但需要手动提取和转换值。错误处理机制提供了一种简洁的方式来处理函数执行状态
Golang函数的返回值处理技巧
2024-04-15

Golang中传递函数作为参数的技巧

向 go 函数传递函数可以实现模块化和可重用。基本语法是通过定义一个接收函数作为参数的函数来实现。高级用法包括:闭包、高阶函数和数据过滤。Go 语言中传递函数作为参数的技巧在 Go 语言中,传递函数作为参数是一个强大且灵活的功能。它允许您
Golang中传递函数作为参数的技巧
2024-04-13

Golang函数库的性能和优化技巧

要最大化 go 函数库性能,可以遵循以下优化技巧:避免动态内存分配以防止性能下降。缓存常用数据以提高重复访问的效率。并行执行任务以利用并发优势。使用 go 协程进行高效的并行处理。优化算法和数据结构,并使用内置的性能分析工具和编译时优化标志
Golang函数库的性能和优化技巧
2024-04-18

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录