在这里,我们将给大家分享关于指针接收器和值接收器在Golang中是什么意思?的知识,让您更了解golang指针与非指针接收器的本质,同时也会涉及到如何更有效地Go-强制接口仅由方法上带有指针接收器的类
在这里,我们将给大家分享关于指针接收器和值接收器在Golang中是什么意思?的知识,让您更了解golang指针与非指针接收器的本质,同时也会涉及到如何更有效地Go - 强制接口仅由方法上带有指针接收器的类型满足?、golang 中的 nil 接收器、Golang 接收器是指针还是至类型的区别、golang-值接收者和指针接收者的区别的内容。
本文目录一览:- 指针接收器和值接收器在Golang中是什么意思?(golang指针与非指针接收器)
- Go - 强制接口仅由方法上带有指针接收器的类型满足?
- golang 中的 nil 接收器
- Golang 接收器是指针还是至类型的区别
- golang-值接收者和指针接收者的区别
指针接收器和值接收器在Golang中是什么意思?(golang指针与非指针接收器)
我一直在从关于指针接收器的话题中出错,我决定用谷歌搜索术语的含义,并且阅读了有关指针接收器的不同资源和文档。例如:http
:
//golang.org/doc/faq和http://jordanorelli.com/post/32665860244/how-
to-use-interfaces-in-go。
虽然,尽管他们谈论这些术语,但仍未能准确定义它们。不过,从上下文来看,我认为它们之间的区别在于将变量定义为类似*MyStruct
vs的指针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 - 强制接口仅由方法上带有指针接收器的类型满足?
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 接收器
索引: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)
}
}
看上去好像一点问题都没有:
- 定义一个 CustomError 类型的指针
- 如果 Name 为空,初始化 m,调用 Add 方法把错误添加到 CustomError.errors
- 如果 Code 为空,如果 m 还没有初始化,先初始化,调用 Add 方法把错误添加到 CustomError.errors
- 最后返回自定义错误
但是当我们执行上面的代码时,会发现结果并不是我们想要的:
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 接收器是指针还是至类型的区别
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-值接收者和指针接收者的区别
方法
方法能给用户自定义的类型添加新的行为。它和函数的区别在于方法有一个接收者,给一个函数添加一个接收者,那么它就变成了方法。接收者可以是值接收者,也可以是指针接收者。
在调用方法的时候,值类型既可以调用值接收者的方法,也可以调用指针接收者的方法;指针类型既可以调用指针接收者的方法,也可以调用值接收者的方法。
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-值接收者和指针接收者的区别的相关知识,请在本站搜索。
本文标签: