在Go语言的项目开发中,结构体切片是常用的数据载体,很多时候我们需要从这类切片中筛选出符合特定条件的元素,比如筛选出成绩大于90分的学生,或者状态为已完成的任务。不同的筛选方式在代码简洁度和可复用性上有明显区别,下面逐一介绍常见的实现技巧。

基础循环筛选方式
最基础的筛选方式是通过for循环遍历结构体切片,逐个判断元素是否符合条件,将符合条件的元素追加到新的切片中。这种方式逻辑直观,适合简单的筛选场景。
假设我们有一个Student结构体,包含姓名和分数两个字段,现在需要筛选出分数大于90的学生:
package main
import "fmt"
// 定义学生结构体
type Student struct {
Name string
Score int
}
func main() {
// 初始化学生切片
students := []Student{
{Name: "张三", Score: 88},
{Name: "李四", Score: 95},
{Name: "王五", Score: 92},
{Name: "赵六", Score: 85},
}
// 定义结果切片,注意初始化为空切片而非nil,避免后续追加问题
var filtered []Student
// 遍历原切片,筛选分数大于90的学生
for _, stu := range students {
if stu.Score > 90 {
filtered = append(filtered, stu)
}
}
fmt.Println("筛选后的学生:", filtered)
}
封装可复用的筛选函数
如果多个地方都需要做类似的筛选操作,重复写循环会让代码冗余,这时候可以把筛选逻辑封装成一个通用函数。我们可以定义一个接收结构体切片和筛选条件的函数,返回筛选后的结果。
上面的学生筛选场景可以封装为如下函数:
package main
import "fmt"
type Student struct {
Name string
Score int
}
// 筛选函数,接收学生切片和筛选条件函数
func FilterStudents(stus []Student, condition func(Student) bool) []Student {
var result []Student
for _, stu := range stus {
if condition(stu) {
result = append(result, stu)
}
}
return result
}
func main() {
students := []Student{
{Name: "张三", Score: 88},
{Name: "李四", Score: 95},
{Name: "王五", Score: 92},
{Name: "赵六", Score: 85},
}
// 传入筛选条件:分数大于90
filtered := FilterStudents(students, func(stu Student) bool {
return stu.Score > 90
})
fmt.Println("筛选后的学生:", filtered)
}
这种方式把筛选逻辑和遍历逻辑分离,后续如果需要修改筛选条件,只需要修改传入的条件函数即可,不需要改动遍历部分的代码。
基于泛型的通用筛选实现
Go 1.18版本引入了泛型,我们可以写一个支持任意类型的通用筛选函数,不需要针对每个结构体都单独写筛选逻辑。通用函数的实现思路和上面的封装函数类似,只是把类型参数化。
package main
import "fmt"
type Student struct {
Name string
Score int
}
type Order struct {
ID int
Status string
}
// 通用筛选函数,支持任意类型的切片
func Filter[T any](items []T, condition func(T) bool) []T {
var result []T
for _, item := range items {
if condition(item) {
result = append(result, item)
}
}
return result
}
func main() {
students := []Student{
{Name: "张三", Score: 88},
{Name: "李四", Score: 95},
{Name: "王五", Score: 92},
}
orders := []Order{
{ID: 1, Status: "已完成"},
{ID: 2, Status: "待支付"},
{ID: 3, Status: "已完成"},
}
// 筛选分数大于90的学生
filteredStus := Filter(students, func(stu Student) bool {
return stu.Score > 90
})
fmt.Println("筛选后的学生:", filteredStus)
// 筛选状态为已完成的订单
filteredOrders := Filter(orders, func(order Order) bool {
return order.Status == "已完成"
})
fmt.Println("筛选后的订单:", filteredOrders)
}
筛选过程的注意事项
避免修改原切片元素
如果结构体是值类型,筛选过程中追加到新切片的是原元素的副本,修改新切片的元素不会影响原切片。但如果结构体包含引用类型字段(比如切片、map),或者你操作的是结构体指针切片,就需要注意修改新切片元素是否会影响原切片。
切片容量问题
如果提前知道筛选后的结果大概有多少元素,可以在初始化结果切片时指定容量,减少append过程中的扩容次数,提升性能。比如预计筛选后大概有一半元素符合,可以初始化为result := make([]Student, 0, len(students)/2)。
空切片处理
筛选结果可能为空,这时候返回的是空切片而不是nil,在后续使用的时候不需要额外判断nil,直接遍历即可,避免空指针问题。