惊!Go里面居然有这样精妙的小函数!
首先老许要感谢他人的认同,这是我乐此不彼的动力,同时我也需要反思。这位小姐姐还是比较委婉, 但用我们四川话来说,前一篇文章的标题是真的cuo
。
老许反复思考后决定哗众取宠一波,感叹号双连取名曰“惊!Go里面居然有这样精妙的小函数!”。下面就让我们来看看和标题没那么符合的一些小函数。
返回a/b向上舍入最接近的整数
func divRoundUp(n, a uintptr) uintptr { return (n + a - 1) / a }
这个方法用过的人应该不少,最典型的就是分页计算。
判断x是否为2的n次幂
func isPowerOfTwo(x uintptr) bool { return x&(x-1) == 0 }
这个也挺容易理解的,唯一需要注意的是x需要大于0,因为该等式0也是成立的。
向上/下将x舍入为a的倍数,且a必须是2的n次幂
// 向上将x舍入为a的倍数,例如:x=6,a=4则返回值为8 func alignUp(x, a uintptr) uintptr { return (x + a - 1) &^ (a - 1) } // 向上将x舍入为a的倍数,例如:x=6,a=4则返回值为4 func alignDown(x, a uintptr) uintptr { return x &^ (a - 1) }
在这里老许再次明确一个概念,2的n次幂即为1左移n位
。然后上述代码中^
为单目运算法按位取反,则^ (a - 1)
的运算结果是除了最低n位为0其余位全为1。剩余的部分则是一个简单的加减运算以及按位与。
上述代码分开来看每一部分都认识,合在一起就一脸懵逼了。幸运的是,经过老许的不懈努力终于找到了一种能够理解的方式。
以x=10,a=4
为例。a
为2的2次幂即1左移2位。x
可看作两部分之和,第一部分x1为0b1000
,第二部分x2为0b0011
。x
的拆分方式是1左移n
位可得到a
来决定的,即x的最低n位为x2,x1则为x-x2。因此x1相当于0b10左移2位得到,即x1已经是a的整数倍,此时x2只要大于0则x2+a-1一定会向前进1,x1+1
或x1
不就是x向上舍入的a的整数倍嘛,最后和^ (a - 1)
进行与运算将最低2位清零得到最终的返回结果。
有一说一,我肯定是写不出这样的逻辑,这也令我不得不感叹大佬们对计算机的理解简直出神入化。这样的函数牛逼归牛逼,但是在实际开发中还是尽量少用。一是有使用场景的限制(a必须为2的n次幂),二是不易理解,当然炫技和装逼除外(性能要求极高也除外)。
布尔转整形
// bool2int returns 0 if x is false or 1 if x is true. func bool2int(x bool) int { return int(uint8(*(*uint8)(unsafe.Pointer(&x)))) }
如果让我来写这个函数,一个稀松平常的switch
就完事儿,现在我又多了一种装逼的套路。老许在这里特别友情提示,字节切片和字符串也可使用上述方式进行相互转换。
计算不同类型最低位0的位数
var ntz8tab = [256]uint8{ 0x08, ..., 0x00, } // Ctz8 returns the number of trailing zero bits in x; the result is 8 for x == 0. func Ctz8(x uint8) int { return int(ntz8tab[x]) } const deBruijn32ctz = 0x04653adf var deBruijnIdx32ctz = [32]byte{ 0, 1, 2, 6, 3, 11, 7, 16, 4, 14, 12, 21, 8, 23, 17, 26, 31, 5, 10, 15, 13, 20, 22, 25, 30, 9, 19, 24, 29, 18, 28, 27, } // Ctz32 counts trailing (low-order) zeroes, // and if all are zero, then 32. func Ctz32(x uint32) int { x &= -x // isolate low-order bit y := x * deBruijn32ctz >> 27 // extract part of deBruijn sequence i := int(deBruijnIdx32ctz[y]) // convert to bit index z := int((x - 1) >> 26 & 32) // adjustment if zero return i + z } const deBruijn64ctz = 0x0218a392cd3d5dbf var deBruijnIdx64ctz = [64]byte{ 0, 1, 2, 7, 3, 13, 8, 19, 4, 25, 14, 28, 9, 34, 20, 40, 5, 17, 26, 38, 15, 46, 29, 48, 10, 31, 35, 54, 21, 50, 41, 57, 63, 6, 12, 18, 24, 27, 33, 39, 16, 37, 45, 47, 30, 53, 49, 56, 62, 11, 23, 32, 36, 44, 52, 55, 61, 22, 43, 51, 60, 42, 59, 58, } // Ctz64 counts trailing (low-order) zeroes, // and if all are zero, then 64. func Ctz64(x uint64) int { x &= -x // isolate low-order bit y := x * deBruijn64ctz >> 58 // extract part of deBruijn sequence i := int(deBruijnIdx64ctz[y]) // convert to bit index z := int((x - 1) >> 57 & 64) // adjustment if zero return i + z }
Ctz8
、Ctz32
和Ctz64
分别计算无符号8、32、64位数最低位为0的个数,即某个数左移的位数。
函数的作用通过翻译倒是能理解,我也能深刻的明白这是典型的空间换时间,然而要问一句为什么我是万万答不上来的。不过老许已经替你们找好了答案,原因就藏在这篇Using de Bruijn Sequences to Index a 1 in a Computer Word论文中。欢迎巨佬们去挑战一下,而我只想坐享其成,那么在巨佬们分析完这篇论文之前就让这些函数安家在我的收藏栏里方便以后炫技。
这里特别说明,术业有专攻,我们不一定要所有东西都会,但要尽可能知道有这么一个东西存在。这即是老许为自己找的一个不去研究此论文的接口,也是写下此篇文章的意义之一(万一有人提到了Bruijn Sequences
关键词,我们也不至于显得过分无知)。
math/bits包中的部分函数
如果有人知道这个包,那请原谅我的无知直接跳过本部分即可。老许发现这个包是源于ntz8tab
变量所在文件runtime/internal/sys/intrinsics_common.go
中的一句注释。
// Copied from math/bits to avoid dependence.
作为一个资深的CV工程师, 看到这句的第一反应就是我终于可以挺直腰杆了。适当Copy代码不丢人!
math/bits
这个包函数较多,老许挑几个介绍即可,其余的还请各位读者自行挖掘。
LeadingZeros(x uint) int
: 返回x所有高位为0的个数。
TrailingZeros(x uint) int
: 返回x最低位为0的个数。
OnesCount(x uint) int
:返回x中bit位为1的个数。
Reverse(x uint) uint
: 将x按bit位倒序后再返回。
Len(x uint) int
: 返回表示x的有效bit位个数(高位中的0不计数)。
ReverseBytes(x uint) uint
: 将x按照每8位一组倒序后返回。
将x逃逸至堆
// Dummy annotation marking that the value x escapes, // for use in cases where the reflect code is so clever that // the compiler cannot follow. func escapes(x interface{}) { if dummy.b { dummy.x = x } } var dummy struct { b bool x interface{} }
老许是在reflect.ValueOf
函数中发现此函数的调用,当时就觉着挺有意思。如今再次回顾也依旧佩服不已。读书是和作者的对话,阅读源码是和开发者的对话,看到此函数就仿佛看到Go语言开发者们和编译器斗智斗勇的场景。
让出当前Processor
// Gosched yields the processor, allowing other goroutines to run. It does not // suspend the current goroutine, so execution resumes automatically. func Gosched() { checkTimeouts() mcall(gosched_m) }
让出当前的Processor,允许其他goroutine执行。在实际的开发当中老许还未遇到需要使用此函数的场景,但多了解总是有备无患。
以上是惊!Go里面居然有这样精妙的小函数!的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

