08 เมษายน 2568
การสร้างซอฟต์แวร์ขนาดใหญ่ รวดเร็ว และเชื่อถือได้ มักจะรู้สึกเหมือนกับการจัดการกับความซับซ้อน จะเป็นอย่างไรถ้ามีภาษาที่ออกแบบมาตั้งแต่ต้นเพื่อทำให้สิ่งนี้ง่ายขึ้น โดยมอบความเร็วและการทำงานพร้อมกัน (concurrency) ที่ตรงไปตรงมาโดยไม่ติดขัด? ขอแนะนำ Go (มักเรียกว่า Golang) ภาษาโปรแกรมที่ถูกสร้างขึ้นเพื่อจัดการกับความท้าทายของการพัฒนาซอฟต์แวร์สมัยใหม่โดยตรง โดยเฉพาะอย่างยิ่งในระดับใหญ่ (at scale) Go ให้ความสำคัญกับความเรียบง่าย ประสิทธิภาพ และการเขียนโปรแกรมพร้อมกัน โดยมีเป้าหมายเพื่อทำให้นักพัฒนามีประสิทธิผลสูง บทเรียน Go นี้ทำหน้าที่เป็นจุดเริ่มต้นของคุณ นำทางคุณผ่านแนวคิดพื้นฐานที่จำเป็นในการเรียนรู้การเขียนโปรแกรม Go
Go เกิดขึ้นที่ Google ราวปี 2007 ออกแบบโดยผู้เชี่ยวชาญด้านการเขียนโปรแกรมระบบที่ต้องการรวมเอาแง่มุมที่ดีที่สุดของภาษาที่พวกเขาชื่นชอบเข้าไว้ด้วยกัน พร้อมกับทิ้งความซับซ้อนที่พวกเขาไม่ชอบ (โดยเฉพาะที่พบใน C++) Go ได้รับการประกาศต่อสาธารณะในปี 2009 และเปิดตัวเวอร์ชัน 1.0 ที่เสถียรในปี 2012 และได้รับความนิยมอย่างรวดเร็วในชุมชนนักพัฒนาซอฟต์แวร์
ลักษณะสำคัญที่นิยาม Go:
gofmt
), การจัดการไลบรารีที่ต้องพึ่งพา (go mod
), การทดสอบ (go test
), การสร้าง (go build
) และอื่นๆ ซึ่งช่วยให้กระบวนการพัฒนาราบรื่นขึ้นภาษานี้ยังมีมาสคอตที่เป็นมิตร คือ Gopher ซึ่งออกแบบโดย Renée French ซึ่งกลายเป็นสัญลักษณ์ของชุมชน Go แม้ว่าชื่ออย่างเป็นทางการคือ Go แต่คำว่า “Golang” เกิดขึ้นเนื่องจากโดเมนเว็บไซต์ดั้งเดิม (golang.org
) และยังคงเป็นชื่อเรียกทั่วไป โดยเฉพาะอย่างยิ่งเมื่อค้นหาทางออนไลน์
ก่อนที่จะเขียนโค้ด Go ใดๆ คุณต้องมีคอมไพเลอร์และเครื่องมือของ Go เยี่ยมชมเว็บไซต์อย่างเป็นทางการของ Go ที่ go.dev และปฏิบัติตามคำแนะนำในการติดตั้งที่ตรงไปตรงมาสำหรับระบบปฏิบัติการของคุณ (Windows, macOS, Linux) ตัวติดตั้งจะตั้งค่าคำสั่งที่จำเป็น เช่น go
มาสร้างโปรแกรมแรกตามธรรมเนียมกัน สร้างไฟล์ชื่อ hello.go
และพิมพ์หรือวางโค้ดต่อไปนี้:
package main
import "fmt"
// นี่คือฟังก์ชัน main ที่การทำงานจะเริ่มต้นขึ้น
func main() {
// Println พิมพ์ข้อความหนึ่งบรรทัดไปยังคอนโซล
fmt.Println("Hello, Gopher!")
}
มาดูรายละเอียดของตัวอย่างโค้ด Go ง่ายๆ นี้:
package main
: ทุกโปรแกรม Go เริ่มต้นด้วยการประกาศแพ็กเกจ แพ็กเกจ main
เป็นแพ็กเกจพิเศษ หมายความว่าแพ็กเกจนี้ควรคอมไพล์เป็นโปรแกรมที่สามารถเรียกใช้งานได้ (executable)import "fmt"
: บรรทัดนี้นำเข้าแพ็กเกจ fmt
ซึ่งเป็นส่วนหนึ่งของไลบรารีมาตรฐานของ Go แพ็กเกจ fmt
มีฟังก์ชันสำหรับการนำเข้าและส่งออกข้อมูลที่มีการจัดรูปแบบ (formatted I/O) เช่น การพิมพ์ข้อความไปยังคอนโซลfunc main() { ... }
: ส่วนนี้กำหนดฟังก์ชัน main
การทำงานของโปรแกรม Go ที่เรียกใช้งานได้จะเริ่มต้นในฟังก์ชัน main
ของแพ็กเกจ main
เสมอfmt.Println("Hello, Gopher!")
: ส่วนนี้เรียกใช้ฟังก์ชัน Println
จากแพ็กเกจ fmt
ที่นำเข้ามา Println
(Print Line) จะส่งออกสตริงข้อความ “Hello, Gopher!” ไปยังคอนโซล ตามด้วยอักขระขึ้นบรรทัดใหม่ในการรันโปรแกรมนี้ เปิดเทอร์มินัลหรือ command prompt ของคุณ ไปยังไดเรกทอรีที่คุณบันทึก hello.go
และรันคำสั่ง:
go run hello.go
คุณควรเห็นผลลัพธ์ต่อไปนี้ปรากฏบนคอนโซลของคุณ:
Hello, Gopher!
ยินดีด้วย! คุณเพิ่งรันโปรแกรม Go แรกของคุณสำเร็จ
เมื่อโปรแกรมแรกของคุณทำงานได้สำเร็จแล้ว มาสำรวจส่วนประกอบพื้นฐานของภาษา Go กัน ส่วนนี้ทำหน้าที่เป็นบทเรียน Go สำหรับผู้เริ่มต้น
ตัวแปรใช้เพื่อเก็บข้อมูลที่สามารถเปลี่ยนแปลงได้ระหว่างการทำงานของโปรแกรม ใน Go คุณต้องประกาศตัวแปรก่อนใช้งาน ซึ่งช่วยให้คอมไพเลอร์มั่นใจในความปลอดภัยของชนิดข้อมูล
การใช้ var
: คำหลัก var
เป็นวิธีมาตรฐานในการประกาศตัวแปรหนึ่งตัวหรือมากกว่า คุณสามารถระบุชนิดข้อมูลอย่างชัดเจนหลังชื่อตัวแปร
package main
import "fmt"
func main() {
var greeting string = "Welcome to Go!" // ประกาศตัวแปรสตริง
var score int = 100 // ประกาศตัวแปรจำนวนเต็ม
var pi float64 = 3.14159 // ประกาศตัวแปรทศนิยม 64 บิต
var isActive bool = true // ประกาศตัวแปรบูลีน
fmt.Println(greeting)
fmt.Println("Initial Score:", score)
fmt.Println("Pi approx:", pi)
fmt.Println("Active Status:", isActive)
}
การประกาศตัวแปรแบบสั้น :=
: ภายในฟังก์ชัน Go มีไวยากรณ์แบบย่อ :=
สำหรับการประกาศ และ กำหนดค่าเริ่มต้นให้กับตัวแปรพร้อมกัน Go จะอนุมานชนิดข้อมูลของตัวแปรโดยอัตโนมัติจากค่าที่กำหนดทางด้านขวา
package main
import "fmt"
func main() {
userName := "Gopher123" // Go อนุมานว่า 'userName' เป็นสตริง
level := 5 // Go อนุมานว่า 'level' เป็น int
progress := 0.75 // Go อนุมานว่า 'progress' เป็น float64
fmt.Println("Username:", userName)
fmt.Println("Level:", level)
fmt.Println("Progress:", progress)
}
หมายเหตุสำคัญ: ไวยากรณ์ :=
สามารถใช้ได้ เฉพาะ ภายในฟังก์ชันเท่านั้น สำหรับตัวแปรที่ประกาศในระดับแพ็กเกจ (นอกฟังก์ชันใดๆ) คุณต้องใช้คำหลัก var
ค่าศูนย์ (Zero Values): หากคุณประกาศตัวแปรโดยใช้ var
โดยไม่ได้กำหนดค่าเริ่มต้นอย่างชัดเจน Go จะกำหนด ค่าศูนย์ ให้โดยอัตโนมัติ ค่าศูนย์ขึ้นอยู่กับชนิดข้อมูล:
0
สำหรับชนิดข้อมูลตัวเลขทั้งหมด (int, float, ฯลฯ)false
สำหรับชนิดข้อมูลบูลีน (bool
)""
(สตริงว่าง) สำหรับชนิดข้อมูล string
nil
สำหรับพอยเตอร์ (pointer), อินเทอร์เฟซ (interface), แมพ (map), สไลซ์ (slice), แชนเนล (channel) และชนิดข้อมูลฟังก์ชันที่ยังไม่ได้กำหนดค่าpackage main
import "fmt"
func main() {
var count int
var message string
var enabled bool
var userScore *int // ชนิดข้อมูลพอยเตอร์
var task func() // ชนิดข้อมูลฟังก์ชัน
fmt.Println("Zero Int:", count) // ผลลัพธ์: Zero Int: 0
fmt.Println("Zero String:", message) // ผลลัพธ์: Zero String:
fmt.Println("Zero Bool:", enabled) // ผลลัพธ์: Zero Bool: false
fmt.Println("Zero Pointer:", userScore) // ผลลัพธ์: Zero Pointer: <nil>
fmt.Println("Zero Function:", task) // ผลลัพธ์: Zero Function: <nil>
}
Go มีชนิดข้อมูลพื้นฐานในตัวหลายชนิด:
int
, int8
, int16
, int32
, int64
, uint
, uint8
, ฯลฯ): แทนจำนวนเต็ม int
และ uint
ขึ้นอยู่กับแพลตฟอร์ม (โดยปกติคือ 32 หรือ 64 บิต) ใช้ขนาดเฉพาะเมื่อจำเป็น (เช่น สำหรับรูปแบบข้อมูลไบนารี หรือการปรับปรุงประสิทธิภาพ) uint8
เป็นชื่อแฝงสำหรับ byte
float32
, float64
): แทนตัวเลขที่มีจุดทศนิยม float64
เป็นค่าเริ่มต้นและโดยทั่วไปนิยมใช้เพื่อความแม่นยำที่ดีกว่าbool
): แทนค่าความจริง คือ true
หรือ false
string
): แทนลำดับของอักขระ เข้ารหัสเป็น UTF-8 สตริงใน Go ไม่สามารถเปลี่ยนแปลงได้ (immutable) – เมื่อสร้างขึ้นแล้ว เนื้อหาของมันไม่สามารถเปลี่ยนแปลงได้โดยตรง การดำเนินการที่ดูเหมือนจะแก้ไขสตริง ที่จริงแล้วเป็นการสร้างสตริงใหม่ขึ้นมานี่คือตัวอย่าง Go ที่ใช้ชนิดข้อมูลพื้นฐาน:
package main
import "fmt"
func main() {
item := "Laptop" // string
quantity := 2 // int
price := 1250.75 // float64 (อนุมาน)
inStock := true // bool
// Go ต้องการการแปลงชนิดข้อมูลอย่างชัดเจนระหว่างชนิดข้อมูลตัวเลขที่แตกต่างกัน
totalCost := float64(quantity) * price // แปลง int 'quantity' เป็น float64 สำหรับการคูณ
fmt.Println("Item:", item)
fmt.Println("Quantity:", quantity)
fmt.Println("Unit Price:", price)
fmt.Println("In Stock:", inStock)
fmt.Println("Total Cost:", totalCost)
}
ตัวอย่างนี้เน้นการประกาศตัวแปรโดยใช้การอนุมานชนิดข้อมูลและความจำเป็นในการแปลงชนิดข้อมูลอย่างชัดเจนเมื่อดำเนินการทางคณิตศาสตร์กับชนิดข้อมูลตัวเลขที่แตกต่างกัน
ค่าคงที่ใช้ผูกชื่อเข้ากับค่า คล้ายกับตัวแปร แต่ค่าของมันจะถูกกำหนดตายตัว ณ เวลาคอมไพล์และไม่สามารถเปลี่ยนแปลงได้ระหว่างการทำงานของโปรแกรม ประกาศโดยใช้คำหลัก const
package main
import "fmt"
const AppVersion = "1.0.2" // ค่าคงที่สตริง
const MaxConnections = 1000 // ค่าคงที่จำนวนเต็ม
const Pi = 3.14159 // ค่าคงที่ทศนิยม
func main() {
fmt.Println("Application Version:", AppVersion)
fmt.Println("Maximum Connections Allowed:", MaxConnections)
fmt.Println("The value of Pi:", Pi)
}
Go ยังมีคำหลักพิเศษ iota
ซึ่งช่วยลดความซับซ้อนในการกำหนดค่าคงที่จำนวนเต็มที่เพิ่มขึ้นทีละน้อย มักใช้สำหรับการสร้างชนิดข้อมูลแบบแจกแจง (enumerated types หรือ enums) iota
เริ่มต้นที่ 0 ภายในบล็อก const
และเพิ่มขึ้นทีละหนึ่งสำหรับการประกาศค่าคงที่แต่ละครั้งในบล็อกนั้น
package main
import "fmt"
// กำหนดชนิดข้อมูล LogLevel เองโดยอิงจาก int
type LogLevel int
const (
Debug LogLevel = iota // 0
Info // 1 (iota เพิ่มค่า)
Warning // 2
Error // 3
)
func main() {
currentLevel := Info
fmt.Println("Current Log Level:", currentLevel) // ผลลัพธ์: Current Log Level: 1
fmt.Println("Error Level:", Error) // ผลลัพธ์: Error Level: 3
}
คำสั่งควบคุมกำหนดลำดับการทำงานของคำสั่งโค้ด
if / else if / else
: ทำงานบล็อกของโค้ดตามเงื่อนไข โดยพิจารณาจากนิพจน์บูลีน วงเล็บ ()
รอบเงื่อนไข ไม่ ใช้ใน Go แต่วงเล็บปีกกา {}
จำเป็นต้องมีเสมอ แม้แต่สำหรับบล็อกที่มีคำสั่งเดียว
package main
import "fmt"
func main() {
temperature := 25
if temperature > 30 {
fmt.Println("อากาศค่อนข้างร้อน")
} else if temperature < 10 {
fmt.Println("อากาศค่อนข้างหนาว")
} else {
fmt.Println("อุณหภูมิกำลังดี") // ข้อความนี้จะถูกพิมพ์
}
// คำสั่งสั้นๆ สามารถอยู่ก่อนเงื่อนไขได้; ตัวแปรที่ประกาศ
// ตรงนั้นจะมีขอบเขตอยู่ภายในบล็อก if/else
if limit := 100; temperature < limit {
fmt.Printf("อุณหภูมิ %d ต่ำกว่าขีดจำกัด %d\n", temperature, limit)
} else {
fmt.Printf("อุณหภูมิ %d ไม่ได้ต่ำกว่าขีดจำกัด %d\n", temperature, limit)
}
}
for
: Go มีโครงสร้างการวนซ้ำเพียงแบบเดียวคือ for
loop ที่ใช้งานได้หลากหลาย สามารถใช้ได้หลายวิธีที่คุ้นเคยจากภาษาอื่น:
for
loop แบบคลาสสิก (init; condition; post):
for i := 0; i < 5; i++ {
fmt.Println("การวนซ้ำ:", i)
}
while
loop):
sum := 1
for sum < 100 { // วนซ้ำตราบเท่าที่ sum น้อยกว่า 100
sum += sum
}
fmt.Println("ผลรวมสุดท้าย:", sum) // ผลลัพธ์: ผลรวมสุดท้าย: 128
break
หรือ return
เพื่อออก):
count := 0
for {
fmt.Println("กำลังวนซ้ำ...")
count++
if count > 3 {
break // ออกจาก loop
}
}
for...range
: วนซ้ำผ่านองค์ประกอบในโครงสร้างข้อมูล เช่น สไลซ์, อาร์เรย์, แมพ, สตริง และ แชนเนล มันให้ดัชนี/คีย์ และค่าสำหรับแต่ละองค์ประกอบ
colors := []string{"Red", "Green", "Blue"}
// รับทั้งดัชนีและค่า
for index, color := range colors {
fmt.Printf("ดัชนี: %d, สี: %s\n", index, color)
}
// ถ้าต้องการแค่ค่า ใช้ตัวระบุว่าง _ เพื่อละเว้นดัชนี
fmt.Println("สี:")
for _, color := range colors {
fmt.Println("- ", color)
}
// วนซ้ำผ่านอักขระ (rune) ในสตริง
for i, r := range "Go!" {
fmt.Printf("ดัชนี %d, Rune %c\n", i, r)
}
switch
: คำสั่งเงื่อนไขหลายทางเลือก ที่เป็นทางเลือกที่สะอาดกว่าการใช้ if-else if
ที่ยาวเหยียด switch
ของ Go มีความยืดหยุ่นมากกว่าในภาษาตระกูล C หลายภาษา:
break
)switch
สามารถใช้ได้โดยไม่มีนิพจน์ (เปรียบเทียบ true
กับนิพจน์ของ case)package main
import (
"fmt"
"time"
)
func main() {
day := time.Now().Weekday()
fmt.Println("วันนี้คือ:", day) // ตัวอย่าง: วันนี้คือ: Tuesday
switch day {
case time.Saturday, time.Sunday: // หลายค่าสำหรับ case เดียว
fmt.Println("เป็นวันหยุดสุดสัปดาห์!")
case time.Monday:
fmt.Println("เริ่มต้นสัปดาห์ทำงาน")
default: // case เริ่มต้น (optional)
fmt.Println("เป็นวันธรรมดา")
}
// Switch โดยไม่มีนิพจน์ ทำงานเหมือน if/else if ที่สะอาด
hour := time.Now().Hour()
switch { // เปรียบเทียบกับ 'true' โดยปริยาย
case hour < 12:
fmt.Println("สวัสดีตอนเช้า!")
case hour < 17:
fmt.Println("สวัสดีตอนบ่าย!")
default:
fmt.Println("สวัสดีตอนเย็น!")
}
}
Go ให้การสนับสนุนในตัวสำหรับโครงสร้างข้อมูลที่จำเป็นหลายอย่าง
อาร์เรย์ใน Go มีขนาดคงที่ซึ่งกำหนด ณ เวลาประกาศ ขนาดเป็นส่วนหนึ่งของชนิดข้อมูลของอาร์เรย์ ([3]int
เป็นชนิดข้อมูลที่แตกต่างจาก [4]int
)
package main
import "fmt"
func main() {
// ประกาศอาร์เรย์ของจำนวนเต็ม 3 ตัว กำหนดค่าเริ่มต้นเป็นค่าศูนย์ (0s)
var numbers [3]int
numbers[0] = 10
numbers[1] = 20
// numbers[2] ยังคงเป็น 0 (ค่าศูนย์)
fmt.Println("Numbers:", numbers) // ผลลัพธ์: Numbers: [10 20 0]
fmt.Println("Length:", len(numbers)) // ผลลัพธ์: Length: 3
// ประกาศและกำหนดค่าเริ่มต้นให้อาร์เรย์ในบรรทัดเดียว
primes := [5]int{2, 3, 5, 7, 11}
fmt.Println("Primes:", primes) // ผลลัพธ์: Primes: [2 3 5 7 11]
// ให้คอมไพเลอร์นับจำนวนองค์ประกอบโดยใช้ ...
vowels := [...]string{"a", "e", "i", "o", "u"}
fmt.Println("Vowels:", vowels, "Length:", len(vowels)) // ผลลัพธ์: Vowels: [a e i o u] Length: 5
}
แม้ว่าอาร์เรย์จะมีประโยชน์ (เช่น เมื่อขนาดคงที่และทราบแน่นอน) แต่ สไลซ์ (slices) ถูกใช้บ่อยกว่ามากใน Go เนื่องจากความยืดหยุ่น
สไลซ์เป็นโครงสร้างข้อมูลหลักสำหรับลำดับใน Go ให้ส่วนต่อประสาน (interface) ที่ทรงพลัง ยืดหยุ่น และสะดวกกว่าอาร์เรย์ สไลซ์มีขนาดแบบไดนามิก เป็นมุมมองที่เปลี่ยนแปลงได้ (mutable) ไปยังอาร์เรย์ที่อยู่เบื้องหลัง
package main
import "fmt"
func main() {
// สร้างสไลซ์ของสตริงโดยใช้ make(type, length, capacity)
// Capacity เป็นทางเลือก; หากละไว้ จะมีค่าเท่ากับ length โดยปริยาย
// Length: จำนวนองค์ประกอบที่สไลซ์มีอยู่ปัจจุบัน
// Capacity: จำนวนองค์ประกอบในอาร์เรย์เบื้องหลัง (เริ่มจากองค์ประกอบแรกของสไลซ์)
names := make([]string, 2, 5) // Length 2, Capacity 5
names[0] = "Alice"
names[1] = "Bob"
fmt.Println("Initial Names:", names, "Len:", len(names), "Cap:", cap(names)) // ผลลัพธ์: Initial Names: [Alice Bob] Len: 2 Cap: 5
// Append เพิ่มองค์ประกอบต่อท้าย ถ้า length เกิน capacity,
// อาร์เรย์เบื้องหลังใหม่ที่ใหญ่กว่าจะถูกจัดสรร และสไลซ์จะชี้ไปที่มัน
names = append(names, "Charlie")
names = append(names, "David", "Eve") // สามารถ append หลายองค์ประกอบได้
fmt.Println("Appended Names:", names, "Len:", len(names), "Cap:", cap(names)) // ผลลัพธ์: Appended Names: [Alice Bob Charlie David Eve] Len: 5 Cap: 5 (หรืออาจใหญ่กว่านี้หากมีการจัดสรรใหม่)
// Slice literal (สร้างสไลซ์และอาร์เรย์เบื้องหลัง)
scores := []int{95, 88, 72, 100}
fmt.Println("Scores:", scores) // ผลลัพธ์: Scores: [95 88 72 100]
// การทำ Slicing กับ slice: สร้างส่วนหัวของสไลซ์ใหม่ที่อ้างอิงถึงอาร์เรย์เบื้องหลัง *เดียวกัน*
// slice[low:high] - รวมองค์ประกอบที่ดัชนี low, ไม่รวมองค์ประกอบที่ดัชนี high
topScores := scores[1:3] // องค์ประกอบที่ดัชนี 1 และ 2 (ค่า: 88, 72)
fmt.Println("Top Scores:", topScores) // ผลลัพธ์: Top Scores: [88 72]
// การแก้ไขสไลซ์ย่อยมีผลต่อสไลซ์เดิม (และอาร์เรย์เบื้องหลัง)
topScores[0] = 90
fmt.Println("Modified Scores:", scores) // ผลลัพธ์: Modified Scores: [95 90 72 100]
// การละขอบเขตล่าง (low bound) จะมีค่าเริ่มต้นเป็น 0, การละขอบเขตบน (high bound) จะมีค่าเริ่มต้นเป็น length
firstTwo := scores[:2]
lastTwo := scores[2:]
fmt.Println("First Two:", firstTwo) // ผลลัพธ์: First Two: [95 90]
fmt.Println("Last Two:", lastTwo) // ผลลัพธ์: Last Two: [72 100]
}
การดำเนินการสำคัญของสไลซ์ ได้แก่ len()
(ความยาวปัจจุบัน), cap()
(ความจุปัจจุบัน), append()
(การเพิ่มองค์ประกอบ) และการทำ slicing โดยใช้ไวยากรณ์ [low:high]
แมพคือการ υλοποίηση (implementation) ในตัวของ Go สำหรับตารางแฮช (hash tables) หรือพจนานุกรม (dictionaries) ใช้เก็บคอลเลกชันของคู่คีย์-ค่าที่ไม่เรียงลำดับ โดยที่คีย์ทั้งหมดต้องเป็นชนิดข้อมูลเดียวกัน และค่าทั้งหมดต้องเป็นชนิดข้อมูลเดียวกัน
package main
import "fmt"
func main() {
// สร้างแมพเปล่าที่มีคีย์เป็นสตริงและค่าเป็น int โดยใช้ make
ages := make(map[string]int)
// กำหนดคู่คีย์-ค่า
ages["Alice"] = 30
ages["Bob"] = 25
ages["Charlie"] = 35
fmt.Println("Ages map:", ages) // ผลลัพธ์: Ages map: map[Alice:30 Bob:25 Charlie:35] (ลำดับไม่รับประกัน)
// รับค่าโดยใช้คีย์
aliceAge := ages["Alice"]
fmt.Println("Alice's Age:", aliceAge) // ผลลัพธ์: Alice's Age: 30
// การรับค่าสำหรับคีย์ที่ไม่มีอยู่ จะคืนค่าศูนย์สำหรับชนิดข้อมูลของค่า (0 สำหรับ int)
davidAge := ages["David"]
fmt.Println("David's Age:", davidAge) // ผลลัพธ์: David's Age: 0
// ลบคู่คีย์-ค่า
delete(ages, "Bob")
fmt.Println("After Deleting Bob:", ages) // ผลลัพธ์: After Deleting Bob: map[Alice:30 Charlie:35]
// ตรวจสอบว่ามีคีย์อยู่หรือไม่โดยใช้รูปแบบการกำหนดค่าสองค่า
// เมื่อเข้าถึงคีย์ของแมพ คุณสามารถรับค่าบูลีนตัวที่สองเป็นทางเลือกได้:
// 1. ค่า (หรือค่าศูนย์หากไม่มีคีย์)
// 2. ค่าบูลีน: true ถ้ามีคีย์อยู่, false ถ้าไม่มี
val, exists := ages["Bob"] // ใช้ตัวระบุว่าง _ หากไม่ต้องการค่า (เช่น _, exists := ...)
fmt.Printf("Does Bob exist? %t, Value: %d\n", exists, val) // ผลลัพธ์: Does Bob exist? false, Value: 0
charlieAge, charlieExists := ages["Charlie"]
fmt.Printf("Does Charlie exist? %t, Age: %d\n", charlieExists, charlieAge) // ผลลัพธ์: Does Charlie exist? true, Age: 35
// Map literal สำหรับการประกาศและกำหนดค่าเริ่มต้นให้กับแมพ
capitals := map[string]string{
"France": "Paris",
"Japan": "Tokyo",
"USA": "Washington D.C.",
}
fmt.Println("Capitals:", capitals)
}
ฟังก์ชันเป็นส่วนประกอบพื้นฐานสำหรับจัดระเบียบโค้ดเป็นหน่วยที่นำกลับมาใช้ใหม่ได้ ประกาศโดยใช้คำหลัก func
package main
import (
"fmt"
"errors" // แพ็กเกจไลบรารีมาตรฐานสำหรับสร้างค่าข้อผิดพลาด
)
// ฟังก์ชันง่ายๆ ที่รับพารามิเตอร์ int สองตัวและคืนค่าผลรวมเป็น int
// ชนิดข้อมูลของพารามิเตอร์ตามหลังชื่อ: func funcName(param1 type1, param2 type2) returnType { ... }
func add(x int, y int) int {
return x + y
}
// หากพารามิเตอร์ที่อยู่ติดกันมีชนิดข้อมูลเดียวกัน คุณสามารถละเว้นชนิดข้อมูล
// จากทั้งหมด ยกเว้นตัวสุดท้าย
func multiply(x, y int) int {
return x * y
}
// ฟังก์ชัน Go สามารถคืนค่าได้หลายค่า นี่เป็นรูปแบบปกติสำหรับการคืนค่า
// ผลลัพธ์และสถานะข้อผิดพลาดพร้อมกัน
func divide(numerator float64, denominator float64) (float64, error) {
if denominator == 0 {
// สร้างและคืนค่าข้อผิดพลาดใหม่หากตัวหารเป็นศูนย์
return 0, errors.New("ไม่อนุญาตให้หารด้วยศูนย์")
}
// คืนค่าผลลัพธ์ที่คำนวณได้และ 'nil' สำหรับข้อผิดพลาดหากสำเร็จ
// 'nil' เป็นค่าศูนย์สำหรับชนิดข้อมูล error (และอื่นๆ เช่น พอยเตอร์, สไลซ์, แมพ)
return numerator / denominator, nil
}
func main() {
sum := add(15, 7)
fmt.Println("Sum:", sum) // ผลลัพธ์: Sum: 22
product := multiply(6, 7)
fmt.Println("Product:", product) // ผลลัพธ์: Product: 42
// เรียกฟังก์ชันที่คืนค่าหลายค่า
result, err := divide(10.0, 2.0)
// ตรวจสอบค่าข้อผิดพลาดทันทีเสมอ
if err != nil {
fmt.Println("Error:", err)
} else {
fmt.Println("Division Result:", result) // ผลลัพธ์: Division Result: 5
}
// เรียกอีกครั้งด้วยอินพุตที่ไม่ถูกต้อง
result2, err2 := divide(5.0, 0.0)
if err2 != nil {
fmt.Println("Error:", err2) // ผลลัพธ์: Error: ไม่อนุญาตให้หารด้วยศูนย์
} else {
fmt.Println("Division Result 2:", result2)
}
}
ความสามารถของฟังก์ชัน Go ในการคืนค่าหลายค่ามีความสำคัญอย่างยิ่งสำหรับกลไกการจัดการข้อผิดพลาดที่ชัดเจน
โค้ด Go ถูกจัดระเบียบเป็นแพ็กเกจ แพ็กเกจคือคอลเลกชันของไฟล์ซอร์สโค้ด (.go
files) ที่อยู่ในไดเรกทอรีเดียวซึ่งคอมไพล์เข้าด้วยกัน แพ็กเกจส่งเสริมการนำโค้ดกลับมาใช้ใหม่และการแบ่งส่วน (modularity)
package packageName
ไฟล์ในไดเรกทอรีเดียวกันต้องอยู่ในแพ็กเกจเดียวกัน แพ็กเกจ main
เป็นแพ็กเกจพิเศษ หมายถึงโปรแกรมที่เรียกใช้งานได้import
เพื่อเข้าถึงโค้ดที่กำหนดในแพ็กเกจอื่น แพ็กเกจไลบรารีมาตรฐานจะถูกนำเข้าโดยใช้ชื่อย่อ (เช่น "fmt"
, "math"
, "os"
) แพ็กเกจภายนอกมักใช้พาธตาม URL ของที่เก็บซอร์สโค้ด (เช่น "github.com/gin-gonic/gin"
)
import (
"fmt" // ไลบรารีมาตรฐาน
"math/rand" // แพ็กเกจย่อยของ math
"os"
// myExtPkg "github.com/someuser/externalpackage" // สามารถตั้งชื่อแฝงให้การนำเข้าได้
)
go.mod
ในไดเรกทอรีรากของโปรเจกต์ คำสั่งหลัก ได้แก่:
go mod init <module_path>
: เริ่มต้นโมดูลใหม่ (สร้าง go.mod
)go get <package_path>
: เพิ่มหรืออัปเดตการพึ่งพาgo mod tidy
: ลบการพึ่งพาที่ไม่ได้ใช้และเพิ่มการพึ่งพาที่ขาดหายไปตามการนำเข้าโค้ดการทำงานพร้อมกัน (Concurrency) เกี่ยวข้องกับการจัดการงานหลายอย่างที่ดูเหมือนทำงานในเวลาเดียวกัน Go มีคุณสมบัติในตัวที่ทรงพลังแต่เรียบง่ายสำหรับการทำงานพร้อมกัน โดยได้รับแรงบันดาลใจจาก Communicating Sequential Processes (CSP)
Goroutines (กอรูทีน): goroutine คือฟังก์ชันที่ทำงานอย่างอิสระ เปิดใช้งานและจัดการโดย Go runtime คิดซะว่าเป็นเธรด (thread) ที่เบามาก คุณเริ่ม goroutine เพียงแค่เติมคำหลัก go
หน้าการเรียกฟังก์ชันหรือเมธอด
Channels (แชนเนล): แชนเนลเป็นท่อส่งข้อมูลที่มีชนิดข้อมูลกำหนด ซึ่งคุณสามารถส่งและรับค่าระหว่าง goroutine ทำให้สามารถสื่อสารและซิงโครไนซ์ได้
ch := make(chan Type)
(เช่น make(chan string)
)ch <- value
variable := <-ch
(การดำเนินการนี้จะบล็อกจนกว่าจะมีการส่งค่า)นี่คือตัวอย่าง Go พื้นฐานที่แสดง goroutine และ channel:
package main
import (
"fmt"
"time"
)
// ฟังก์ชันนี้จะทำงานเป็น goroutine
// รับข้อความและ channel เพื่อส่งข้อความกลับไป
func displayMessage(msg string, messages chan string) {
fmt.Println("Goroutine กำลังทำงาน...")
time.Sleep(1 * time.Second) // จำลองการทำงานบางอย่าง
messages <- msg // ส่งข้อความเข้าไปใน channel
fmt.Println("Goroutine เสร็จสิ้น")
}
func main() {
// สร้าง channel ที่ขนส่งค่าสตริง
// นี่คือ unbuffered channel หมายความว่าการดำเนินการส่ง/รับจะบล็อก
// จนกว่าอีกฝั่งจะพร้อม
messageChannel := make(chan string)
// เริ่มฟังก์ชัน displayMessage เป็น goroutine
// คำหลัก 'go' ทำให้การเรียกนี้ไม่บล็อก; main ทำงานต่อไปทันที
go displayMessage("Ping!", messageChannel)
fmt.Println("ฟังก์ชัน Main กำลังรอข้อความ...")
// รับข้อความจาก channel
// การดำเนินการนี้จะบล็อกฟังก์ชัน main จนกว่าจะมีข้อความถูกส่ง
// เข้ามาใน messageChannel โดย goroutine
receivedMsg := <-messageChannel
fmt.Println("ฟังก์ชัน Main ได้รับ:", receivedMsg) // ผลลัพธ์ (หลังจาก ~1 วินาที): ฟังก์ชัน Main ได้รับ: Ping!
// อนุญาตให้คำสั่งพิมพ์สุดท้ายของ goroutine แสดงผลก่อนที่ main จะจบการทำงาน
time.Sleep(50 * time.Millisecond)
}
ตัวอย่างง่ายๆ นี้แสดงให้เห็นถึงการเปิดใช้งานงานที่ทำงานพร้อมกันและการรับผลลัพธ์อย่างปลอดภัยผ่าน channel โมเดลการทำงานพร้อมกันของ Go เป็นหัวข้อที่ลึกซึ้ง ซึ่งรวมถึง buffered channels, คำสั่ง select
ที่ทรงพลังสำหรับการจัดการหลาย channel และกลไกการซิงโครไนซ์ในแพ็กเกจ sync
Go มีแนวทางในการจัดการข้อผิดพลาดที่แตกต่างจากภาษาที่ใช้ exception ข้อผิดพลาดถือเป็นค่าปกติ ฟังก์ชันที่อาจล้มเหลวได้ โดยทั่วไปจะคืนค่าชนิดข้อมูลอินเทอร์เฟซ error
เป็นค่าคืนค่าสุดท้าย
error
มีเมธอดเดียว: Error() string
error
ที่เป็น nil
หมายถึงความสำเร็จerror
ที่ไม่เป็น nil
หมายถึงความล้มเหลว และค่าเองมักจะมีรายละเอียดเกี่ยวกับข้อผิดพลาดerror
ที่ส่งคืนทันทีหลังจากเรียกฟังก์ชันpackage main
import (
"fmt"
"os"
)
func main() {
// พยายามเปิดไฟล์ที่น่าจะไม่มีอยู่จริง
file, err := os.Open("a_surely_non_existent_file.txt")
// การตรวจสอบข้อผิดพลาดตามแบบฉบับ: ตรวจสอบว่า err ไม่ใช่ nil
if err != nil {
fmt.Println("ร้ายแรง: เกิดข้อผิดพลาดในการเปิดไฟล์:", err)
// จัดการข้อผิดพลาดอย่างเหมาะสม ที่นี่เราแค่จบการทำงาน
// ในแอปพลิเคชันจริง คุณอาจบันทึกข้อผิดพลาด, คืนค่าจาก
// ฟังก์ชันปัจจุบัน หรือลองใช้วิธีสำรอง
return // ออกจากฟังก์ชัน main
}
// หาก err เป็น nil แสดงว่าฟังก์ชันสำเร็จ
// ตอนนี้เราสามารถใช้ตัวแปร 'file' ได้อย่างปลอดภัย
fmt.Println("เปิดไฟล์สำเร็จ!") // ข้อความนี้จะไม่ถูกพิมพ์ในสถานการณ์ข้อผิดพลาดนี้
// การปิดทรัพยากรเช่นไฟล์เป็นสิ่งสำคัญ
// 'defer' กำหนดเวลาการเรียกฟังก์ชัน (file.Close()) ให้ทำงาน
// ก่อนที่ฟังก์ชันที่ล้อมรอบ (main) จะคืนค่า
defer file.Close()
// ... ดำเนินการอ่านหรือเขียนไฟล์ต่อไป ...
fmt.Println("กำลังดำเนินการกับไฟล์...")
}
การตรวจสอบ if err != nil
ที่ชัดเจนนี้ทำให้ลำดับการควบคุมชัดเจนมาก และกระตุ้นให้นักพัฒนาพิจารณาและจัดการกับความล้มเหลวที่อาจเกิดขึ้นอย่างจริงจัง คำสั่ง defer
มักใช้ควบคู่ไปกับการตรวจสอบข้อผิดพลาดเพื่อให้แน่ใจว่าทรัพยากรได้รับการทำความสะอาดอย่างน่าเชื่อถือ
จุดแข็งที่สำคัญของ Go คือเครื่องมือที่ยอดเยี่ยมและทำงานร่วมกันได้ดี ซึ่งรวมอยู่ในชุดการแจกจ่ายมาตรฐาน:
go run <filename.go>
: คอมไพล์และรันไฟล์ซอร์สโค้ด Go ไฟล์เดียวหรือแพ็กเกจ main โดยตรง มีประโยชน์สำหรับการทดสอบอย่างรวดเร็วgo build
: คอมไพล์แพ็กเกจ Go และส่วนที่ต้องพึ่งพา โดยค่าเริ่มต้น จะสร้างไฟล์ที่เรียกใช้งานได้หากแพ็กเกจเป็น main
gofmt
: จัดรูปแบบซอร์สโค้ด Go โดยอัตโนมัติตามแนวทางสไตล์อย่างเป็นทางการของ Go ทำให้มั่นใจในความสอดคล้องกันระหว่างโปรเจกต์และนักพัฒนา ใช้ gofmt -w .
เพื่อจัดรูปแบบไฟล์ Go ทั้งหมดในไดเรกทอรีปัจจุบันและไดเรกทอรีย่อยgo test
: รันการทดสอบหน่วย (unit tests) และการทดสอบประสิทธิภาพ (benchmarks) การทดสอบอยู่ในไฟล์ _test.go
go mod
: เครื่องมือ Go modules สำหรับจัดการการพึ่งพา (เช่น go mod init
, go mod tidy
, go mod download
)go get <package_path>
: เพิ่มการพึ่งพาใหม่ไปยังโมดูลปัจจุบันของคุณหรืออัปเดตการพึ่งพาที่มีอยู่go vet
: เครื่องมือวิเคราะห์สแตติก (static analysis) ที่ตรวจสอบซอร์สโค้ด Go เพื่อหาโครงสร้างที่น่าสงสัยและข้อผิดพลาดที่อาจเกิดขึ้นซึ่งคอมไพเลอร์อาจตรวจไม่พบgo doc <package> [symbol]
: แสดงเอกสารประกอบสำหรับแพ็กเกจหรือสัญลักษณ์เฉพาะเครื่องมือที่ผสานรวมเหล่านี้ช่วยลดความซับซ้อนของงานพัฒนาทั่วไป เช่น การสร้าง การทดสอบ การจัดรูปแบบ และการจัดการการพึ่งพาได้อย่างมาก
Go นำเสนอข้อเสนอที่น่าสนใจสำหรับการพัฒนาซอฟต์แวร์สมัยใหม่: ภาษาที่สมดุลระหว่างความเรียบง่าย ประสิทธิภาพ และคุณสมบัติที่ทรงพลัง โดยเฉพาะอย่างยิ่งสำหรับการสร้างระบบที่ทำงานพร้อมกัน บริการเครือข่าย และแอปพลิเคชันขนาดใหญ่ ไวยากรณ์ที่สะอาดตา การกำหนดชนิดข้อมูลแบบคงที่ที่แข็งแกร่ง การจัดการหน่วยความจำอัตโนมัติผ่าน garbage collection กลไกการทำงานพร้อมกันในตัว ไลบรารีมาตรฐานที่ครอบคลุม และเครื่องมือที่ยอดเยี่ยม ล้วนมีส่วนช่วยให้วงจรการพัฒนาเร็วขึ้น การบำรุงรักษาง่ายขึ้น และซอฟต์แวร์ที่เชื่อถือได้มากขึ้น ทำให้เป็นตัวเลือกที่แข็งแกร่งไม่เพียงแต่สำหรับโปรเจกต์ใหม่ (greenfield) เท่านั้น แต่ยังรวมถึงการย้ายโค้ด (porting code) หรือการปรับปรุงระบบที่มีอยู่ให้ทันสมัย ซึ่งประสิทธิภาพ การทำงานพร้อมกัน และความสามารถในการบำรุงรักษาเป็นเป้าหมายสำคัญ