为什么通过HTTP传输结构会创建一个副本?

huangapple go评论89阅读模式
英文:

Why does serving a struct via http create a copy?

问题

我注意到在Go语言中创建一个结构体并在其New函数中注册一个http处理程序时出现了一些奇怪的行为。

考虑以下代码:

package main

import (
	"fmt"
	"net/http"
)

type Counter struct {
	name  string
	value int
}

func New(name string) Counter {
	c := Counter{
		name:  name,
		value: 0,
	}

	http.HandleFunc("/", c.serve)
	return c
}

func (c *Counter) inc()             { c.value++ }
func (c *Counter) reset()           { c.value = 0 }
func (c *Counter) nameApp(n string) { c.name += n }
func (c *Counter) print()           { fmt.Printf("[%s]: %d (%p)\n", c.name, c.value, &c) }

func (c *Counter) Reinit(name string, value int) {
	c.name = name
	c.value = value
}

func (c *Counter) serve(w http.ResponseWriter, req *http.Request) {
	c.inc()
	c.nameApp("-foo")
	fmt.Println("Counter served:")
	c.print()

	w.WriteHeader(http.StatusOK)
	w.Write([]byte{})
}

func main() {
	c := New("My New Counter")
	fmt.Println("New Counter:")
	c.print()

	c.Reinit("My reinit Counter", 10)
	fmt.Println("Counter after Reinit() call:")
	c.print()

	http.ListenAndServe("localhost:9000", nil)
}

运行代码后,输出如下:

New Counter:
[My New Counter]: 0 (0xc00012a2a0)
Counter after Reinit() call:
[My reinit Counter]: 10 (0xc00012a2a0)

发送两个请求到服务器后,输出如下:

Counter served:
[My New Counter-foo]: 1 (0xc00012a2c0) // 期望输出为"[My reinit Counter]: 11 (0xc00012a2a0)"
Counter served:
[My New Counter-foo-foo]: 2 (0xc00012a2c0) // 期望输出为"[My reinit Counter]: 12 (0xc00012a2a0)"

为什么结构体的行为不符合我的预期,即使我正确使用了指针接收器?

如何修改我的结构体,使其能够从主程序或其他任何程序中进行修改,并反映在关联的http请求处理程序中?

英文:

I noticed some strange behaviour when I create a struct in go that registers an http-Handler in its New function.

Consider the following code:

package main
import (
"fmt"
"net/http"
)
type Counter struct {
name  string
value int
}
func New(name string) Counter {
c := Counter{
name:  name,
value: 0,
}
http.HandleFunc("/", c.serve)
return c
}
func (c *Counter) inc()             { c.value++ }
func (c *Counter) reset()           { c.value = 0 }
func (c *Counter) nameApp(n string) { c.name += n }
func (c *Counter) print()           { fmt.Printf("[%s]: %d (%p)\n", c.name, c.value, &c) }
func (c *Counter) Reinit(name string, value int) {
c.name = name
c.value = value
}
func (c *Counter) serve(w http.ResponseWriter, req *http.Request) {
c.inc()
c.nameApp("-foo")
fmt.Println("Counter served:")
c.print()
w.WriteHeader(http.StatusOK)
w.Write([]byte{})
}
func main() {
c := New("My New Counter")
fmt.Println("New Counter:")
c.print()
c.Reinit("My reinit Counter", 10)
fmt.Println("Counter after Reinit() call:")
c.print()
http.ListenAndServe("localhost:9000", nil)
}

When running it it creates the following output:

New Counter:
[My New Counter]: 0 (0xc00012a2a0)
Counter after Reinit() call:
[My reinit Counter]: 10 (0xc00012a2a0)

After sending two requests to the server the output is:

Counter served:
[My New Counter-foo]: 1 (0xc00012a2c0) // expected "[My reinit Counter]: 11 (0xc00012a2a0)"
Counter served:
[My New Counter-foo-foo]: 2 (0xc00012a2c0) // expected "[My reinit Counter]: 12 (0xc00012a2a0)"

Why does the struct not behave as I had expected, even though I am properly using pointer receivers?

How can I modify my struct from a main routine or really any other routine to and have these changes being reflected in the associated http request handlers?

答案1

得分: 2

func (c *Counter) print()

中,你正在打印

fmt.Printf("[%s]: %d (%p)\n", c.name, c.value, &c)

值得注意的是,你通过%p打印了&c
c是该方法的指针接收器参数。&c是指向指针接收器的指针。换句话说,&c是指向局部变量的指针,即接收器变量。如果你只想打印调用该方法的Counter的地址,请使用c。例如:

fmt.Printf("[%s]: %d (%p)\n", c.name, c.value, c)
英文:

In

func (c *Counter) print()

You are printing

fmt.Printf("[%s]: %d (%p)\n", c.name, c.value, &c)

Most notably, you are printing &c via %p.
c is the pointer receiver argument to the method. &c is a pointer to the pointer receiver. In other words, &c is a pointer to a local variable, the receiver variable. If you want to print just the address of the Counter which was called on, use the plain c. For example:

fmt.Printf("[%s]: %d (%p)\n", c.name, c.value, c)

答案2

得分: 1

在撰写问题时找到了解决方案:

新函数的返回值需要是一个指针,否则它将返回一个副本。以下更改解决了这个问题:

func New(name string) *Counter { // 添加 *
    // ... 之前的代码
    return &c // 添加 &
}

(但是,我真的不明白的是,为什么初始输出中的地址保持不变!?)

英文:

Found the solution while writing up the question:

The return value of the new function needs to be a pointer, otherwise it will return a copy. The following change solves the problem:

func New(name string) *Counter { // add the *
// ... as before
return &c // add the &
}

(What I don't really get, though, is why the address in initial output stays the same!?)

huangapple
  • 本文由 发表于 2022年7月29日 18:51:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/73165556.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定