在Go中,函数生命周期包括定义、加载、链接、初始化、调用和返回;变量作用域分为函数级和块级,函数内的变量在内部可见,而块内的变量仅在块内可见。

在Go中,可以使用正则表达式匹配时间戳:编译正则表达式字符串,例如用于匹配ISO8601时间戳的表达式:^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$。使用regexp.MatchString函数检查字符串是否与正则表达式匹配。

在Go中,可以使用gorilla/websocket包发送WebSocket消息。具体步骤:建立WebSocket连接。发送文本消息:调用WriteMessage(websocket.TextMessage,[]byte("消息"))。发送二进制消息:调用WriteMessage(websocket.BinaryMessage,[]byte{1,2,3})。

Go和Go语言是不同的实体,具有不同的特性。Go(又称Golang)以其并发性、编译速度快、内存管理和跨平台优点而闻名。Go语言的缺点包括生态系统不如其他语言丰富、语法更严格以及缺乏动态类型。

内存泄漏会导致Go程序内存不断增加,可通过:关闭不再使用的资源,如文件、网络连接和数据库连接。使用弱引用防止内存泄漏,当对象不再被强引用时将其作为垃圾回收目标。利用go协程,协程栈内存会在退出时自动释放,避免内存泄漏。

使用IDE查看Go函数文档:将光标悬停在函数名称上。按下热键(GoLand:Ctrl+Q;VSCode:安装GoExtensionPack后,F1并选择"Go:ShowDocumentation")。

在Golang中,错误包装器允许你在原始错误上追加上下文信息,从而创建新错误。这可用于统一不同库或组件抛出的错误类型,简化调试和错误处理。步骤如下:使用errors.Wrap函数将原有错误包装成新错误。新错误包含原始错误的上下文信息。使用fmt.Printf输出包装后的错误,提供更多上下文和可操作性。在处理不同类型的错误时,使用errors.Wrap函数统一错误类型。

对并发函数进行单元测试至关重要,因为这有助于确保其在并发环境中的正确行为。测试并发函数时必须考虑互斥、同步和隔离等基本原理。可以通过模拟、测试竞争条件和验证结果等方法对并发函数进行单元测试。
