Yayako's Blog

「Shooting for the stars when I couldn't make a killing.」

仅作java2go的简单记录,有错误烦请提出

合集整理:Java转Go的学习-合集

demo已上传git:https://gitee.com/yayako/go-learning.git

指针 和 make、new分配内存

本科是有学c++和Java两门课的,最终走上了Java的路,一方面是大环境,另一方面就是那时候觉得指针很烦,而Java正好没有指针。没想到转到了go后还是要重新捡起来。

指针

普通的变量对应了自己内存地址,而指针是一种特殊的变量,对应了另一个变量的内存地址。

指针

&与*

谈到指针离不开谈 &* 两个符号,前者用于取地址,而后者则是指针运算符,可以标识指针类型,也可以表示一个指针变量所指向的存储单元

以该结构体为例

1
2
3
4
type Test struct {
a int
b int
}

以下代码,分别以两种方式创建出了普通变量 t1 和指针变量 t2 ,将其分别赋值给 tt1tt2 后再分别改变原来的a值,最后打印出 tt1tt2 的现值查看区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* 普通变量 */
var t1 Test = Test{a: 0, b: 0}
// t1 值:{0 0},类型:main.Test,地址:0xc00000a0c0
fmt.Printf("t1 值:%v,类型:%T,地址:%p\n", t1, t1, &t1)

tt1 := t1
// tt1 值:{0 0},类型:main.Test,地址:0xc00000a0f0
fmt.Printf("tt1 值:%v,类型:%T,地址:%p\n", tt1, tt1, &tt1)

t1.a = 1 // 修改原t1中a的值
fmt.Println(t1) // {1 0}
fmt.Println(tt1) // {0 0} 发现此处tt1的a值并没有改变

/* 指针变量 */
var t2 *Test = &Test{a: 0, b: 0}
// t2 值:&{0 0},类型:*main.Test,地址:0xc000006048,指向地址:0xc00000a140
fmt.Printf("t2 值:%v,类型:%T,地址:%p,指向地址:%p\n", t2, t2, &t2, t2)

tt2 := t2
// tt2 值:&{0 0},类型:*main.Test,地址:0xc000006050,指向地址:0xc00000a140
fmt.Printf("tt2 值:%v,类型:%T,地址:%p,指向地址:%p\n", tt2, tt2, &tt2, tt2)

t2.a = 1 // 修改原t2中a的值
fmt.Println(t2) // &{1 0}
fmt.Println(tt2) // &{1 0} 发现此处tt2的a值也发生了改变

控制台中我们会发现普通变量 t1 赋值给 tt1 后两者在内存中分配的地址并不相同,而指针变量 t2tt2 则还是指向同一个存储单元。

内存分配情况大致如下:

image-20210203182116301

值类型与引用类型

值类型:直接存储数据,在声明时就会默认为其分配内存空间。go中的数组、基本数据类型、结构体都是值类型变量。

引用类型:持有数据的引用,在使用时不仅需要声明,还需要手动为其分配内存空间。go中的slice、map、channel都是引用类型变量。(Java中除了基本数据类型外,所有变量都是引用型变量。

用数组和slice举个与上述相同的例子,会发现效果一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 数组 */
arr1 := [...]int{1, 2, 3}
// arr1 值:[1 2 3],类型:[3]int,地址:0xc000012380
fmt.Printf("arr1 值:%v,类型:%T,地址:%p\n", arr1, arr1, &arr1)
arr2 := arr1
// arr1 值:[1 2 3],类型:[3]int,地址:0xc0000123e0
fmt.Printf("arr1 值:%v,类型:%T,地址:%p\n", arr2, arr2, &arr2)

arr1[0] = 4
fmt.Println("arr1 =", arr1) // [4 2 3]
fmt.Println("arr2 =", arr2) // [1 2 3]


/* 切片 */
slice01 := []int{1, 2, 3}
// slice01 值:[1 2 3],类型:[]int,地址:0xc0000044a0,指向地址:0xc000012480
fmt.Printf("slice01 值:%v,类型:%T,地址:%p,指向地址:%p\n", slice01, slice01, &slice01, slice01)
slice02 := slice01
// slice02 值:[1 2 3],类型:[]int,地址:0xc000004520,指向地址:0xc000012480
fmt.Printf("slice02 值:%v,类型:%T,地址:%p,指向地址:%p\n", slice02, slice02, &slice02, slice02)

slice02[0] = 0
fmt.Println("slice01 =", slice01) // [0 2 3]
fmt.Println("slice02 =", slice02) // [0 2 3]

new与make

func new

1
func new(Type) *Type

内建函数new,分配内存,第一个实参为类型。其返回值为指向该类型的新分配的零值的指针。

使用 new 创建一个指针类型的数组进行上面的实验

1
2
3
4
5
6
t := new(Test)
tt := t
// t 值:&{0 0},类型:*main.Test,地址:0xc000006048,指向的地址:0xc00000a1f0
fmt.Printf("t 值:%v,类型:%T,地址:%p,指向的地址:%p\n", t, t, &t, t)
// tt 值:&{0 0},类型:*main.Test,地址:0xc000006050,指向的地址:0xc00000a1f0
fmt.Printf("tt 值:%v,类型:%T,地址:%p,指向的地址:%p\n", tt, tt, &tt, tt)

func make

1
func make(Type, size IntegerType) Type

内建函数make,分配并初始化一个类型为slice、map、或channel的对象,第一个实参为类型。make的返回类型与其参数相同,而不是指向它的指针。

评论