GVKun编程网logo

指针接收器和值接收器在Golang中是什么意思?(golang指针与非指针接收器)

22

在这里,我们将给大家分享关于指针接收器和值接收器在Golang中是什么意思?的知识,让您更了解golang指针与非指针接收器的本质,同时也会涉及到如何更有效地Go-强制接口仅由方法上带有指针接收器的类

在这里,我们将给大家分享关于指针接收器和值接收器在Golang中是什么意思?的知识,让您更了解golang指针与非指针接收器的本质,同时也会涉及到如何更有效地Go - 强制接口仅由方法上带有指针接收器的类型满足?、golang 中的 nil 接收器、Golang 接收器是指针还是至类型的区别、golang-值接收者和指针接收者的区别的内容。

本文目录一览:

指针接收器和值接收器在Golang中是什么意思?(golang指针与非指针接收器)

指针接收器和值接收器在Golang中是什么意思?(golang指针与非指针接收器)

我一直在从关于指针接收器的话题中出错,我决定用谷歌搜索术语的含义,并且阅读了有关指针接收器的不同资源和文档。例如:http
:
//golang.org/doc/faq和http://jordanorelli.com/post/32665860244/how-
to-use-interfaces-in-go。

虽然,尽管他们谈论这些术语,但仍未能准确定义它们。不过,从上下文来看,我认为它们之间的区别在于将变量定义为类似*MyStructvs的指针MyStruct。尽管我不是100%肯定它们之间的区别,但我想对这些术语,尤其是它们的区别(指针接收者和值接收者)有一个更正式或扎实的理解。如果可能的话,一些简单的示例代码将显示它们在运行中的不同之处,那就太棒了!(可能是真正理解这一点所必需的)

例如,令我感到困惑的是,术语“指针”和“指针接收器”之间有什么区别?还是价值和价值接收者?接收者一词对这些概念有什么补充?

答案1

小编典典

既然您已经弄清楚了,您对术语“ 接收者”
而不是指针/值的区别感到困惑。在Go中,“接收器”是指为接口目的而在方法上定义的值。您可以将接收器视为函数的第一个参数的特例。

func (m MyStruct) DoStuff()

这就是所谓的“值接收器”,它在 MyStruct 上定义。这在功能上等同于:

func DoStuff(m MyStruct)

除:

使用“接收器”,您可以使用“。”来调用函数,就像在许多OO语言中一样:

 m := MyStruct{}  m.DoStuff() // as opposed to DoStuff(m)

类型是接收者的一组方法定义了它实现的接口:

type DoesStuff interface {    DoStuff()}func DoSomething(d DoesStuff) {    d.DoStuff()}func main() {    m := MyStruct{}    DoSomething(m)}

那么什么是指针接收器?看起来像这样:

func (m *MyStruct) DoOtherStuff()

区别恰恰是指针和值之间的区别。尽管会发生轻微的语义变化。Go会自动寻址和自动取消引用指针(在大多数情况下),因此m := MyStruct{};m.DoOtherStuff()Go仍然可以工作,因为Go会自动(&m).DoOtherStuff()为您服务。(自然,您也可以自由地做m :=&MyStruct{}; m.DoOtherStuff)。此外,接口是在指针上定义的,因此:

type DoesOtherStuff interface {    DoOtherStuff()}func DoSomethingElse(d DoesOtherStuff) {    d.DoOtherStuff()}func main() {    m := MyStruct{}    // DoSomethingElse(m) will fail since because the interface    // DoesOtherStuff is defined on a pointer receiver and this is a value    DoSomethingElse(&m)}

如果您仍然对 何时
使用指针接收器与变量接收器感到困惑,那么简短的答案是:可能是指针接收器。长答案已经被回答了好几次,但是我将其链接到这里仅仅是因为在我的历史记录中很容易找到它。

Go - 强制接口仅由方法上带有指针接收器的类型满足?

Go - 强制接口仅由方法上带有指针接收器的类型满足?

go - 强制接口仅由方法上带有指针接收器的类型满足?

php小编百草为您介绍Go语言中的强制接口规则,即只有方法上带有指针接收器的类型才能满足接口的要求。Go语言是一门静态类型的编程语言,它通过接口来实现多态性。在定义接口时,可以指定方法的接收器类型,可以是值类型或指针类型。但是,当我们使用强制接口规则时,只有方法上带有指针接收器的类型才能满足接口的要求,这是因为指针类型可以修改值的内容,而值类型不能。这个规则保证了接口的方法在操作值时不会引起不可预知的行为。通过了解这个规则,我们可以更好地理解Go语言中接口的使用和设计。

问题内容

我正在对类型参数进行一些实验,以提出一种连接结构的通用方法,以生成对 json http 请求的响应。

结构必须实现的 method 接口有一个 setparams 方法。只要实现使用指针接收器,这就会按预期工作。

我的问题:如果 setparams 有值接收器,有什么方法可以使其成为编译时错误吗?

以下示例演示了具有值接收器的 setparams 的问题:

package main

import (
    "encoding/json"
    "fmt"
    "log"
)

type PingParams struct {
    Name string
}

type PingResponse struct {
    Message string
}

func (p PingParams) Greeting() string {
    if p.Name != "" {
        return fmt.Sprintf("Hello, %s", p.Name)
    }

    return fmt.Sprintf("Hello, nobody!")
}

type GoodPing struct {
    Params PingParams
}

// SetParams has a pointer receiver.
func (m *GoodPing) SetParams(p PingParams) {
    fmt.Printf("assign %v with pointer receiver, Good!\n", p)
    m.Params = p
}
func (m GoodPing) Run() (*PingResponse, error) {
    return &PingResponse{Message: fmt.Sprintf("%T %s", m, m.Params.Greeting())}, nil
}

type BadPing struct {
    Params PingParams
}

// SetParams has a value receiver.
func (m BadPing) SetParams(p PingParams) {
    fmt.Printf("assign %v with value receiver, Bad!\n", p)
    m.Params = p
}
func (m BadPing) Run() (*PingResponse, error) {
    return &PingResponse{Message: fmt.Sprintf("%T %s", m, m.Params.Greeting())}, nil
}

type Method[M, RQ, RS any] interface {
    // Run builds the RPC result.
    Run() (*RS, error)
    // SetParams is intended to set the request parameters in the struct implementing the RPC method.
    // This then allows the request parameters to be easily available to all methods of the Method struct.
    // The method MUST have a pointer receiver. This is NOT enforced at compile time.
    SetParams(p RQ)
    // The following line requires the implementing type is a pointer to M.
    *M
    // https://stackoverflow.com/a/72090807
}

func HandlerMethod[M, RQ, RS any, T Method[M, RQ, RS]](in json.RawMessage) (*RS, error) {
    // A real implementation of this would return a func for wiring into a request router

    var req RQ

    err := json.Unmarshal(in, &req)

    if err != nil {
        return nil, err
    }

    var m T = new(M)

    m.SetParams(req)

    return m.Run()
}

func main() {

    payload := []byte(`{"Name": "Mark"}`)

    bad, err := HandlerMethod[BadPing, PingParams, PingResponse](payload)

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(bad.Message)

    good, err := HandlerMethod[GoodPing, PingParams, PingResponse](payload)

    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(good.Message)
}
登录后复制

https://go.dev/play/p/eii8adkmdxe

解决方法

你不能这么做。

当您在代码中执行以下操作时:

var m T = new(M)
登录后复制

即使t的类型集仅包括*m作为类型项,*m的方法集也包括在m上声明的方法。编译器无法检查该方法如何出现在 *m 的方法集中。

在 badping 上声明方法 setparam 时,您有责任确保该方法不会尝试徒劳地修改接收者。

以上就是Go - 强制接口仅由方法上带有指针接收器的类型满足?的详细内容,更多请关注php中文网其它相关文章!

golang 中的 nil 接收器

golang 中的 nil 接收器

索引:https://waterflow.link/articles/1666534616841

我们先看一个简单的例子,我们自定义一个错误,用来把多个错误放在一起输出:

type CustomError struct {
	errors []string
}

func (c *CustomError) Add(err string) {
	c.errors = append(c.errors, err)
}

func (c *CustomError) Error() string {
	return strings.Join(c.errors, ";")
}

因为实现了 Error() string 方法,所以它实现了 error 接口。

现在我们要实现一个添加课件的功能,但是添加之前需要验证参数的合法性,所以我们创建了一个 Validate 方法,我们可能会这么写:

package main

import (
	"errors"
	"fmt"
	"strings"
)

type CustomError struct {
	errors []string
}

func (c *CustomError) Add(err error) {
	c.errors = append(c.errors, err.Error())
}

func (c *CustomError) Error() string {
	return strings.Join(c.errors, ";")
}

type Courseware struct {
	Name string
	Code string
}

func (c *Courseware) Validate() error {
	var m *CustomError // 1
	if c.Name == "" { // 2
		m = &CustomError{}
		m.Add(errors.New("课件名不能为空"))
	}
	if c.Code == "" { // 3
		if m == nil {
			m = &CustomError{}
		}
		m.Add(errors.New("课件编号不能为空"))
	}

	return m // 4
}

func main() {
	m := Courseware{
		Name: "多媒体课件",
		Code: "CW330",
	}
	if err := m.Validate(); err != nil {
		fmt.Println("valid err: ", err)
	}
}

看上去好像一点问题都没有:

  1. 定义一个 CustomError 类型的指针
  2. 如果 Name 为空,初始化 m,调用 Add 方法把错误添加到 CustomError.errors
  3. 如果 Code 为空,如果 m 还没有初始化,先初始化,调用 Add 方法把错误添加到 CustomError.errors
  4. 最后返回自定义错误

但是当我们执行上面的代码时,会发现结果并不是我们想要的:

go run 8.go
valid err:  <nil>

我们发现居然走到了打印错误的判断里,但是打印出来的错误居然是一个 nil

在 Go 中,我们必须知道指针接收器可以为 nil。我们看一个简单的例子:

package main

import (
	"fmt"
)

type Demo struct {
}

func (d *Demo) Print() string {
	return "demo"
}

func main() {
	var d *Demo
	fmt.Println(d)
	fmt.Println(d.Print())
}
go run 8.go
<nil>
demo

Demo 被初始化为 nil,但是这段代码可以正常运行。说明 nil 指针也可以作为接收器。

其实上面的 Print 方法等价于:

func Print(d *Demo) string {
	return "demo"
}

因为将 nil 指针传递给函数是有效的。 所以使用 nil 指针作为接收器也是有效的。

我们继续回到上面的自定义错误。

m 被初始化为指针的零值:nil。 如果所有验证都通过,return 语句返回的结果不是 nil,而是一个 nil 指针。 因为 nil 指针是一个有效的接收器,所以将结果转换为 error 接口不会产生 nil 值。

所以我们虽然返回了一个 nil 指针,但是转换为 error 接口时并不是一个 nil 的接口(虽然是 nil 指针,但是是 * CustomError 类型,并实现了 error)。

要解决这个问题,我们只要直接返回 nil 值,不返回 nil 的指针:

package main

import (
	"errors"
	"fmt"
	"strings"
)

type CustomError struct {
	errors []string
}

func (c *CustomError) Add(err error) {
	c.errors = append(c.errors, err.Error())
}

func (c *CustomError) Error() string {
	return strings.Join(c.errors, ";")
}

type Courseware struct {
	Name string
	Code string
}

func (c *Courseware) Validate() error {
	var m *CustomError
	if c.Name == "" {
		m = &CustomError{}
		m.Add(errors.New("课件名不能为空"))
	}
	if c.Code == "" {
		if m == nil {
			m = &CustomError{}
		}
		m.Add(errors.New("课件编号不能为空"))
	}

  // 这里如果m指针为nil,直接返回nil
	if m == nil {
		return nil
	}

	return m
}

func main() {
	m := Courseware{
		Name: "多媒体课件",
		Code: "CW330",
	}

	if err := m.Validate(); err != nil {
		fmt.Println("valid err: ", err)
	}
}

或者我们直接返回 * CustomError 类型的错误:

package main

import (
	"errors"
	"fmt"
	"strings"
)

type CustomError struct {
	errors []string
}

func (c *CustomError) Add(err error) {
	c.errors = append(c.errors, err.Error())
}

func (c *CustomError) Error() string {
	return strings.Join(c.errors, ";")
}

type Courseware struct {
	Name string
	Code string
}

// 返回*CustomError
func (c *Courseware) Validate() *CustomError {
	var m *CustomError
	if c.Name == "" {
		m = &CustomError{}
		m.Add(errors.New("课件名不能为空"))
	}
	if c.Code == "" {
		if m == nil {
			m = &CustomError{}
		}
		m.Add(errors.New("课件编号不能为空"))
	}

	return m
}

func main() {
	m := Courseware{
		Name: "多媒体课件",
		Code: "CW330",
	}

	if err := m.Validate(); err != nil {
		fmt.Println("valid err: ", err)
	}
}

但这并不是可取的,为了扩展我们实现了 error 接口,也需要返回 error 类型的错误。

Golang 接收器是指针还是至类型的区别

Golang 接收器是指针还是至类型的区别

Go语言可以根据下面的函数:
func(a Integer) Less(b Integer) bool

自动生成一个新的Less()方法:
func(a *Integer) Less(b Integer) bool{
return(*a).Less(b)

}

这样,类型*Integer就既存在Less()方法,也存在Add()方法,满足LessAdder接口。而 从另一方面来说,根据 func(a *Integer) Add(b Integer) 这个函数无法自动生成以下这个成员方法: func(a Integer) Add(b Integer) { (&a).Add(b) }

golang-值接收者和指针接收者的区别

golang-值接收者和指针接收者的区别

方法

方法能给用户自定义的类型添加新的行为。它和函数的区别在于方法有一个接收者,给一个函数添加一个接收者,那么它就变成了方法。接收者可以是值接收者,也可以是指针接收者
在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法。

package main
import "fmt"
type Person struct {
	age int
}
func (p Person) AddAge() {
	p.age += 1
}

func (p *Person) GetAge() {
	p.age += 1
}

func main() {
	// p1 是值类型
	p := Person{age: 18}
	// 值类型 调用接收者也是值类型的方法
	p.AddAge()
	fmt.Println(p.age)

	// ----------------------

	// p2 是指针类型  指针类型调用接收者是值类型的方法
	p2 := &Person{age: 100}
	p2.AddAge()
	fmt.Println(p2.age)
	//值类型 调用接收者也是指针类型的方法
	p3 := Person{age: 18}
	p3.GetAge()
	fmt.Println(p3.age)
	// 指针类型 调用收者也是指针类型的方法
	p4 := Person{age: 100}
	p4.GetAge()
	fmt.Println(p4.age)
}
//18
//100
//19
//101
值接收者 指针接收者
值类型调用者 传递一个副本 使用值的引用来调用方法
指针类型调用者 传递一个副本 方法里的操作会影响到调用者,类似于指针传参,拷贝了一份指针

总结:
1.一个结构体的方法的接收者可能是类型值或指针
2.当接受者不是一个指针时,该方法操作对应接受者的值的副本,即使你使用了指针调用函数,但是函数的接受者是值类型,所以函数内部操作还是对副本的操作,而不是指针操作。
3.如果接收者是指针,则调用者修改的是指针指向的值本身。

接口实现

当结构体实现一个接口时,可以在方法中设置一个接收者,比如对于以下接口:

type Inter interface {
    foo()
}

结构体在实现它时,方法的接收者类型可以是:值、指针。比如:

type S struct {}

func (s S) foo() {} // 值类型
func (s *S) foo() {} // 或者指针类型

在使用结构体初始化接口变量时,结构体的类型也可以是:值、指针。比如:

//赋值
var s Inter = S{} // 值类型
s.foo()

var s Inter = &S{} // 指针类型
s.foo()

那么调用接口方法的组合实际有四种情况:
值类型结构体 -> 赋值给接口 -> 调用接收者类型为值类型的结构体方法
指针类型结构体 -> 赋值给接口 -> 调用接收者类型为指针类型的结构体方法
值类型结构体 -> 赋值给接口 -> 调用接收者类型为指针类型的结构体方法(不通过)
指针类型结构体 -> 赋值给接口 -> 调用接收者类型为值类型的结构体方法

结构体类型为值类型、调用了接收者为指针的方法不通过。但是反过来,结构体为指针类型时,却可以调用接收值为值或指针的任何方法。这是为什么呢?

接收者是方法的一个额外的参数,而 Go 在调用函数的时候,参数都是值传递的。将一个指针拷贝,它们还是指向同一个地址,指向一个确定的结构体;将一个值拷贝,它们变成了两个不同的结构体,有着不同的地址。这会导致以下两种情况:

当在一个结构体指针上,通过接口,调用一个接收者为值类型的方法时,Go 首先会创建这个指针的副本,然后将这个指针解引用,再作为接收者参数传递给该方法。这两个指针指向相同的地址,所以它们传递给方法的接收者参数都是相同的。

type Inter interface {
    foo()
}
type S struct {}
func (s S) foo() {} // 接收者为值类型的方法

var a Inter = &S{} // 使用结构体指针初始化一个接口
a.foo() // 调用 foo 方法

// 实际上底层是这样的:
// 首先拷贝 a 的底层值,即 `&S{}`,是一个结构体指针:
var b *S = a.inner_value // a、b 是不同的变量,但是指向同一个结构体
// 然后将 b 解引用,传递给 foo:
foo(*b) // *b 和 *(a.inner_value) 其实都表示同一个结构体

这些规则用来说明是否我们一个类型的值或者指针实现了该接口:

  • 类型 *T 的可调用方法集包含接受者为 *T 或 T 的所有方法集
  • 类型 T 的可调用方法集包含接受者为 T 的所有方法

两者分别在何时使用

如果方法的接收者是值类型,无论调用者是对象还是对象指针,修改的都是对象的副本,不影响调用者;如果方法的接收者是指针类型,则调用者修改的是指针指向的对象本身

使用指针作为方法的接收者的理由:

  • 方法能够修改接收者指向的值。
  • 避免在每次调用方法时复制该值,在值的类型为大型结构体时,这样做会更加高效。
  • 是使用值接收者还是指针接收者,不是由该方法是否修改了调用者(也就是接收者)来决定,而是应该基于该类型的本质。

今天关于指针接收器和值接收器在Golang中是什么意思?golang指针与非指针接收器的讲解已经结束,谢谢您的阅读,如果想了解更多关于Go - 强制接口仅由方法上带有指针接收器的类型满足?、golang 中的 nil 接收器、Golang 接收器是指针还是至类型的区别、golang-值接收者和指针接收者的区别的相关知识,请在本站搜索。

本文标签: