Go Data Structures

Back to Go Index

Data Types

ในภาษา go ส่วน type ไม่ได้มีความซับซ้อน เพราะไม่ใช่ OOP แบ่งเป็น 4 กลุ่มหลัก ดังนี้

รายละเอียด:

  • complex64 จะแบ่งเป็นจำนวนเต็ม 32 bit และจำนวนจินตภาพ 32 bit
  • การใช้ int เฉยๆ จะเป็นการหมายถึง int ที่มีขนาดมากที่สุดเท่าที่ CPU บนเครื่องเราใช้ เช่น เช่น CPU 64 bit ก็จะได้ int64
  • uint หมายถึง unsigned int
  • byte ใช้หมายถึงอักขระ ASCII code ขนาด 8 bit
  • rune ใช้เป็น Unicode code point (utf) มีขนาดได้ 1 - 4 byte
  • pointer มี zero value คือ nil (เทียบเคียงเหมือน null ในภาษาอื่น)

Rune

  • A rune is a character. That’s it. Rune is also an alias for int32
  • ถ้าเป็นตัวอักขระ string เช่น abc ปกติจะไม่มีปัญหา เพราะสามารถรองรับใน byte (8 bit) ได้
  • แต่ถ้าเป็นภาษาหรืออักขระอื่น ๆ จะเกินที่รองรับใน 8 bit ทำให้มีปัญหาในการนับจำนวนอักขระที่จะเกินจากความเป็นจริง ตัวอย่างการนับ
  • Formatting: print rune ปกติออกมาเป็นเลข ถ้าอยากให้เป็น char ต้องกำหนด format
var r rune = '😝'	
fmt.Println("r:", r) // r: 128541
fmt.Printf("r: %c\n", r) // r: 😝

ถ้าอยากให้ format ออกมาตรงค่าโดยไม่ต้องจำแยก type ให้ใช้ %#v

Array

  • Immutable (เปลี่ยนแปลงขนาดไม่ได้)
  • ประกาศโดยการวาง [ ] ที่มีตัวเลข วางไว้หน้า type
  • Index เริ่มที่ 0 และเก็บค่า default เป็น zero value ของ type ที่ประกาศ
var fourNum [4]int
fourNum[0] = 1
fourNum[2] = 3

การ Assign ค่า Array

// var skills [3]string = [3]string{"JS", "Go", "Python"}
skills := [3]string{"JS", "Go", "Python"}
 
fmt.Println(skills[2])

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

Slice

  • Mutable (เปลี่ยนแปลงขนาดได้)
  • ประกาศโดยการวาง [ ] โดยไม่มีตัวเลข วางไว้หน้า type
  • Zero value คือ nil ฉะนั้นจึงนับ Slice เป็น Pointer ประเภทนึง
  • ประกาศด้วย make(type, สมาชิกตั้งต้น) จะทำหน้าที่ allocate memory ให้ โดยสมาชิกจะเป็น zero value ตาม type
  • สมาชิกเเดิมเป็น 0 ก่อนก็ได้ แล้วค่อยเติมของทีหลัง
  • สามารถเติมของด้วย append (slice ตั้งต้น, ค่าที่ต้องการเติมโดย type ต้องเหมือนกับสมาชิก slice) และสามารถเพิ่มทีละหลายค่าได้
var num []int
nums = make([]int, 4) // [0, 0, 0, 0]
nums[0] = 1
nums[2] = 2
nums = append(nums, 20, 30)

Slice Internals

ภายใน (Internal) ของ Slice ประกอบไปด้วย 3 ค่า อ้างอิงกับ make()

  1. pointer มีหน้าที่ชี้ไปหา Array จริง ๆ ตัวนึง แปลว่าเบื้องหลัง Slice ทุกตัวจะมี Array อยู่เสมอ
  2. length เก็บจำนวนสมาชิกที่มันชี้ไป len()
  3. capacity ความจุของ array cap()

Slicing with Colon

การหั่น (slice) ด้วย colon แบบ half-open range

skills := []string{"Go", "JS", "Ruby"}
 
fmt.Println(skills[0:2])           // [Go JS]
fmt.Println(skills[:len(skills)])  // All
fmt.Println(skills[0:])            // All
fmt.Println(skills[:])             // All

Variadic Functions with Slices

เราสามารถส่ง slice เป็น variadic function โดยตรง ซึ่งมีค่าเท่ากับการ unpack สมาชิกแต่ละตัว โดยใช้ ...

xs := []float64{1, 2, 3, 4}
ys := []float64{5, 6, 7}
 
var xys []float64
xys = append(xs, ys...)
// xys = append(xs, ys[0], ys[1], ys[2])

Slice Demo: Shared Underlying Array

เบื้องหลัง Slice ทุกตัวจะมี Array อยู่ข้างล่างเสมอ (Underlying array)

func show(tag string, sk []string) {
	l := len(sk)
	fmt.Printf("%s: len: %d -- %v\n", tag, l, sk)
}
 
