Go语言封装qsort快速排序函数
来源:云海天教程
时间:2023-01-07 12:05:34 105浏览 收藏
怎么入门Golang编程?需要学习哪些知识点?这是新手们刚接触编程时常见的问题;下面golang学习网就来给大家整理分享一些知识点,希望能够给初学者一些帮助。本篇文章就来介绍《Go语言封装qsort快速排序函数》,涉及到并发,有需要的可以收藏一下
qsort 快速排序函数是 C语言的高阶函数,支持用于自定义排序比较函数,可以对任意类型的数组进行排序。本节我们尝试基于 C语言的 qsort 函数封装一个 Go语言版本的 qsort 函数。认识 qsort 函数
qsort 快速排序函数有void qsort(
void* base, size_t num, size_t size,
int (*cmp)(const void*, const void*)
);
cmp 排序函数的两个指针参数分别是要比较的两个元素的地址,如果第一个参数对应元素大于第二个参数对应的元素将返回结果大于 0,如果两个元素相等则返回 0,如果第一个元素小于第二个元素则返回结果小于 0。
下面的代码是用 C语言的 qsort 对一个 int 类型的数组进行排序:
#include package qsort//typedef int (*qsort_cmp_func_t)(const void* a, const void* b);import "C"import "unsafe"func Sort( base unsafe.Pointer, num, size C.size_t, cmp C.qsort_cmp_func_t,) { C.qsort(base, num, size, cmp)}因为 Go语言的 CGO 语言不好直接表达 C语言的函数类型,因此在 C语言空间将比较函数类型重新定义为一个 qsort_cmp_func_t 类型。 func Sort( /*#include package main//extern int go_qsort_compare(void* a, void* b);import "C"import ( "fmt" "unsafe" qsort ".")//export go_qsort_comparefunc go_qsort_compare(a, b unsafe.Pointer) C.int { pa, pb := (*C.int)(a), (*C.int)(b) return C.int(*pa - *pb)}func main() { values := []int32{42, 9, 101, 95, 27, 25} qsort.Sort(unsafe.Pointer(&values[0]), len(values), int(unsafe.Sizeof(values[0])), qsort.CompareFunc(C.go_qsort_compare), ) fmt.Println(values)}为了使用 Sort 函数,我们需要将 Go语言的切片取首地址、元素个数、元素大小等信息作为调用参数,同时还需要提供一个 C语言规格的比较函数。其中 go_qsort_compare 是用 Go语言实现的,并导出到 C语言空间的函数,用于 qsort 排序时的比较函数。 func Slice(slice interface{}, less func(i, j int) bool) import "sort"func main() { values := []int32{42, 9, 101, 95, 27, 25} sort.Slice(values, func(i, j int) bool { return values[i] 我们也尝试将 C语言的 qsort 函数包装为以下格式的 Go语言函数: package qsort var go_qsort_compare_info struct { fn func(a, b unsafe.Pointer) int sync.Mutex}//export _cgo_qsort_comparefunc _cgo_qsort_compare(a, b unsafe.Pointer) C.int { return C.int(go_qsort_compare_info.fn(a, b))}其中导出的 C语言函数 _cgo_qsort_compare 是公用的 qsort 比较函数,内部通过 go_qsort_compare_info.fn 来调用当前的闭包比较函数。 /*#include func main() { values := []int32{42, 9, 101, 95, 27, 25} qsort.Sort(unsafe.Pointer(&values[0]), len(values), int(unsafe.Sizeof(values[0])), func(a, b unsafe.Pointer) int { pa, pb := (*int32)(a), (*int32)(b) return int(*pa - *pb) }, ) fmt.Println(values)}现在排序不再需要通过 CGO 实现 C语言版本的比较函数了,可以传入 Go语言闭包函数作为比较函数。但是导入的排序函数依然依赖 unsafe 包,这是违背 Go语言编程习惯的。 package qsort var go_qsort_compare_info struct { //export _cgo_qsort_comparefunc _cgo_qsort_compare(a, b unsafe.Pointer) C.int { var ( // array memory is locked base = uintptr(go_qsort_compare_info.base) elemsize = uintptr(go_qsort_compare_info.elemsize) ) i := int((uintptr(a) - base) / elemsize) j := int((uintptr(b) - base) / elemsize) switch { case go_qsort_compare_info.less(i, j): // v[i] v[j] return +1 default: return 0 }}新的 Slice 函数的实现如下: func Slice(slice interface{}, less func(a, b int) bool) { sv := reflect.ValueOf(slice) if sv.Kind() != reflect.Slice { panic(fmt.Sprintf("qsort called with non-slice value of type %T", slice)) } if sv.Len() == 0 { return } go_qsort_compare_info.Lock() defer go_qsort_compare_info.Unlock() defer func() { go_qsort_compare_info.base = nil go_qsort_compare_info.elemnum = 0 go_qsort_compare_info.elemsize = 0 go_qsort_compare_info.less = nil }() // baseMem = unsafe.Pointer(sv.Index(0).Addr().Pointer()) // baseMem maybe moved, so must saved after call C.fn go_qsort_compare_info.base = unsafe.Pointer(sv.Index(0).Addr().Pointer()) go_qsort_compare_info.elemnum = sv.Len() go_qsort_compare_info.elemsize = int(sv.Type().Elem().Size()) go_qsort_compare_info.less = less C.qsort( go_qsort_compare_info.base, C.size_t(go_qsort_compare_info.elemnum), C.size_t(go_qsort_compare_info.elemsize), C.qsort_cmp_func_t(C._cgo_qsort_compare), )}首先需要判断传入的接口类型必须是切片类型。然后通过反射获取 qsort 函数需要的切片信息,并调用 C语言的 qsort 函数。 import ( "fmt" qsort ".")func main() { values := []int64{42, 9, 101, 95, 27, 25} qsort.Slice(values, func(i, j int) bool { return values[i] 为了避免在排序过程中,排序数组的上下文信息 go_qsort_compare_info 被修改,我们进行了全局加锁。因此目前版本的 qsort.Slice 函数是无法并发执行的,读者可以自己尝试改进这个限制。 好了,本文到此结束,带大家了解了《Go语言封装qsort快速排序函数》,希望本文对你有所帮助!关注golang学习网公众号,给大家分享更多Golang知识!将 qsort 函数从 Go 包导出
为了方便 Go语言的非 CGO 用户使用 qsort 函数,我们需要将 C语言的 qsort 函数包装为一个外部可以访问的 Go函数。
用 Go语言将 qsort 函数重新包装为 qsort.Sort 函数:
虽然 Sort 函数已经导出了,但是对于 qsort 包之外的用户依然不能直接使用该函数——Sort 函数的参数还包含了虚拟的 C 包提供的类型。 在虚拟的 C 包下的任何名称其实都会被映射为包内的私有名字。比如 C.size_t 会被展开为 _Ctype_size_t,C.qsort_cmp_func_t 类型会被展开为 _Ctype_qsort_cmp_func_t 。
被 CGO 处理后的 Sort 函数的类型如下:
base unsafe.Pointer, num, size _Ctype_size_t,
cmp _Ctype_qsort_cmp_func_t,
)
重新调整 Sort 函数的参数类型和实现如下:
以下代码展示了 Sort 函数的使用方式:
目前已经实现了对 C语言的 qsort 初步包装,并且可以通过包的方式被其它用户使用。但是 qsort.Sort 函数已经有很多不便使用之处:用户要提供 C语言的比较函数,这对许多 Go语言用户是一个挑战。
下一步我们将继续改进 qsort 函数的包装函数,尝试通过闭包函数代替 C语言的比较函数。消除用户对 CGO 代码的直接依赖。改进:闭包函数作为比较函数
在改进之前我们先回顾下 Go语言 sort 包自带的排序函数的接口:
func Sort(base unsafe.Pointer, num, size int, cmp func(a, b unsafe.Pointer) int)
代码如下所示:
新的 Sort 包装函数实现如下:
基于新包装的函数,我们可以简化之前的排序代码:改进:消除用户对 unsafe 包的依赖
前一个版本的 qsort.Sort 包装函数已经比最初的 C语言版本的 qsort 易用很多,但是依然保留了很多 C语言底层数据结构的细节。现在我们将继续改进包装函数,尝试消除对 unsafe 包的依赖,并实现一个类似标准库中 sort.Slice 的排序函数。
新的包装函数声明如下:
func Slice(slice interface{}, less func(a, b int) bool)
为了保存必要的排序上下文信息,我们需要在全局包变量增加要排序数组的地址、元素个数和元素大小等信息,比较函数改为 less:
base unsafe.Pointer
elemnum int
elemsize int
less func(a, b int) bool
sync.Mutex
}
基于新包装的函数我们可以采用和标准库相似的方式排序切片:
-
417 收藏
-
328 收藏
-
367 收藏
-
221 收藏
-
109 收藏
-
266 收藏
-
188 收藏
-
317 收藏
-
430 收藏
-
451 收藏
-
311 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 542次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 507次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 497次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 484次学习