Go語言高階資料型別之指標篇

尹正杰發表於2024-07-06

目錄
  • 一.指標
    • 1.指標概述
    • 2.指標地址和指標型別
    • 3.定義指標變數
    • 4.指標細節
      • 4.1 可以透過改變指向值
      • 4.2 指標變數接收的一定是地址值
      • 4.3 指標的地址不可以不匹配
      • 4.4 基礎資料型別又稱為值型別
    • 5.指標傳值
      • 5.1 案例一
      • 5.2 案例二(注意,陣列也是值型別喲~)
  • 二.new和make
    • 1.new
      • 1.1 new概述
      • 1.2 new案例
    • 2.make
      • 2.1 make概述
      • 2.2 make案例
    • 3.new和make的區別

一.指標

1.指標概述

任何程式資料載入記憶體後,在記憶體都有他們的地址,這就是指標。而為了儲存一個資料在記憶體中的地址,我們就需要指標變數。

Go語言中的指標不能進行偏移和運算,因此我們說Go語言的指標是隻讀的。

Go語言中的指標操作非常簡單,我們只需要記住兩個符號:
	- &:
		用於取地址。
	- *:
		根據地址取值。
		
取地址運算子&和取值運算子*是一對互補運算子,&取出地址,*根據地址取出地址指向的值。

要搞明白Go語言中的指標需要先知道三個概念:
	- 指標地址: 
		&a
	- 指標取值: 
		*&a
	- 指標型別: 
		比如: "*int"

變數、指標地址、指標變數、取地址、取值的相互關係和特性如下:
	- 對變數進行取地址(&)操作,可以獲得這個變數的指標變數。
	- 指標變數的值是指標地址。
	- 對指標變數進行取值(*)操作,可以獲得指標變數指向的原變數的值。

2.指標地址和指標型別

每個變數在執行時都擁有一個地址,這個地址代表變數在記憶體中的位置。

Go語言中使用"&"字元放在變數前面對變數進行"取地址"操作。 

Go語言中的值型別(int、float、bool、string、array、struct)都有對應的指標型別,如:*int、*int64、*string等。


取變數指標的語法如下:
	ptr := &v    // v的型別為T

其中:
	- v:
		代表被取地址的變數,型別為T。
	- ptr:
		用於接收地址的變數,ptr的型別就為"*T",稱做T的"指標型別"。"*"代表指標。

3.定義指標變數

package main

import (
	"fmt"
)

func main() {
	var (
		a int = 10

		// 定義指標變數,*int可以理解為指向int型別的指標。指標本質上指向的是一個記憶體地址
		b *int = &a  // 取變數a的記憶體地址,將指標儲存到b指標中
	)

	fmt.Printf("type of b:%T, value of b: %v\n", b, b)

	// 指標取值(根據指標去記憶體地址取值)
	c := *b
	fmt.Printf("type of c:%T,value of c: %v\n", c, c)
}

4.指標細節

4.1 可以透過改變指向值

package main

import (
	"fmt"
)

func main() {
	var (
		a int  = 100
		b *int = &a
	)

	fmt.Printf("a = %v\n", a)

	// 可以透過改變指向值
	*b = 200
	fmt.Printf("a = %v\n", a)

}
 

4.2 指標變數接收的一定是地址值

package main

import (
	"fmt"
)

func main() {
	var (
		a int  = 100
		// 指標變數接收的一定是地址值
		// b *int = a  // 編譯報錯: "cannot use a (variable of type int) as *int value in variable declaration"
		b *int = &a
	)

	fmt.Printf("b = %v\n", b)

}
 

4.3 指標的地址不可以不匹配

package main

import (
	"fmt"
)

func main() {
	var (
		a int  = 100
		// 指標的地址不可以不匹配
		// b *float64 = &a  // 編譯報錯: cannot use &a (value of type *int) as *float64 value in variable declaration
		b *int = &a
	)

	fmt.Printf("b = %v\n", b)

}
 

4.4 基礎資料型別又稱為值型別

基礎資料型別又稱為值型別,都有對應的指標型別,形式為"*資料型別"。