func main() {
	skills := []string{"JS", "Go", "Python"}
 
	s1 := skills[0:2]
	show("s1", s1)
 
	s2 := skills[1:3]
	show("s2", s2)
 
	s1[1] = "Gopher" // ถ้าเปลี่ยนค่าใน index 1 เป็น "Gopher"
	show("s1", s1)
	show("s2", s2)
	show("skills", skills)
}
 
/* Before */
// s1: len: 2 -- [JS Go]
// s2: len: 2 -- [Go Python]
 
/* After */
// s1: len: 2 -- [JS Gopher]
// s2: len: 2 -- [Gopher Python]
// skills: len: 3 -- [JS Gopher Python]

Slice Demo: Capacity

func show(tag string, sk []string) {
	l := len(sk)
	c := cap(sk)
	fmt.Printf("%s: len: %d cap: %d -- %v\n", tag, l, c, sk)
}
 
func main() {
	skills := []string{"JS", "Go", "Python"}
 
	s1 := skills[0:2]
	show("s1", s1)
 
	s2 := skills[1:3]
	show("s2", s2)
}
 
// s1: len: 2 cap: 3 -- [JS Go]
// s2: len: 2 cap: 2 -- [Go Python]
  • ถ้าเราทำการ s2 = append(s2, "C++") Go จะสร้าง array ตัวใหม่สำหรับ s2 โดย copy ค่าเดิมมาด้วย ทำให้ตอนนี้ s1, s2 ไม่ได้ชี้ array ตัวเดียวกันแล้ว

See also: Pointers in Go

Map

Maps เป็น Data Structure มีลักษณะเหมือนกับ Dictionary คือ Key และ Value

var m map[string]int = map[string]int{"a": 1, "b": 2}
fmt.Printf("Values: %#v\n", m)
 
m["c"] = 3 // add
fmt.Printf("Values: %#v\n", m)
 
v1 := m["a"] // get
fmt.Println("Values:", v1)
 
delete(m, "b") // delete
fmt.Printf("Values: %#v\n", m)
 
v2 := m["b"] // get blank return zero value
fmt.Println("Values:", v2)
 
v3, ok := m["b"] // get with ok for check key exist
fmt.Println("Values:", v3, ok)

Map Example: Word Count

func WordCount(s string) map[string]int {
	words := strings.Fields(s)
	r := map[string]int{}
	for _, w := range words {
		r[w] = r[w] + 1
	}
	return r
}
 
func main() {
	s := "If it looks like a duck swims like a duck and quacks like a duck then it probably is a duck"
	w := WordCount(s)
	fmt.Printf("%#v\n", w)
}

Pointers

ทุกตัวแปรจะมีการจองที่จัดเก็บข้อมูลไว้ โดย memory address

  • pointer มี Zero value คือ nil
  • pointer ไม่สามารถใช้ทำ Arithmetic Operation ได้
  • * ใช้ประกาศ pointer โดยการวางไว้หน้า type
  • * ยังสามารถใช้ dereference ตัวแปร pointer เพื่อเข้าถึงค่าที่ชี้ไปได้ เช่น *addr
  • & วางไว้หน้าตัวแปร เพื่อใช้ reference ไปยัง memory address เช่น &price
var price int = 100
var addr *int = &price
 
fmt.Println("[1]", price, &price)
fmt.Println("[2]", *addr, addr)
 
// Same outout:
// [1] 100 0xc0000160a8
// [2] 100 0xc0000160a8

Modifying Values with Pointers

เปลี่ยนค่าตัวแปรโดยใช้การ dereference

func main() {
	var price int = 100
	var addr *int = &price
	fmt.Println(price, &price) // 100 0xc0000160a8
 
	*addr = 200                // write
	fmt.Println(price, &price) // 200 0xc0000160a8
}

Pass by Value vs Pass by Pointer

เนื่องจาก Go ใช้หลักการ Pass by Value คือ copy แล้วส่งค่าไปที่ function หรือ struct

func changePrice(p int) {
	p = p - 50
	fmt.Println("[1]", p, &p)
}
 
func main() {
	var price int = 500
	var addr *int = &price
 
	changePrice(price)
	fmt.Println("[2]", price, addr)
}
 
// [1] 450 0xc0000160c0
// [2] 500 0xc0000160a8

Work around: สามารถให้ตัว address เข้าเป็น parameter *int แล้ว dereference pointer เป็น *p

func changePrice(p *int) {
	*p = *p - 50
	fmt.Println("[1]", p, &p)
}
 
func main() {
	var price int = 500
	var addr *int = &price
 
	changePrice(&price)
	fmt.Println("[2]", price, addr)
}
 
// [1] 0xc000098058 0xc0000ba018
// [2] 450 0xc000098058

Practical Example

func add1(num int) int {
	return num + 1
}
 
func add2(num int) {
	num = num + 1
}
 
func add3(num *int) {
	*num = *num + 1
}
 
func main() {
	a := add1(10)
	fmt.Println(a)
	// 11 because add1 returns 10 + 1
 
	b := 10
	add2(b)
	fmt.Println(b)
	// 10 because add2 does not change the value of b
 
	c := 10
	add3(&c)
	fmt.Println(c)
	// 11 because add3 changes the value of c through pointer
}

Related: