Skip to content

พอยน์เตอร์ใน Go

Go ยังคงมีพอยน์เตอร์ ซึ่งรับประกันประสิทธิภาพในระดับหนึ่ง และเพื่อ GC ที่ดีขึ้นและความปลอดภัย จึงจำกัดการใช้พอยน์เตอร์

การสร้าง

สำหรับการดำเนินการพอยน์เตอร์มีตัวดำเนินการที่ใช้บ่อยสองตัว หนึ่งคือตัวดำเนินการหาที่อยู่ & อีกตัวคือตัวดำเนินการ dereference * การหาที่อยู่ของตัวแปรหนึ่งจะส่งคืนพอยน์เตอร์ของประเภทที่ตรงกัน เช่น

go
func main() {
   num := 2
   p := &num
   fmt.Println(p)
}

พอยน์เตอร์เก็บที่อยู่ของตัวแปร num

0xc00001c088

ตัวดำเนินการ dereference มีสองวัตถุประสงค์ ประการแรกคือการเข้าถึงองค์ประกอบที่พอยน์เตอร์ชี้ไป คือการ dereference เช่น

go
func main() {
  num := 2
  p := &num
  rawNum := *p
  fmt.Println(rawNum)
}

p เป็นพอยน์เตอร์ การ dereference พอยน์เตอร์สามารถเข้าถึงองค์ประกอบที่พอยน์เตอร์ชี้ไปได้อีกวัตถุประสงค์หนึ่งคือการประกาศพอยน์เตอร์หนึ่งตัว เช่น

go
func main() {
   var numPtr *int
   fmt.Println(numPtr)
}
<nil>

*int หมายถึงประเภทของตัวแปรนี้เป็นพอยน์เตอร์ประเภท int แต่พอยน์เตอร์ไม่เพียงประกาศเท่านั้น ยังต้องเริ่มต้นด้วย ต้องจัดสรรหน่วยความจำให้มัน มิฉะนั้นจะเป็นพอยน์เตอร์ว่าง ไม่สามารถใช้งานได้ตามปกติ ไม่ก็ใช้ตัวดำเนินการหาที่อยู่เพื่อกำหนดค่าที่อยู่ของตัวแปรอื่นให้กับพอยน์เตอร์นี้ ไม่ก็ใช้ฟังก์ชันในตัว new เพื่อจัดสรรด้วยตนเอง เช่น

go
func main() {
   var numPtr *int
   numPtr = new(int)
   fmt.Println(numPtr)
}

ส่วนใหญ่ใช้ตัวแปรสั้น

go
func main() {
   numPtr := new(int)
   fmt.Println(numPtr)
}

ฟังก์ชัน new มีพารามิเตอร์เดียวคือประเภท และส่งคืนพอยน์เตอร์ของประเภทที่ตรงกัน ฟังก์ชันจะจัดสรรหน่วยความจำให้พอยน์เตอร์นี้ และพอยน์เตอร์ชี้ไปที่ค่าศูนย์ของประเภทที่ตรงกัน เช่น

go
func main() {
   fmt.Println(*new(string))
   fmt.Println(*new(int))
   fmt.Println(*new([5]int))
   fmt.Println(*new([]float64))
}

0
[0 0 0 0 0]
[]

ห้ามการคำนวณพอยน์เตอร์

ใน Go ไม่รองรับการคำนวณพอยน์เตอร์ นั่นคือพอยน์เตอร์ไม่สามารถเลื่อนได้ ดูโค้ด C++ ส่วนนี้ก่อน

cpp
int main() {
    int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
    int *p = &arr[0];
    cout << &arr << endl
         << p << endl
         << p + 1 << endl
         << &arr[1] << endl;
}
0x31d99ff880
0x31d99ff880
0x31d99ff884
0x31d99ff884

จะเห็นว่าที่อยู่ของอาร์เรย์กับที่อยู่ขององค์ประกอบแรกของอาร์เรย์เหมือนกัน และหลังจากบวกหนึ่งกับการดำเนินการพอยน์เตอร์แล้ว มันชี้ไปที่องค์ประกอบที่สองของอาร์เรย์ ใน Go อาร์เรย์ก็เช่นเดียวกัน แต่ความแตกต่างคือพอยน์เตอร์ไม่สามารถเลื่อนได้ เช่น

go
func main() {
   arr := [5]int{0, 1, 2, 3, 4}
   p := &arr
   println(&arr[0])
   println(p)
   // พยายามดำเนินการคำนวณพอยน์เตอร์
   p++
   fmt.Println(p)
}

โปรแกรมแบบนี้จะไม่สามารถผ่านการคอมไพล์ได้ รายงานข้อผิดพลาดดังนี้

main.go:10:2: invalid operation: p++ (non-numeric type *[5]int)

TIP

ไลบรารีมาตรฐาน unsafe มีการดำเนินการมากมายสำหรับการเขียนโปรแกรมระดับต่ำ รวมถึงการคำนวณพอยน์เตอร์ ไปที่ ไลบรารีมาตรฐาน-unsafe เพื่อดูรายละเอียด

new และ make

ในหลายบทก่อนหน้าได้กล่าวถึงฟังก์ชันในตัว new และ make หลายครั้งแล้ว ทั้งสองคล้ายกันบ้าง แต่ก็มีความแตกต่างต่างกัน ทบทวนด้านล่าง

go
func new(Type) *Type
  • ค่าส่งคืนคือพอยน์เตอร์ของประเภท
  • พารามิเตอร์ที่รับคือประเภท
  • ใช้เฉพาะสำหรับการจัดสรรหน่วยความจำให้พอยน์เตอร์
go
func make(t Type, size ...IntegerType) Type
  • ค่าส่งคืนคือค่า ไม่ใช่พอยน์เตอร์
  • พารามิเตอร์ตัวแรกที่รับคือประเภท พารามิเตอร์แบบแปรผันแตกต่างกันไปตามประเภทที่ส่งเข้า
  • ใช้เฉพาะสำหรับการจัดสรรหน่วยความจำให้สไลซ์ แมป แชนเนล

ด้านล่างเป็นบางตัวอย่าง

go
new(int) // พอยน์เตอร์ int
new(string) // พอยน์เตอร์ string
new([]int) // พอยน์เตอร์สไลซ์จำนวนเต็ม
make([]int, 10, 100) // สไลซ์จำนวนเต็มความยาว 10 ความจุ 100
make(map[string]int, 10) // แมปความจุ 10
make(chan int, 10) // แชนเนลขนาดบัฟเฟอร์ 10

Golang by www.golangdev.cn edit