สอนภาษา Go ตั้งแต่ต้น พร้อมตัวอย่าง 25 เรื่อง
Go (Golang) เป็นภาษาที่ Google พัฒนาขึ้น เน้นความเรียบง่าย คอมไพล์เร็ว และรองรับการทำงานพร้อมกัน (concurrency) ได้ในตัว จึงนิยมใช้กับเซิร์ฟเวอร์ เครื่องมือ และระบบคลาวด์ บทความนี้มีตัวอย่างโค้ด 25 ตัวอย่างที่รันจริงด้วย Go 1.27 และแสดงผลลัพธ์จริงของแต่ละตัวอย่าง เรียงจากพื้นฐานไปจนถึงการทำงานพร้อมกัน เว็บ และ Generics
สิ่งที่ต้องเตรียม
ติดตั้ง Go จาก go.dev/dl แล้วตรวจด้วย `go version` จากนั้นสร้างไฟล์ `.go` และรันด้วย `go run ชื่อไฟล์.go`
ภาพประกอบ
ตัวอย่างที่ 1: สวัสดีชาวโลก
โปรแกรม Go ที่ง่ายที่สุด ทุกโปรแกรมเริ่มจากแพ็กเกจ main และฟังก์ชัน main
package main
import "fmt"
func main() {
fmt.Println("สวัสดี Go")
}
**ผลลัพธ์ที่ได้จากการรันจริง**
สวัสดี Go
ตัวอย่างที่ 2: ตัวแปรและค่าคงที่
`:=` ประกาศและกำหนดค่าพร้อมกัน `var` ประกาศได้ชัดเจนกว่า และ `const` ใช้กับค่าที่ไม่เปลี่ยน
package main
import "fmt"
const schoolName = "โรงเรียนตัวอย่าง"
func main() {
name := "สมชาย"
var age int = 20
fmt.Println(schoolName, name, age)
}
**ผลลัพธ์ที่ได้จากการรันจริง**
โรงเรียนตัวอย่าง สมชาย 20
ตัวอย่างที่ 3: ค่าเริ่มต้นของตัวแปร
ตัวแปรที่ประกาศโดยไม่กำหนดค่าจะได้ค่าศูนย์ของชนิดนั้น เช่น 0, "" และ false
package main
import "fmt"
func main() {
var n int
var s string
var b bool
var f float64
fmt.Printf("int=%d string=%q bool=%t float=%.1f\n", n, s, b, f)
}
**ผลลัพธ์ที่ได้จากการรันจริง**
int=0 string="" bool=false float=0.0
ตัวอย่างที่ 4: เงื่อนไข if / else
การตัดสินใจด้วย if และ else if โดยไม่ต้องใส่วงเล็บรอบเงื่อนไข
package main
import "fmt"
func main() {
score := 72
if score >= 80 {
fmt.Println("A")
} else if score >= 70 {
fmt.Println("B")
} else {
fmt.Println("C")
}
}
**ผลลัพธ์ที่ได้จากการรันจริง**
B
ตัวอย่างที่ 5: ลูป for
Go มีลูปเพียงแบบเดียวคือ for ซึ่งทำได้ทั้งแบบนับค่า และแบบเหมือน while
package main
import "fmt"
func main() {
for i := 1; i <= 3; i++ {
fmt.Println("นับ", i)
}
n := 0
for n < 2 {
n++
}
fmt.Println("n =", n)
}
**ผลลัพธ์ที่ได้จากการรันจริง**
นับ 1
นับ 2
นับ 3
n = 2
ตัวอย่างที่ 6: switch
switch เปรียบเทียบค่าหลายกรณี และไม่ต้องใส่ break เพราะไม่ตกลงไปกรณีถัดไปเอง
package main
import "fmt"
func main() {
day := "เสาร์"
switch day {
case "เสาร์", "อาทิตย์":
fmt.Println("วันหยุด")
default:
fmt.Println("วันทำงาน")
}
}
**ผลลัพธ์ที่ได้จากการรันจริง**
วันหยุด
ตัวอย่างที่ 7: อาร์เรย์และสไลซ์
อาร์เรย์มีขนาดคงที่ ส่วนสไลซ์ขยายได้ด้วย append และตัดช่วงด้วย s[a:b]
package main
import "fmt"
func main() {
arr := [3]int{1, 2, 3}
s := []int{10, 20}
s = append(s, 30, 40)
fmt.Println("array:", arr, "len:", len(arr))
fmt.Println("slice:", s, "len:", len(s), "cap:", cap(s) >= 4)
fmt.Println("ตัดช่วง:", s[1:3])
}
**ผลลัพธ์ที่ได้จากการรันจริง**
array: [1 2 3] len: 3
slice: [10 20 30 40] len: 4 cap: true
ตัดช่วง: [20 30]
ตัวอย่างที่ 8: แมป (map)
แมปเก็บคู่คีย์กับค่า ใช้รูปแบบ `v, ok :=` เพื่อตรวจว่าคีย์มีอยู่หรือไม่
package main
import "fmt"
func main() {
scores := map[string]int{"สมชาย": 85, "สมหญิง": 72}
scores["สมศักดิ์"] = 45
if v, ok := scores["สมชาย"]; ok {
fmt.Println("คะแนน:", v)
}
delete(scores, "สมหญิง")
fmt.Println("จำนวน:", len(scores))
}
**ผลลัพธ์ที่ได้จากการรันจริง**
คะแนน: 85
จำนวน: 2
ตัวอย่างที่ 9: ฟังก์ชันหลายค่าคืน
ฟังก์ชันคืนได้หลายค่า และนิยมคืน error เป็นค่าสุดท้ายเพื่อจัดการข้อผิดพลาด
package main
import (
"errors"
"fmt"
)
func divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("หารด้วยศูนย์ไม่ได้")
}
return a / b, nil
}
func main() {
q, err := divide(10, 2)
fmt.Println("ผล:", q, "error:", err)
_, err = divide(1, 0)
fmt.Println("error:", err)
}
**ผลลัพธ์ที่ได้จากการรันจริง**
ผล: 5 error: <nil>
error: หารด้วยศูนย์ไม่ได้
ตัวอย่างที่ 10: คลอเชอร์ (closure)
ฟังก์ชันที่จำค่าตัวแปรจากภายนอกไว้ได้ ใช้สร้างตัวนับหรือสถานะส่วนตัว
package main
import "fmt"
func counter() func() int {
count := 0
return func() int {
count++
return count
}
}
func main() {
next := counter()
fmt.Println(next(), next(), next())
}
**ผลลัพธ์ที่ได้จากการรันจริง**
1 2 3
ตัวอย่างที่ 11: โครงสร้าง (struct) และเมธอด
struct รวมข้อมูลหลายอย่างเข้าด้วยกัน และผูกเมธอดไว้กับชนิดนั้น
package main
import "fmt"
type Student struct {
Name string
Score int
}
func (s Student) Grade() string {
if s.Score >= 80 {
return "A"
}
return "B"
}
func main() {
s := Student{Name: "สมชาย", Score: 85}
fmt.Println(s.Name, s.Grade())
}
**ผลลัพธ์ที่ได้จากการรันจริง**
สมชาย A
ตัวอย่างที่ 12: อินเทอร์เฟซ (interface)
อินเทอร์เฟซกำหนดชุดเมธอดที่ต้องมี ชนิดใดมีเมธอดครบจะเป็นไปตามอินเทอร์เฟซนั้นโดยอัตโนมัติ
package main
import "fmt"
type Shape interface {
Area() float64
}
type Rect struct{ W, H float64 }
func (r Rect) Area() float64 { return r.W * r.H }
func main() {
var s Shape = Rect{W: 3, H: 4}
fmt.Printf("พื้นที่ = %.1f\n", s.Area())
}
**ผลลัพธ์ที่ได้จากการรันจริง**
พื้นที่ = 12.0
ตัวอย่างที่ 13: พอยน์เตอร์
&x ให้ที่อยู่ของตัวแปร และ *p ใช้อ่านหรือแก้ค่าผ่านที่อยู่นั้น ทำให้ฟังก์ชันแก้ค่าภายนอกได้
package main
import "fmt"
func increment(n *int) {
*n++
}
func main() {
x := 5
increment(&x)
fmt.Println("x =", x)
}
**ผลลัพธ์ที่ได้จากการรันจริง**
x = 6
ตัวอย่างที่ 14: จัดการข้อผิดพลาด (error)
สร้าง error ด้วย fmt.Errorf และ %w แล้วตรวจด้วย errors.Is เพื่อดูว่าเป็นข้อผิดพลาดชนิดเดียวกันหรือไม่
package main
import (
"errors"
"fmt"
)
var ErrNotFound = errors.New("ไม่พบข้อมูล")
func find(id int) error {
if id != 1 {
return fmt.Errorf("รหัส %d: %w", id, ErrNotFound)
}
return nil
}
func main() {
err := find(2)
fmt.Println(err)
fmt.Println("ตรงกับ ErrNotFound:", errors.Is(err, ErrNotFound))
}
**ผลลัพธ์ที่ได้จากการรันจริง**
รหัส 2: ไม่พบข้อมูล
ตรงกับ ErrNotFound: true
ตัวอย่างที่ 15: defer
defer เลื่อนการทำงานไปจนฟังก์ชันจบ เหมาะกับการปิดไฟล์หรือคืนทรัพยากร
package main
import "fmt"
func main() {
defer fmt.Println("ทำงานสุดท้าย")
fmt.Println("เริ่มต้น")
fmt.Println("ประมวลผล")
}
**ผลลัพธ์ที่ได้จากการรันจริง**
เริ่มต้น
ประมวลผล
ทำงานสุดท้าย
ตัวอย่างที่ 16: panic และ recover
panic หยุดการทำงาน และ recover กู้คืนได้เฉพาะภายใน defer เพื่อไม่ให้โปรแกรมล่มทั้งหมด
package main
import "fmt"
func safeDivide(a, b int) (result int, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("กู้คืนจาก panic: %v", r)
}
}()
return a / b, nil
}
func main() {
fmt.Println(safeDivide(10, 2))
fmt.Println(safeDivide(1, 0))
}
**ผลลัพธ์ที่ได้จากการรันจริง**
5 <nil>
0 กู้คืนจาก panic: runtime error: integer divide by zero
ตัวอย่างที่ 17: โกรูทีน (goroutine)
คำสั่ง go เริ่มงานให้ทำพร้อมกัน และ sync.WaitGroup รอให้ทุกงานเสร็จ
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go func(n int) {
defer wg.Done()
_ = n * n
}(i)
}
wg.Wait()
fmt.Println("ทุกงานเสร็จแล้ว")
}
**ผลลัพธ์ที่ได้จากการรันจริง**
ทุกงานเสร็จแล้ว
ตัวอย่างที่ 18: แชนแนล (channel)
แชนแนลส่งค่าระหว่างโกรูทีน ตัวอย่างนี้ส่งค่าจาก goroutine ไปยัง main แล้วปิดแชนแนล
package main
import "fmt"
func main() {
ch := make(chan int)
go func() {
for i := 1; i <= 3; i++ {
ch <- i * 10
}
close(ch)
}()
for v := range ch {
fmt.Println("ได้รับ", v)
}
}
**ผลลัพธ์ที่ได้จากการรันจริง**
ได้รับ 10
ได้รับ 20
ได้รับ 30
ตัวอย่างที่ 19: select และการหมดเวลา
select รอหลายแชนแนลพร้อมกัน ใช้ time.After เพื่อกำหนดเวลาสูงสุดในการรอ
package main
import (
"fmt"
"time"
)
func main() {
result := make(chan string)
go func() {
time.Sleep(200 * time.Millisecond)
result <- "เสร็จ"
}()
select {
case msg := <-result:
fmt.Println(msg)
case <-time.After(50 * time.Millisecond):
fmt.Println("หมดเวลา")
}
}
**ผลลัพธ์ที่ได้จากการรันจริง**
หมดเวลา
ตัวอย่างที่ 20: Mutex ป้องกันการแย่งข้อมูล
sync.Mutex ทำให้ goroutine หลายตัวแก้ตัวแปรเดียวกันได้โดยไม่ชนกัน ผลรวมที่ได้จึงถูกต้องเสมอ
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.Mutex
var wg sync.WaitGroup
total := 0
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
mu.Lock()
total++
mu.Unlock()
}()
}
wg.Wait()
fmt.Println("รวม =", total)
}
**ผลลัพธ์ที่ได้จากการรันจริง**
รวม = 100
ตัวอย่างที่ 21: แพ็กเกจ strings และ rune
strings ให้ฟังก์ชันจัดการข้อความ และ len นับไบต์ ส่วนการนับตัวอักษรไทยต้องแปลงเป็น []rune
package main
import (
"fmt"
"strings"
)
func main() {
s := "Go เขียนง่าย"
fmt.Println(strings.ToUpper("go"), strings.Contains(s, "เขียน"))
fmt.Println(strings.Split("a,b,c", ","), len("สวัสดี"))
fmt.Println([]rune("สวัสดี")[0] == 'ส')
}
**ผลลัพธ์ที่ได้จากการรันจริง**
GO true
[a b c] 18
true
ตัวอย่างที่ 22: อ่านและเขียนไฟล์
os.WriteFile และ os.ReadFile เขียนและอ่านไฟล์ทั้งไฟล์ในคำสั่งเดียว
package main
import (
"fmt"
"os"
)
func main() {
if err := os.WriteFile("note.txt", []byte("บันทึกจาก Go\n"), 0644); err != nil {
fmt.Println("เขียนไม่ได้:", err)
return
}
data, err := os.ReadFile("note.txt")
if err != nil {
fmt.Println("อ่านไม่ได้:", err)
return
}
fmt.Print("อ่านได้: ", string(data))
}
**ผลลัพธ์ที่ได้จากการรันจริง**
อ่านได้: บันทึกจาก Go
ตัวอย่างที่ 23: JSON
encoding/json แปลง struct เป็น JSON และแปลงกลับ โดยใช้แท็ก `json:"..."` กำหนดชื่อฟิลด์
package main
import (
"encoding/json"
"fmt"
)
type Student struct {
Name string `json:"name"`
Score int `json:"score"`
}
func main() {
b, _ := json.Marshal(Student{Name: "สมชาย", Score: 85})
fmt.Println(string(b))
var s Student
_ = json.Unmarshal([]byte(`{"name":"สมหญิง","score":72}`), &s)
fmt.Println(s.Name, s.Score)
}
**ผลลัพธ์ที่ได้จากการรันจริง**
{"name":"สมชาย","score":85}
สมหญิง 72
ตัวอย่างที่ 24: ทดสอบ HTTP handler
httptest ให้ทดสอบฟังก์ชัน handler โดยไม่ต้องเปิดเซิร์ฟเวอร์จริง
package main
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
)
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "สวัสดีจาก Go HTTP")
}
func main() {
req := httptest.NewRequest(http.MethodGet, "/", nil)
rec := httptest.NewRecorder()
hello(rec, req)
body, _ := io.ReadAll(rec.Result().Body)
fmt.Println(rec.Code, string(body))
}
**ผลลัพธ์ที่ได้จากการรันจริง**
200 สวัสดีจาก Go HTTP
ตัวอย่างที่ 25: Generics
ฟังก์ชันที่รองรับหลายชนิดข้อมูลด้วยพารามิเตอร์ชนิด T และกำหนดข้อจำกัดด้วยอินเทอร์เฟซ
package main
import "fmt"
type Number interface {
~int | ~float64
}
func Sum[T Number](values []T) T {
var total T
for _, v := range values {
total += v
}
return total
}
func main() {
fmt.Println(Sum([]int{1, 2, 3}))
fmt.Println(Sum([]float64{1.5, 2.5}))
}
**ผลลัพธ์ที่ได้จากการรันจริง**
6
4
ขั้นต่อไป
ลองเขียนเว็บเซิร์ฟเวอร์ที่รับข้อมูล JSON แล้วบันทึกลงไฟล์ จากนั้นศึกษาแพ็กเกจ net/http และ context เพื่อจัดการเวลาและการยกเลิกงาน
สรุป
Go ใช้ไวยากรณ์เรียบง่าย ตรวจชนิดข้อมูลตอนคอมไพล์ จัดการข้อผิดพลาดด้วยค่า error และรองรับการทำงานพร้อมกันด้วย goroutine และ channel จึงเหมาะกับงานเซิร์ฟเวอร์และระบบที่มีงานจำนวนมาก