目錄
- 一.指標
- 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.new
一.指標
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等非內建資料型別申請空間
- 返回的是一個指標型別