Skip to main content

Pointer ใน GO

Pointer คืออะไร ?

Pointer คือ data type อีก 1 ประเภทที่ใช้สำหรับเก็บ memory address ของตัวแปร ถูกใช้ใช้ใน 2 จุดประสงค์ใหญ่ๆคือ

  1. Mutability = อนุญาตให้สามารถกลับมาแก้ original data ของตัวแปรตัวนั้นได้ (เพื่อให้เป็นการส่งแบบ pass by reference แทน)
  2. 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
}

ผลลัพธ์ go-control-01

Pass by value และ Pass by reference

มาพูดถึงการส่งข้อมูลใน function เพิ่มเติมกัน โดยปกติการส่งข้อมูล (value) เข้า function เข้าไปจะมีวิธีการส่งอยู่ทั้งหมด 2 แบบคือ

  1. 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 นี้ go-control-01

สังเกตว่า

  • value ของ x จะยังคงเป็น 20 เหมือนเดิม (แม้ว่าจะมีการเปลี่ยนแปลงค่าใน function แล้วก็ตาม)
  1. 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 นี้ go-control-01

สังเกตว่าค่า 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)
}