zz
一、前言:(问题引出)
昨天写的【Golang项目实战】手把手教你写一个备忘录程序|附源码——建议收藏
我写第二个版本代码的时候遇到如下问题:
这段代码报错的原因是memos
是一个map类型的变量,而map类型的值不能直接被赋值。在修改map类型变量的值时,需要使用键来访问对应的值,并对值进行修改。
假设memos
是一个map[int]*Memo
类型的变量,其中键是备忘录的编号,值是指向备忘录的指针。在这种情况下,如果要修改某个备忘录的标题,可以使用如下的代码:
memos[id].Title = newMemoTitle
这段代码将会访问memos
中编号为id
的备忘录,并将其标题修改为newMemoTitle
。注意,这里使用了指针类型的值来表示备忘录,因此可以直接访问备忘录的字段。
如果memos
不是指针类型的变量,而是map[int]Memo
类型的变量,那么在修改备忘录的标题时需要先将备忘录取出来,然后再修改标题。例如:
memo := memos[id]
memo.Title = newMemoTitle
memos[id] = memo
这段代码将会访问memos
中编号为id
的备忘录,并将其取出来赋值给一个新的变量memo
。然后修改memo
的标题,并将其重新赋值给memos
中的编号为id
的备忘录。
承接上文,为什么memos是map[int]Memo类型变量需要先将备忘录取出来,而不能memos[id].Head=newMemoHead这样直接赋值
memos
是一个map[int]Memo
类型的变量,其中键是备忘录的编号,值是备忘录的结构体,而结构体是值类型(非指针类型),在Go语言中,值类型的变量在赋值时会进行一次值拷贝,即将变量的值复制一份到新的内存地址中。因此,如果直接对memos[id]
进行赋值,实际上是在对备忘录的副本进行操作,而不是对原备忘录进行操作,这样就无法实现真正的修改。
为了实现真正的修改,需要先将备忘录从memos
中取出来,修改其字段的值,然后再将其放回到memos
中。这样,就可以保证对原备忘录进行操作,而不是对备忘录的副本进行操作。因此,在map[int]Memo
类型的变量中,修改备忘录的字段需要使用如下的代码:
memo := memos[id]
memo.Head = newMemoHead
memos[id] = memo
这段代码将会访问memos
中编号为id
的备忘录,并将其取出来赋值给一个新的变量memo
。然后修改memo
的Head
字段,并将其重新赋值给memos
中的编号为id
的备忘录。这样,就可以实现对原备忘录进行修改。
💡Summary
上面说了这么多呢,其实简单来说,Go语言的结构体是值类型。通过map的键map[id]
,获得到的是原始结构体的值拷贝(一个副本)(如果是指针类型,那么获取的是结构体的指针!!!)值类型的结构体,就是获取到了一个重新开了内存,但存值和原来结构体一样的新结构体。你对它进行操作,根本不会影响原始备忘录。
(感觉熟悉Java的朋友和我一样可能还是不太清楚,可以看下面补充,结构体可以对应到Java中的类,类变量都是指针类型,所以像遇到这种值类型的结构体,我们才会如此不解)
二、不同语言的值类型、指针类型比较
值类型和指针类型在很多编程语言中都有类似的概念和特性,但具体实现和表达方式可能有所不同。
c++:
结构体可以定义为值类型或指针类型,而且可以通过引用或指针来访问结构体的字段。在C++中,值类型的结构体在传递和复制时会进行 值拷贝-,而指针类型的结构体则只是复制指向结构体的指针。这和Go语言中值类型和指针类型的行为类似。
- 值类型结构体和指针类型结构体:
// 值类型结构体
struct Point {
int x;
int y;
};
// 指针类型结构体
struct Rect {
Point* start;
Point* end;
};
- 值类型结构体传参可以用引用来接收(底层还是进行地址传递),不会进行值拷贝,通过形参可以影响实参
#include <iostream>
using namespace std;
struct Point {
int x;
int y;
};
void modifyPoint(Point& p) {
p.x = 3;
p.y = 4;
}
int main() {
Point p1 = { 1, 2 };
modifyPoint(p1);
cout << "p1: (" << p1.x << ", " << p1.y << ")" << endl; // 输出 p1: (3, 4)
return 0;
}
💬”值拷贝”?=crlC+ctrV
值类型的结构体来说,它们的值包含了所有的成员字段,当一个值类型的结构体变量被赋值给另一个变量时,或者作为函数参数传递时,都会进行值拷贝。这意味着在内存中会创建一个新的结构体对象,并将原始结构体对象的值复制到新对象中,两个结构体对象之间互不影响。因此,如果我们修改其中一个值类型的结构体变量的属性值,不会影响到其他的变量。
再通俗来说,值拷贝就是CV。你想一下,你平时从文章CV一段文字到你的word来进行编辑,你在word来进行编辑,会影响你看到的原始那篇文章吗?不会对吧!对,这就是值拷贝!!!
Java:
在Java中,所有的对象都是指针类型,因此对象的操作都是基于指针进行的。在Java中,可以使用关键字new
来创建对象,这会分配一块内存用于存储对象的数据,并返回一个指向该内存区域的指针。在Java中,不能直接访问对象的内存地址,只能使用对象的引用来访问对象的字段和方法。
Python:
在Python中,对象的内存管理是由解释器自动进行的,因此不需要手动进行内存分配和释放。Python中的对象都是指针类型,而且对象的引用计数机制可以自动进行垃圾回收。在Python中,可以使用赋值语句将一个对象的引用复制给另一个变量,这样两个变量都指向同一个对象。但是,如果对其中一个变量进行修改,就会创建一个新的对象,而另一个变量仍然指向原来的对象。
总的来说,值类型和指针类型是编程语言中一种基础概念和特性,不同的语言可能会有不同的实现和表达方式,但其本质是相似的。
Go语言中值类型、指针类型:
在Go语言中,值类型和引用类型都有各自的适用场景和运用方式。以下是一些常见的示例:
- 值类型的运用示例(1)
// 定义一个点(Point)结构体
type Point struct {
X, Y int
}
// 定义一个函数,用于计算两个点之间的距离
func distance(p1, p2 Point) float64 {
dx := p2.X - p1.X
dy := p2.Y - p1.Y
return math.Sqrt(float64(dx*dx + dy*dy))
}
// 创建两个点并计算它们之间的距离
p1 := Point{1, 2}
p2 := Point{4, 6}
d := distance(p1, p2)
fmt.Println(d)
上述代码中,我们定义了一个Point
结构体来表示二维平面上的一个点,该结构体是一个值类型。我们还定义了一个distance
函数来计算两个点之间的距离。在计算距离时,我们将两个点作为参数传递给distance
函数,由于Point
是一个值类型,因此在传递参数时会进行值拷贝。这样,我们就可以在不改变原始数据的情况下计算两个点之间的距离。
- 值类型的运用示例(2)
type Memo struct {
Head string
// other fields
}
var memos map[string]Memo
func main() {
memos = make(map[string]Memo)
memo1 := Memo{Head: "Memo 1"}
memos["1"] = memo1
memo2 := memos["1"]
memo2.Head = "Memo 2"
fmt.Println(memos["1"].Head) // "Memo 1"
}
在上面的代码中,我们首先声明了一个map类型的变量memos,其中键值对的值类型为Memo。然后,我们创建了一个Memo结构体变量memo1,并将其添加到memos中。接着,我们通过memos[“1”]获取到了memo1的拷贝,并将其赋值给了memo2。然后,我们修改了memo2的Head属性,并打印了memos[“1”].Head的值。由于memos[“1”]中存储的是memo1的拷贝,因此memo2的修改并不会影响到memos[“1”],所以最终打印出来的结果是”Memo 1″。
相比之下,如果使用map[string]*Memo这样的类型来存储结构体指针,那么每次在map中存储一个值时,只会存储指向原始结构体的指针。这样,当修改map中的某个元素时,会直接影响原始的结构体变量。例如:
type Memo struct {
Head string
// other fields
}
var memos map[string]*Memo
func main() {
memos = make(map[string]*Memo)
memo1 := Memo{Head: "Memo 1"}
memos["1"] = &memo1
memo2 := memos["1"]
memo2.Head = "Memo 2"
fmt.Println(memos["1"].Head) // "Memo 2"
}
在上面的代码中,我们首先声明了一个map类型的变量memos,其中键值对的值类型为*Memo。然后,我们创建了一个Memo结构体变量memo1,并将其地址添加到memos中。接着,我们通过memos[“1”]获取到了指向memo1的指针,并将其赋值给了memo2。然后,我们修改了memo2的Head属性,并打印了memos[“1”].Head的值。由于memos[“1”]中存储的是memo1的指针,因此memo2的修改直接影响到了memos[“1”],所以最终打印出来的结果是”Memo 2″。
因此,如果希望在map类型的变量中存储结构体,并且希望修改map中的元素会影响到原始的结构体变量,应该使用map[string]*Memo这样的类型来存储结构体指针。
- 引用类型的运用示例
// 定义一个切片(Slice)变量
nums := []int{1, 2, 3, 4, 5}
// 定义一个函数,用于对切片进行排序
func sort(slice []int) {
sort.Ints(slice)
}
// 对切片排序并输出结果
sort(nums)
fmt.Println(nums)
上述代码中,我们定义了一个nums
变量来表示一个整数切片,该变量是一个引用类型。我们还定义了一个sort
函数来对整数切片进行排序。在对切片排序时,我们将切片作为参数传递给sort
函数,由于切片是一个引用类型,因此在传递参数时只是复制切片的指针。这样,我们就可以在不改变原始数据的情况下对切片进行排序,并输出排序后的结果。
总的来说,值类型和引用类型在Go语言中都有其各自的优势和适用场景。需要结合具体的应用场景和需求来选择合适的类型,并注意在使用值类型和引用类型时需要注意其特性和行为。
欢迎在评论区交流和留下你的想法和建议
如果对你有用,还请:💭评论+👍🏻点赞+⭐收藏+➕关注
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/142427.html