Golang中何时使用接收接口指针的函数

Golang中何时使用接收接口指针的函数 在研究结构体接口时,我遇到了一个有趣的问题:我声明了一个接收接口指针的函数,但无法通过结构体值或结构体指针来调用该函数。请看以下代码片段:

package main

type Intr interface {
    m1()
}

type Student struct {
}

func (s *Student) m1() {
}

func main() {
    s := Student{}
    p := &Student{}

    myfnc(s)         // 不工作
    myfnc(p)         // 不工作
    myfnc(p.(*Intr)) // 不工作
    myfnc(s.(*Intr)) // 不工作
}

func myfnc(i *Intr) {
}

观察主函数中的调用尝试:

  • 结构体值
  • 结构体指针
  • 将结构体指针转换为接口指针
  • 将结构体值转换为接口指针

但所有这些方式都无效。我很好奇这种声明方式在什么场景下需要使用,以及具体该如何使用。


更多关于Golang中何时使用接收接口指针的函数的实战教程也可以访问 https://www.itying.com/category-94-b0.html

6 回复

更多关于Golang中何时使用接收接口指针的函数的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


我朋友想问在Go语言中是否允许将接口指针作为参数使用。我们该如何使用这个函数,因为从语法上看这些方法都行不通。这是否是一个bug?

// 代码示例应放在这里

1 个赞

非常感谢你的回答。

我正在深入探索接口、方法和指针的相关知识。出于好奇,我尝试创建了一个指向接口的指针。Go编译器没有报错,允许我创建它。

由于接口只是一种可能包含方法列表的契约,任何定义了这些方法的类型都会隐式实现该接口。因此我尝试了上述场景。多亏了你,你很好地解释了指向接口的指针也是一个具体类型。

但我仍然无法理解,为什么需要指向接口的指针?为什么会有人创建指向接口的指针?它有什么实际用例?

Go语言中的类型可分为两类:具体类型和接口类型。具体类型指所有非接口的类型,包括整型、字符串、数组、切片、映射和指针。空接口类型interface{}属于接口类型,但指向空接口的指针*interface{}本身是具体类型,就像空接口切片[]interface{}也是具体类型一样。

我已调整您的示例使其能够编译:https://play.golang.org/p/cs0RjFf16Sh

至于为何要使用接口指针,我认为合理的应用场景并不多。

func main() {
    var i interface{} = "hello"
    var pi *interface{} = &i
    fmt.Println(*pi)
}

@Prithvipal_Singh 使用场景必须非常具体,就像任何会让开发者使用 ***bool 的令人费解的场景一样。

我能想到的一个比较真实的场景是反序列化:

import (
    "encoding/binary"
    "reflect"
)

var (
    endianness = binary.BigEndian
)

func MyUnmarshal(p []byte, t interface{}) error {
    switch t := t.(type) {
    case *uint32:
        *t = endianness.Uint32(p[:4])
    case *uint64:
        *t = endianness.Uint64(p[:8])
    // ...
    case *interface{}:
        // 使用 p 的前4个字节来确定某种魔术类型代码
        // 并将其反序列化到该类型中:
        code := endianness.Uint32(p[:4])
        tp := getReflectTypeForCode(code)
        v := reflect.New(tp).Interface()
        if err := MyUnmarshal(p[4:], v); err != nil {
            return err
        }
        *t = v
    }
    return nil
}

在Go语言中,接收接口指针作为参数的函数确实有其特定的使用场景,但需要正确理解接口和指针的语义。让我们分析你的代码并给出正确的用法。

问题分析

在你的代码中,主要问题是:

  1. *Intr 表示接口指针,而不是实现了接口的类型的指针
  2. 接口本身已经是一个引用类型,通常不需要再使用指针
  3. 类型断言语法使用错误

正确的使用场景

接口指针主要在以下场景中使用:

1. 需要修改接口值本身时

package main

import "fmt"

type Intr interface {
    m1()
}

type Student struct {
    Name string
}

func (s *Student) m1() {
    fmt.Printf("Student %s: m1 called\n", s.Name)
}

// 接收接口指针,可以修改接口持有的具体值
func replaceInterfaceValue(i *Intr) {
    // 创建一个新的实现者替换原接口值
    *i = &Student{Name: "Replaced Student"}
}

func main() {
    var intr Intr = &Student{Name: "Original Student"}
    
    fmt.Println("Before replacement:")
    intr.m1()
    
    // 传递接口指针来修改接口值
    replaceInterfaceValue(&intr)
    
    fmt.Println("After replacement:")
    intr.m1()
}

2. 需要nil接口检查时

package main

import "fmt"

type Intr interface {
    m1()
}

type Student struct {
    Name string
}

func (s *Student) m1() {
    if s == nil {
        fmt.Println("nil student")
        return
    }
    fmt.Printf("Student %s: m1 called\n", s.Name)
}

// 检查接口是否为nil,并可能重新赋值
func checkAndAssign(i *Intr) {
    if *i == nil {
        fmt.Println("Interface is nil, assigning new value")
        *i = &Student{Name: "New Student"}
    } else {
        fmt.Println("Interface is not nil")
    }
}

func main() {
    var intr Intr
    
    // 初始为nil接口
    checkAndAssign(&intr)
    intr.m1()
    
    // 重新赋值为nil
    intr = nil
    checkAndAssign(&intr)
    intr.m1()
}

3. 在需要修改接口持有的具体类型时

package main

import "fmt"

type Intr interface {
    m1()
}

type Student struct {
    Name string
}

type Teacher struct {
    Subject string
}

func (s *Student) m1() {
    fmt.Printf("Student %s studying\n", s.Name)
}

func (t *Teacher) m1() {
    fmt.Printf("Teacher teaching %s\n", t.Subject)
}

// 切换接口持有的具体类型
func switchImplementation(i *Intr) {
    // 根据当前类型决定切换到哪种实现
    if _, isStudent := (*i).(*Student); isStudent {
        *i = &Teacher{Subject: "Mathematics"}
    } else {
        *i = &Student{Name: "New Student"}
    }
}

func main() {
    var intr Intr = &Student{Name: "Alice"}
    
    fmt.Println("Initial type:")
    intr.m1()
    
    switchImplementation(&intr)
    fmt.Println("After switching:")
    intr.m1()
    
    switchImplementation(&intr)
    fmt.Println("After switching again:")
    intr.m1()
}

修正你的原始代码

package main

type Intr interface {
    m1()
}

type Student struct {
}

func (s *Student) m1() {
}

// 正确:接收接口类型,而不是接口指针
func myfnc(i Intr) {
    i.m1()
}

// 如果需要使用接口指针的场景
func myfncWithPtr(i *Intr) {
    if *i != nil {
        (*i).m1()
    }
}

func main() {
    s := Student{}
    p := &Student{}
    
    // 这些调用现在可以工作
    myfnc(p)  // p实现了Intr接口
    
    var intr Intr = p
    myfnc(intr)
    
    // 使用接口指针的场景
    var intrPtr Intr = &Student{}
    myfncWithPtr(&intrPtr)
}

关键要点

  1. 接口已经是引用类型:接口值包含指向具体数据的指针,通常不需要再使用接口指针
  2. 方法接收者决定实现func (s *Student) m1() 意味着只有 *Student 实现了 Intr 接口
  3. 接口指针的使用场景有限:主要用于需要修改接口值本身的情况
  4. 类型断言语法:正确的类型断言是 value, ok := intr.(*Student),而不是 intr.(*Intr)

在大多数情况下,你应该直接使用接口类型而不是接口指针。只有在确实需要修改接口变量本身时才考虑使用接口指针。

回到顶部