这些结构体的内部是怎么回事?结构体会被复制吗?

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

What is going on under the hood with these structs? Will struct be copied?

问题

我不理解这段代码中到底发生了什么。

接收函数在原始结构体User上工作(因为使用了指针),所以在函数内部我们改变了原始对象。但是结构体的地址是原始的还是原始的副本呢?

func main() {
	a := Address{"Freedom", "Kyiv"}
	u := User{"Valeriy", "Zalyzhnyi", a}
	fmt.Println(a)
	fmt.Println(u)
	u.updateStreet("Peremohy")

	fmt.Println(a)
	fmt.Println(u)
}

func (u *User) updateStreet(street string) {
	u.address.street = street
}

type User struct {
	firstName string
	lastName  string
	address   Address
}

type Address struct {
	street string
	city   string
}

这是我的输出结果

{Freedom Kyiv}
{Valeriy Zalyzhnyi {Freedom Kyiv}}
{Freedom Kyiv}
{Valeriy Zalyzhnyi {Peremohy Kyiv}}

从这个输出结果中,我理解到u.address被改变了,同时我也看到了u内部的'a'与原始对象不同。
那么在底层和内存中到底发生了什么呢?
根据输出结果,这种行为对我来说完全出乎意料。
我原本期望由于指针的存在,我们在两种情况下('a'和'u')都使用了原始对象。并且在func 'update..'之后,第二次的fmt.Println(a)将会给我们{Peremohy Kyiv},因为第二次的fmt.Println(u)给了我们{Valeriy Zalyzhnyi {Peremohy Kyiv}}。

英文:

I'm not uderstanding what is really going on in this piece of code.

The receiver func works on original struct User (because of pointer), so inside of func we changing the original obj. But will the struct address also be original or it will be copy of original 'a'?

func main() {
	a := Address{"Freedom", "Kyiv"}
	u := User{"Valeriy", "Zalyzhnyi", a}
	fmt.Println(a)
	fmt.Println(u)
	u.updateStreet("Peremohy")

	fmt.Println(a)
	fmt.Println(u)
}

func (u *User) updateStreet(street string) {
	u.address.street = street
}

type User struct {
	firstName string
	lastName  string
	address   Address
}

type Address struct {
	street string
	city   string
}

This is my output

{Freedom Kyiv}
{Valeriy Zalyzhnyi {Freedom Kyiv}}
{Freedom Kyiv}
{Valeriy Zalyzhnyi {Peremohy Kyiv}}

From this I understand that u.address is changed, also I see that this 'a' inside of 'u' is different obj from the original one.
So what exactly is going on under the hood and in memory?
Based on the output this behavior for me is completely unexpected.
I was expecting that because of the pointer we work with the original object in both cases ('a' and 'u'). And the second (after func 'update..') printing of fmt.Println(a) will give us {Peremohy Kyiv} because the second fmt.Println(u) gave us {Valeriy Zalyzhnyi {Peremohy Kyiv}}

答案1

得分: 3

为了理解代码的内部运行机制,将代码的执行过程可视化是很有用的:

a = address{}
u := user{address: a}

可以分解为以下内容:

| 变量值                | 内存地址      |
| a = address{}         | 0x000001     |
| u = user{}            | 0x000002     |
| u.address = a的副本   | 0x000003     |

因此,你为1个user{}实例和2个address{}实例分配了内存。第二个地址实例的值是在创建副本时与第一个地址实例完全相同的。

现在,当你调用updateStreet函数时,它通过指针在u上调用,它不会创建user实例的副本,而是在内存地址0x000002上操作,因此它实际上是在操作同一个a变量。因此,表达式:

u.address.street = "foo"

可以理解为:在内存地址0x000002中的值上,访问名为address的字段,在该字段中,访问street字段并将其赋予新值。让我们将其映射到上面创建的表格中:

  0x000002 -> address(存储在0x000003中)
              |
              --> 将street设置为"foo"

函数返回后,我们仍然有相同的对象,位于内存中的相同位置,但由于我们通过内存地址访问了a的值,updateStreet函数所做的更改已经应用于u的值(因为我们使用了相同的内存地址)。

在对u.address赋值时,变量a复制,因此它的内存地址对于updateStreet函数来说是未知的,也没有传递给该函数,因此保持不变。

英文:

To understand what happens under the hood, it can be useful to visualise what your code does:

a = address{}
u := user{address: a}

breaks down to this:

| variable value        | memory address |
| a = address{}         | 0x000001       |
| u = user{}            | 0x000002       |
| u.address = copy of a | 0x000003       |

So you have allocated memory for 1 instance of user{}, and 2 instances of address{}. The value of the second address instance is an exact copy of the first, at the point when the copy was created.

Now when you call updateStreet, it is invoked on u through a pointer, it doesn't create a copy of the user instance, but rather operates on the memory address 0x000002, so effectively it operates on the same a variable. Therefore the expression:

u.address.street = "foo"

Translates to something like: on the value held in memory address 0x000002, access the field called address, in that field, access the field street and assign it the new value. Let's map this on to the table we have created above:

  0x000002 -> address (which is stored in 0x000003)
              |
              --> set street to "foo"

Once the function returns, we still have the same objects as before, in the same location in memory, but because we've accessed the value of a through its address in memory, the changes made by the updateStreet function have been made to the value of u (because we used the same memory address).

The variable a was copied in the assignment to u.address, so its memory address wasn't known to, or passed on to the updateStreet function, and therefore remains unchanged.

huangapple
  • 本文由 发表于 2023年5月18日 16:37:01
  • 转载请务必保留本文链接:https://go.coder-hub.com/76279080.html
匿名

发表评论

匿名网友

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

确定