【Golang开发入门】一篇文章弄懂:值类型、值拷贝、指针类型

zz
在这里插入图片描述

  • 博主简介:努力学习的大一在校计算机专业学生,热爱学习和创作。目前在学习和分享:数据结构、Go,Java等相关知识。
  • 博主主页: @是瑶瑶子啦
  • 所属专栏: Go语言核心编程
  • 近期目标:写好专栏的每一篇文章

在这里插入图片描述


在这里插入图片描述

一、前言:(问题引出)

昨天写的【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。然后修改memoHead字段,并将其重新赋值给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. 值类型的运用示例(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是一个值类型,因此在传递参数时会进行值拷贝。这样,我们就可以在不改变原始数据的情况下计算两个点之间的距离。

  1. 值类型的运用示例(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这样的类型来存储结构体指针。

  1. 引用类型的运用示例
// 定义一个切片(Slice)变量
nums := []int{1, 2, 3, 4, 5}

// 定义一个函数,用于对切片进行排序
func sort(slice []int) {
    sort.Ints(slice)
}

// 对切片排序并输出结果
sort(nums)
fmt.Println(nums)

上述代码中,我们定义了一个nums变量来表示一个整数切片,该变量是一个引用类型。我们还定义了一个sort函数来对整数切片进行排序。在对切片排序时,我们将切片作为参数传递给sort函数,由于切片是一个引用类型,因此在传递参数时只是复制切片的指针。这样,我们就可以在不改变原始数据的情况下对切片进行排序,并输出排序后的结果。

总的来说,值类型和引用类型在Go语言中都有其各自的优势和适用场景。需要结合具体的应用场景和需求来选择合适的类型,并注意在使用值类型和引用类型时需要注意其特性和行为。


欢迎在评论区交流和留下你的想法和建议

如果对你有用,还请:💭评论+👍🏻点赞+⭐收藏+➕关注

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是瑶瑶子啦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值