Pointer ใน GO
Pointer คืออะไร ?
Pointer คือ data type อีก 1 ประเภทที่ใช้สำหรับเก็บ memory address ของตัวแปร ถูกใช้ใช้ใน 2 จุดประสงค์ใหญ่ๆคือ
- Mutability = อนุญาตให้สามารถกลับมาแก้ original data ของตัวแปรตัวนั้นได้ (เพื่อให้เป็นการส่งแบบ pass by reference แทน)
- Efficient large structs = สามารถทำให้ส่งข้อมูลขนาดใหญ่ต่อไปได้ (โดยการส่ง address แทน value ของตัวแปรแทน) เช่น
- การส่ง struct ขนาดใหญ่เข้าไป
- การส่ง config ขนาดใหญ่เข้าไป (เช่น config database, connection ต่างๆ)
มาดู code ตัวอย่างแบบง่ายกันก่อน
package main
import "fmt"
func main() {
// Declare an integer variable
x := 10
// Declare a pointer to an integer and assign it the address of x
var p *int = &x
// Print the value of x and the value at the pointer p
fmt.Println("Value of x:", x) // Output: Value of x: 10
fmt.Println("Value at p:", *p) // Output: Value at p: 10
// Modify the value at the pointer p
*p = 20
// x is modified since p points to x
fmt.Println("New value of x:", x) // Output: New value of x: 20
}
ผลลัพธ์
Pass by value และ Pass by reference
มาพูดถึงการส่งข้อมูลใน function เพิ่มเติมกัน โดยปกติการส่งข้อมูล (value) เข้า function เข้าไปจะมีวิธีการส่งอยู่ทั้งหมด 2 แบบคือ
- Pass by value = การส่ง copy data เข้าไปใน function
- ในภาษา go เกือบทุก data type เป็น default การส่งเป็น pass by value อยู่แล้ว (เช่น การส่ง int ,float, struct เข้า function)
package main
import "fmt"
func changeValue(val int) {
val = 50
}
func main() {
x := 20
changeValue(x)
fmt.Println(x) // Output: 20 (x is unchanged)
}
ผลลัพธ์ของ code นี้
สังเกตว่า
- value ของ x จะยังคงเป็น 20 เหมือนเดิม (แม้ว่าจะมีการเปลี่ยนแปลงค่าใน function แล้วก็ตาม)
- Pass by reference = การส่งตัวแปร reference ของตัว data เข้าไป
- ในภาษา go การส่ง pass by reference สามารถเกิดขึ้นได้จากการส่งตัวแปร Pointer เข้าไป (เป็นเหมือนอ้างอิงไปยัง address ของตัวแปร) เพื่อให้สามารถอ้างอิงไปยังตัวแปรโดยตรง (แทนที่จะเป็นการ copy ค่าไป)
- data type ที่มีการส่ง "เสมือน" pass by reference คือ Slices, Maps, Channel
- ไอเดียของ 3 data type นี้คือ จะทำการ copy reference ของตัวแปรออกมาเป็น pointer และทุกครั้งที่มีการแก้ไข value ใน 3 data type นี้ = ก็จะสามารถ modifications ตัวแปรเหล่านี้ออกมาได้
** ตามทฤษฎี ของภาษา Go ไม่มี pass by reference แต่ไอเดียนี้จะเสมือนว่าเป็นการส่ง pass by reference เฉยๆนะ
package main
import "fmt"
func changeValue(ptr *int) {
*ptr = 50
}
func main() {
x := 20
changeValue(&x)
fmt.Println(x) // Output: 50 (x is changed)
}
ผลลัพธ์ของ code นี้
สังเกตว่าค่า x ก็จะสามารถเปลี่ยนแปลงไปได้
นอกเหนือจากการใช้กับ data type ทั่วไปแล้ว ยังสามารถใช้กับ struct ได้ด้วย
type Person struct {
Name string
}
func changeName(p Person) {
p.Name = "Alice"
}
func main() {
person := Person{Name: "Bob"}
changeName(person)
fmt.Println(person.Name) // Output: Bob (person.Name is unchanged)
}
Example use case ของ pointer
Use case 1: modified ข้อมูลต้นฉบับผ่าน Address
- ใช้กับพวก struct ที่อาจจะเป็นข้อมูลขนาดใหญ่ในอนาคตได้
package main
import "fmt"
type Employee struct {
Name string
Salary int
}
// Function to give a raise to an employee
func giveRaise(e *Employee, raise int) {
e.Salary += raise
}
func main() {
emp := Employee{Name: "John Doe", Salary: 50000}
giveRaise(&emp, 5000)
fmt.Println("After raise:", emp)
}
Use case 2: ใช้ implement data structure แบบอื่นใน Algorithm
- เช่น Linked List
package main
import "fmt"
type ListNode struct {
Value int
Next *ListNode
}
// Function to add a node to the front of the list
func prepend(head **ListNode, value int) {
newNode := ListNode{Value: value, Next: *head}
*head = &newNode
}
func main() {
var head *ListNode
prepend(&head, 10)
prepend(&head, 20)
current := head
for current != nil {
fmt.Println(current.Value)
current = current.Next
}
}
Use case 3: ดึงพวก config มาใช้
package main
import "fmt"
// Config represents the application configuration
type Config struct {
LogLevel string
Port int
}
// UpdateConfig modifies the provided configuration
func UpdateConfig(c *Config, logLevel string, port int) {
c.LogLevel = logLevel
c.Port = port
}
func main() {
// Initial configuration
appConfig := &Config{
LogLevel: "info",
Port: 8080,
}
fmt.Println("Initial Config:", appConfig)
// Update configuration
UpdateConfig(appConfig, "debug", 9000)
fmt.Println("Updated Config:", appConfig)
}