Golang无法对指向切片的指针进行范围操作

38

当我尝试对一个切片指针进行范围循环时,我一直收到这个错误。

app/domain/repositories/class_repository.go:24: cannot range over classes (type *[]entities.Class)

我做错了什么?

这是结构体:

 package repositories

import (
    "mobifit/app/domain/entities"
)

type ClassRepository struct {
    *Repository
}

func (c *ClassRepository) ClassesForLastNDays(days int) *[]entities.Class {
    classes := new([]entities.Class)
    query := Select("*").
        From("Class").
        Where("VisibleAt > CURRENT_TIMESTAMP() - INTERVAL ? DAY").
        OrderBy("ClassTypeId").
        Sql()
    c.Repository.Select(classes, query, days)
    c.populateClassRelationships(classes)
    return classes
}

func (c *ClassRepository) populateClassRelationships(classes *[]entities.Class) {
    for i := range classes {  <<<<<<<<<<< Here is the problem
        class := classes[i]

        // ClassType
        c.Repository.GetById(class.ClassType, class.ClassTypeId)

        //Instructor
        c.Repository.GetById(class.Instructor, class.ClassType.InstructorId)

        // Equipment
        query := Select("E.*").
            From("Equipment E").
            Join("ClassEquipment CE on E.Id = CE.EquipmentId").
            Where("CE.ClassId = ?").
            Sql()
        c.Repository.Select(class.Equipment, query, class.Id)
    }
}

这里是 Class 结构体:

package entities

import (
    "time"
)

    type Class struct {
        Id                int
        ClassTypeId       int
        VideoPath         string
        VideoSize         int
        Duration          float64
        CreatedAt         time.Time
        VisibleAt         time.Time
        NoLongerVisibleAt time.Time

        // Relationships
        ClassType  ClassType
        Instructor User
        Equipment  []Equipment
    }

切片本身就是一种指针,没有必要再指向它。 - Denys Séguret
我想要一个指针切片,这样我就可以用PoulateClassRelationships函数填充它们。 - Lee
@dystroy 我认为你现在实际上已经得出了最好的答案,因为你已经到达了问题的根本。 根据 http://golang.org/doc/effective_go.html#slices 的说明,如果一个函数接受一个切片参数并对其元素进行更改,则这些更改将对调用方可见,类似于传递指向底层数组的指针 - Lee
1
我曾经也踩过同样的耙子。Play - Ivan Black
4个回答

39

你假设一个slice的指针将自动解引用进行迭代。

这不是这种情况,也没有理由这样做,因为一个slice已经是一种指针,使得一个指向slice的指针完全无用。

来自Effective Go:

如果一个函数接受一个切片参数,则对切片元素的更改将对调用者可见,类似于传递指向底层数组的指针。

在内部,一个slice是由以下部分组成的

  • 指向底层数组中切片第一个元素的指针
  • 切片的长度
  • 切片的容量(通常可以扩展切片直到数组的末尾)

这个结构非常小,使指针变得无用。


6
澄清:指向切片的指针有一个用途:如果程序的多个部分需要共享同一个切片,那么对切片本身的修改会反映在程序的其他部分中(例如,如果通过执行a = append(a[:i], a[i+1:]...)从切片中删除元素应该反映在其他数据结构中持有的切片中)。然而,这通常并不是您想要的,而且没有锁定就不是线程安全的。 - fixermark
刚刚花了几个小时调试代码,因为我试图变得太聪明了。我有一个函数返回一个 []*structs,因为我不想重复内存分配。但这在 for each 循环中构建返回结果时对我的代码造成了很大的破坏,因为切片中的每个指针都指向 for each 循环迭代器的内存地址,这意味着我最终返回的切片有 10 个指针都指向同一个结构体。当我在等号两边去除指针并只返回一个普通的切片后,我的代码完美地运行了。 - fIwJlxSzApHEZIl

14

如果你需要从*切片中提取单个元素,你必须首先对它进行解引用,像这样:(*slice)[0]。在我意识到这一点之前,我花了大约6个小时来尝试*slice[0]。这与操作顺序有关,不是一个非常优雅的结果。

最终,我编写了一些指针接收器方法,以更加合理的方式执行就地修改,例如追加和弹出 - 可以在这里找到示例:https://play.golang.org/p/qZEYMcPHl4


2
@HassaanSalik - 感谢您的点赞,所以我想说我再也不这样做了。切片已经是指针类型,所以通常不需要使用*slice - 我已经重构了这段代码,并建议您寻找类似的机会。 - domoarigato

12

引用自《Effective Go》

如果您想要循环遍历一个数组、切片、字符串或映射,或从通道中读取数据,则可以使用 range 子句来管理循环。

您正试图迭代一个指向切片的指针,而它是一个单值,不是集合类型,因此无法进行迭代。

populateClassRelationships 的参数改为一个切片,而不是一个指向切片的指针。或者您可以对指针进行解引用:

func (c *ClassRepository) populateClassRelationships(classes *[]entities.Class) {
    for i := range *classes { // dereferencing the pointer to get the actual slice
        class := classes[i]

        // ClassType
        c.Repository.GetById(class.ClassType, class.ClassTypeId)

        //Instructor
        c.Repository.GetById(class.Instructor, class.ClassType.InstructorId)

        // Equipment
        query := Select("E.*").
            From("Equipment E").
            Join("ClassEquipment CE on E.Id = CE.EquipmentId").
            Where("CE.ClassId = ?").
            Sql()
        c.Repository.Select(class.Equipment, query, class.Id)
    }
}

不,它不一定要是指针,我只是想让它能够工作。 - Lee
你能演示一下如何不使用指针来完成吗?我将关键部分复制到了http://play.golang.org/p/KonrOk3bp-。 - Lee
第19行出现了问题。 - Lee
我认为,如果在调用populateClassRelationships时没有传递数组的指针,则只是在填充副本,原始数组在ClassesForLastNDays返回时将为空。 - Lee
现在出现了问题:无法将 &classes (type **[]entities.Class) 作为函数参数中的类型 []entities.Class 使用。 - Lee

5
您可以取消引用指针:
func (c *ClassRepository) populateClassRelationships(classes *[]entities.Class) {
    for _, class := range *classes { // NOTE the * dereference
    // ClassType
    c.Repository.GetById(class.ClassType, class.ClassTypeId)

    //Instructor
    c.Repository.GetById(class.Instructor, class.ClassType.InstructorId)

    // Equipment
    query := Select("E.*").
        From("Equipment E").
        Join("ClassEquipment CE on E.Id = CE.EquipmentId").
        Where("CE.ClassId = ?").
        Sql()
    c.Repository.Select(class.Equipment, query, class.Id)
    }
}

我也改变了range子句,因为我认为你没有修改classes


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接