寫到第五天覺得這個根本是個考驗我記憶力的比賽和懺悔錄
我記得有次被code review被問說
為什麼要改成不用pointer
我說這個function,不會改變變數
沒必要用pointer
但這樣的說法完整嗎?
你有沒有想過為什麼Golang 預設要用pass by value ?
Golang設計的出發點,為高併發使用和大團隊使用的語言。
預設使用傳值(pass by value)的好處
1.透過預設使用傳值,可以使程式更易於理解,因為所有參數都被視為function內部的拷貝,這減少了副作用的發生。這意味著function內部對參數的修改不會影響到呼叫方的資料,從而減少了不必要的混亂。
(因為語言目的為了高併發,這樣就減少副作用)
2.讓工程師明確地選擇何時傳遞資料的記憶體位址(即指標)。如果你想在function內部修改外部變數的值,可以傳遞變數的指標。這樣可以讓程式碼更具表達性,清楚地表明哪些變數會被function修改,而不僅僅依賴預設行為。
(我覺得這說法非常符合Go的設計理念)
3.在並發環境下,傳值可以防止資料競爭條件的出現。當 Goroutines 在共享資料時,使用傳值可以確保每個 Goroutine 都擁有自己的資料副本,從而避免資料競爭和鎖機制的開銷。
(FYI,Goroutine設計最好是用溝通而非共享)
4.有效控制記憶體的使用。在 Golang 中,當涉及較小資料類型(如整數、boolean等)時,傳值是高效率的,因為這些類型佔用記憶體較少,傳遞它們的副本幾乎沒有效能開銷。當需要傳遞大型資料結構時(如結構體、陣列),可以透過指標來避免複製大量資料,這樣就能控制記憶體和效能的消耗。
回到最初的問題?
雖然在function中,不會改變值
加上參數數據不夠大,不用使用指標來節省記憶體
不過這樣回答就完整了嗎?
前一章講到slice
但從中分割成子slice時
還是會指向本來的slice
因為它的底層是有指標的!!
雖然Golang預設是Pass by value 但其傳遞類型本性不改!
特殊類型的傳遞方式
Slice、Map、Channel、Interface、Function
Slice 的底層結構
type SliceHeader struct {
Data uintptr // 底層陣列的指標
Len int // 長度
Cap int // 容量
}
傳值時:複製了 SliceHeader,但 Data 指向同一個底層陣列。
影響:函式內部修改元素,外部可見;但重新分配底層陣列後,外部不可見。
Map 的底層結構
type hmap struct {
// ... 省略其他欄位
buckets unsafe.Pointer // 哈希桶的指標
// ... 省略其他欄位
}
傳值時:複製了 hmap 結構,但 buckets 指向同一個底層哈希表。
影響:函式內部對 map 的增刪改操作,外部均可見。
Channel 的底層結構
type hchan struct {
// ... 省略其他欄位
buf unsafe.Pointer // 緩衝區的指標
// ... 省略其他欄位
}
傳值時:複製了 hchan 結構,但 buf 指向同一個緩衝區。
影響:多個 Goroutine 可以透過傳遞的 channel 共享資料。
Interface 的底層結構
type iface struct {
tab *itab // 方法表
data unsafe.Pointer // 資料指標
}
傳值時:複製了介面的結構,但 data 指向同一個具體值。
影響:透過介面修改指向的資料,可能影響原始變數。
含指標的結構
傳值時:複製了結構體,但內部的指標欄位指向原始資料。
影響:修改指標欄位指向的資料,外部可見。
回到最初的問題?
雖然在function中,不會改變值
加上參數數據不夠大,不用使用指標來節省記憶體
而且參數不屬於特殊類型會指向原本的資料
不過這樣回答就完整了嗎?
使用指標處理 SQL 中的 NULL 值
當你不需要區分 NULL 和空字串,只關心column是否有值
type User struct {
ID int
Name string
Email *string // nil 表示 NULL,非 nil 表示有值(可能是空字串)
}
func main() {
// 讀取資料
var user User
err := db.QueryRow("SELECT id, name, email FROM users WHERE id = ?", 1).Scan(
&user.ID, &user.Name, &user.Email)
if err != nil {
log.Fatal(err)
}
if user.Email != nil {
fmt.Println("Email:", *user.Email)
} else {
fmt.Println("Email is NULL")
}
}
這個時候也可以用pointer
使用指標的時候
真的要注意最好寫好寫滿測試或者go vet /race指令/staticcheck 等工具做檢查
不然你會常常看到
panic: runtime error: invalid memory address or nil pointer dereference
[signal 0xb code=0x1 addr=0x38 pc=0x26df]