比如int的對應指標就是"*int",float64對應的指標型別就是"*float64",以此類推。

5.指標傳值

5.1 案例一

package main

import (
	"fmt"
)

func modify1(x int) {
	x = 200
}

func modify2(x *int) {
	*x = 300
}

func main() {
	a := 100
	fmt.Printf("in main: %d\n", a)

	// 傳遞的是值
	modify1(a)
	fmt.Printf("after modify1: %d\n", a)

	// 傳遞的是指標變數
	modify2(&a)
	fmt.Printf("after modify2: %d\n", a)
}

5.2 案例二(注意,陣列也是值型別喲~)

package main

import (
	"fmt"
)

func modifyArray1(x [3]int) {
	x[0] = 200
}

func modifyArray2(x *[3]int) {
	x[0] = 200
}

func main() {

	a := [3]int{1, 2, 3}
	fmt.Printf("in main:  %v\n", a)

	modifyArray1(a)
	fmt.Printf("modifyArray1: %v\n", a)

	modifyArray2(&a)
	fmt.Printf("modifyArray2: %v\n", a)
}

二.new和make

1.new

1.1 new概述

new是一個內建的函式,它的函式簽名如下:
	func new(Type) *Type
其中:
	Type:
		表示型別,new函式只接受一個引數,這個引數是一個型別
	*Type:
		表示型別指標,new函式返回一個指向該型別記憶體地址的指標。


new函式不太常用,使用new函式得到的是一個型別的指標,並且該指標對應的值為該型別的零值。


指標作為引用型別需要初始化後才會擁有記憶體空間,才可以給它賦值。

1.2 new案例

package main

import (
	"fmt"
)

func main() {
	// a是一個int型別的指標,但並沒有記憶體地址,但無法使用,寫法錯誤!
	// var a *int
	// 正確的寫法是,使用new得到一個int型別的指標,此時指標是有記憶體地址的。
	var a = new(int)

	fmt.Printf("記憶體地址: %v, 資料: %v, 型別: %T\n", a, *a, a)
	// 取出a的記憶體地址,並修改其值為100
	*a = 100

	// 對a指標型別進行初始化
	fmt.Printf("記憶體地址: %v, 資料: %v, 型別: %T\n", a, *a, a)

	// 宣告一個"[3]int"陣列型別指標並分配記憶體地址
	var b = new([3]int)
	fmt.Printf("記憶體地址: %v, 資料: %v, 型別: %T\n", b, *b, b)

	// 下面這種兩種寫法都是正確的,但Go編譯器使用了語法糖,讓我們可以有更簡潔的寫法。
	// (*b)[0] = 200
	b[0] = 200

	fmt.Printf("記憶體地址: %v, 資料: %v, 型別: %T\n", b, *b, b)
}

2.make

2.1 make概述

make也是用於記憶體分配的,區別於new,它只用於slice、map以及channel的記憶體建立,而且它返回的型別就是這三個型別本身,而不是他們的指標型別,因為這三種型別就是引用型別,所以就沒有必要返回他們的指標了。


make函式的函式簽名如下:
	func make(t Type, size ...IntegerType) Type
	
make函式是無可替代的,我們在使用slice、map以及channel的時候,都需要使用make進行初始化,然後才可以對它們進行操作。這個我們在上一章中都有說明,關於channel我們會在後續的章節詳細說明。

2.2 make案例

package main

import (
	"fmt"
)

func main() {
	var teacher map[string]int

	// 對引用型別的資料進行初始化操作,用於分配記憶體地址
	teacher = make(map[string]int, 10)

	teacher["尹正傑"] = 20

	fmt.Println(teacher)

}

3.new和make的區別

- 二者都是用來做記憶體分配的。
- make只用於slice、map以及channel的初始化,返回的還是這三個引用型別本身:
	- make只能對內建資料型別進行申請記憶體;
	- 返回是資料值本身;
- 而new用於型別的記憶體分配,返回的是指向型別的指標。
	- new方法主要給struct等非內建資料型別申請空間
	- 返回的是一個指標型別

相關文章