ประเภท
ในส่วนย่อยของประเภทข้อมูลก่อนหน้านี้ได้แนะนำไปแล้วอย่างง่ายเกี่ยวกับประเภทข้อมูลที่มีอยู่ในตัวทั้งหมดของ Go ประเภทพื้นฐานที่มีอยู่ในตัวเหล่านี้เป็นพื้นฐานของประเภทที่กำหนดเองในภายหลัง Go เป็นภาษาประเภทคงที่โดยทั่วไป ประเภทของตัวแปรทั้งหมดจะถูกกำหนดในช่วงเวลาคอมไพล์ และจะไม่เปลี่ยนแปลงตลอดวงจรชีวิตของโปรแกรม ส่วนย่อยนี้จะแนะนำระบบประเภทและการใช้งานพื้นฐานของ Go อย่างง่าย
สถิตชนิดเข้มแข็ง
Go เป็นภาษาสถิตชนิดเข้มแข็ง สถิตหมายถึงประเภทของตัวแปรทั้งหมดของ Go ได้ถูกกำหนดไว้แล้วในช่วงเวลาคอมไพล์ และจะไม่เปลี่ยนแปลงตลอดวงจรชีวิตของโปรแกรม แม้การประกาศตัวแปรแบบสั้นใน Go จะคล้ายกับสไตล์การเขียนของภาษาไดนามิก แต่ประเภทของตัวแปรนั้นถูกอนุมานโดยคอมไพเลอร์ ความแตกต่างที่พื้นฐานที่สุดคือประเภทของมันหลังจากถูกอนุมานแล้วจะไม่发生变化อีกต่อไป ภาษาไดนามิก则ตรงกันข้ามโดยสิ้นเชิง ดังนั้นโค้ดด้านล่างจึงไม่สามารถผ่านการคอมไพล์ได้เลย เพราะ a เป็นตัวแปรประเภท int ไม่สามารถกำหนดค่าเป็นสตริงได้
func main() {
var a int = 64
a = "64"
fmt.Println(a) // cannot use "64" (untyped string constant) as int value in assignment
}ชนิดเข้มแข็งหมายถึงการดำเนินการตรวจสอบประเภทอย่างเข้มงวดในโปรแกรม หากปรากฏสถานการณ์ประเภทไม่ตรงกัน จะบอกโปรแกรมเมอร์ทันทีว่าไม่ควรทำเช่นนี้ แทนที่จะพยายามอนุมานผลลัพธ์ที่เป็นไปได้เหมือนภาษาไดนามิก ดังนั้นโค้ดด้านล่างจึงไม่สามารถผ่านการคอมไพล์ได้ เพราะทั้งสองประเภทต่างกัน ไม่สามารถดำเนินการคำนวณได้
func main() {
fmt.Println(1 + "1") // invalid operation: 1 + "1" (mismatched types untyped int and untyped string)
}ประเภทหลัง
เหตุใด Go จึงวางการประกาศประเภทไว้ด้านหลังแทนที่จะอยู่ด้านหน้า ส่วนใหญ่แล้ว吸取บทเรียนจากภาษา C นำตัวอย่างหนึ่งจากทางการ来展示ผลลัพธ์ นี่คือพอยน์เตอร์ฟังก์ชัน
int (*(*fp)(int (*)(int, int), int))(int, int)พูดตามตรง若不认真ดูยากที่จะรู้ว่านี่คือประเภทอะไร ใน Go การเขียนที่คล้ายกันมีดังนี้
f func(func(int,int) int, int) func(int, int) intวิธีการประกาศของ Go ปฏิบัติตามหลักการที่ชื่ออยู่ด้านหน้า ประเภทอยู่ด้านหลังเสมอ อ่านจากซ้ายไปขวา โดยประมาณแล้ว第一眼就可以รู้ว่านี่คือฟังก์ชัน และค่าส่งกลับคือ func(int,int) int เมื่อประเภท变得越来越ซับซ้อน时 ประเภท后置在可读性上要好得多 Go ในหลายระดับการออกแบบล้วน是为了可读性而服务的
การประกาศประเภท
ใน Go ผ่านการประกาศประเภท สามารถประกาศประเภทใหม่ที่มีชื่อ自定义ได้ การประกาศประเภทใหม่通常需要一个类型名称以及一个基础类型 ตัวอย่างง่ายๆ มีดังนี้:
type MyInt int64ในการประกาศประเภทข้างต้น ผ่านคำสำคัญ type ได้ประกาศประเภทที่มี基础类型为 int64 ชื่อว่า MyInt ใน Go ทุกประเภทที่ประกาศใหม่ต้องมี基础类型ที่สอดคล้องกันหนึ่ง类型 และ不建议ใช้ชื่อประเภทซ้ำกับ标识符ที่มีอยู่แล้ว
type MyInt int64
type MyFloat64 float64
type MyMap map[string]int
// สามารถผ่านการคอมไพล์ได้ แต่不建议ใช้ เพราะ这会覆盖原有的类型
type int int64ประเภทที่ประกาศผ่านการประกาศประเภทล้วนเป็นประเภทใหม่ ประเภทที่ต่างกันไม่สามารถดำเนินการคำนวณได้ แม้基础类型จะเหมือนกัน
type MyFloat64 float64
var f1 MyFloat64
var f float64
f1 = 0.2
f = 0.1
fmt.Println(f1 + f)invalid operation: f1 + f (mismatched types MyFloat64 and float64)นามแฝงประเภท
นามแฝงประเภทแตกต่างจากการประกาศประเภท นามแฝงประเภทเป็นเพียงนามแฝงเท่านั้น ไม่ได้สร้างประเภทใหม่ ตัวอย่างง่ายๆ มีดังนี้:
type Int = intทั้งสองเป็นประเภทเดียวกัน เพียงชื่อที่เรียกต่างกัน ดังนั้นจึงสามารถดำเนินการคำนวณได้ ดังนั้นตัวอย่างด้านล่าง自然也就可以ผ่านการคอมไพล์
type Int = int
var a Int = 1
var b int = 2
fmt.Println(a + b)3นามแฝงประเภทมีประโยชน์มากสำหรับประเภทที่ซับซ้อน特别มาก ตัวอย่างเช่น ตอนนี้มีประเภทหนึ่ง map[string]map[string]int นี่คือ map สองมิติ ตอนนี้มีฟังก์ชันหนึ่งพารามิเตอร์เป็นประเภท map[string]map[string]int ดังนี้
func PrintMyMap(mymap map[string]map[string]int) {
fmt.Println(mymap)
}ในสถานการณ์เช่นนี้ ก็ไม่จำเป็นต้องใช้การประกาศประเภทแล้ว เพราะ前者เป็นการประกาศประเภทใหม่หนึ่ง类型 ไม่สามารถเป็นพารามิเตอร์ของฟังก์ชันนี้ได้ ตัวอย่างหลังใช้นามแฝงประเภทมีดังนี้
type TwoDMap = map[string]map[string]int
func PrintMyMap(mymap TwoDMap) {
fmt.Println(mymap)
}หลังใช้นามแฝงประเภทแล้ว看起来จะ简洁一些
TIP
ประเภทที่มีอยู่ในตัว any คือนามแฝงประเภทของ interface{} ทั้งสองเทียบเท่ากัน เพียงชื่อเรียก不一样
การแปลงประเภท
ใน Go มีเพียงการแปลงประเภทแบบ显式เท่านั้น ไม่มีการแปลงประเภทแบบ隐式 ดังนั้นตัวแปรประเภทที่ต่างกันจึงไม่สามารถดำเนินการคำนวณได้ ไม่สามารถส่งเป็นพารามิเตอร์ได้ เงื่อนไขเบื้องต้นที่ใช้ได้กับการแปลงประเภทคือต้องทราบประเภทของตัวแปรที่ถูกแปลงและประเภทเป้าหมายที่ต้องการแปลง ตัวอย่างมีดังนี้:
type MyFloat64 float64
var f1 MyFloat64
var f float64
f1 = 0.2
f = 0.1
fmt.Println(float64(f1) + f)0.30000000000000004ผ่านการแปลง MyFloat64 เป็น float64 แบบ显式 จึงสามารถดำเนินการบวกได้ อีกเงื่อนไขเบื้องต้นของการแปลงประเภทคือ: ประเภทที่ถูกแปลงต้องสามารถ被แทนได้โดยประเภทเป้าหมาย (Representability) ตัวอย่างเช่น int สามารถ被แทนได้โดย int64 และยังสามารถ被แทนได้โดย float64 ดังนั้นระหว่างพวกมันจึงสามารถดำเนินการแปลงประเภทแบบ显式ได้ แต่ประเภท int ไม่สามารถ被แทนได้โดย string และ bool ดังนั้นจึงไม่สามารถดำเนินการแปลงประเภทได้
TIP
เกี่ยวกับนิยามของ Representability สามารถ前往参考手册 - Representabilityเพื่อเรียนรู้รายละเอียดเพิ่มเติม
แม้ว่าสองประเภทสามารถแทนกันได้ แต่ผลลัพธ์ของการแปลงประเภทก็ไม่总是ถูกต้อง ดูตัวอย่างหนึ่งด้านล่าง:
var num1 int8 = 1
var num2 int32 = 512
fmt.Println(int32(num1), int8(num2))1 0num1 ถูกแปลงเป็น int32 อย่างถูกต้อง แต่ num2 ไม่ได้ นี่เป็นปัญหาการล้นของตัวเลข典型的 int32 สามารถแสดงจำนวนเต็ม 31 บิตได้ int8 สามารถแสดงจำนวนเต็ม 7 บิตได้เท่านั้น เมื่อจำนวนเต็มความแม่นยำสูงแปลงเป็นจำนวนเต็มความแม่นยำต่ำจะทิ้งบิตสูง保留บิตต่ำ ดังนั้นผลลัพธ์การแปลงของ num1 จึงเป็น 0 ในการแปลงประเภทของตัวเลข โดยทั่วไปแนะนำให้แปลงเล็กเป็นใหญ่ ไม่แนะนำให้แปลงใหญ่เป็นเล็ก
เมื่อใช้การแปลงประเภท สำหรับบางประเภทต้องหลีกเลี่ยงความกำกวม ตัวอย่างมีดังนี้:
*Point(p) // เทียบเท่ากับ *(Point(p))
(*Point)(p) // แปลง p เป็นประเภท *Point
<-chan int(c) // เทียบเท่ากับ <-(chan int(c))
(<-chan int)(c) // แปลง c เป็นประเภท <-chan int
(func())(x) // แปลง x เป็นประเภท func()
(func() int)(x) // แปลง x เป็นประเภท func() intการยืนยันประเภท
การยืนยันประเภท通常ใช้สำหรับตัดสินว่าตัวแปรประเภทอินเทอร์เฟซหนึ่งเป็นประเภทใดประเภทหนึ่ง ตัวอย่างมีดังนี้
var b int = 1
var a interface{} = b
if intVal, ok := a.(int); ok {
fmt.Println(intVal)
} else {
fmt.Println("error type")
}1เนื่องจาก interface{} เป็นประเภทอินเทอร์เฟซว่าง ประเภทอินเทอร์เฟซว่างสามารถแทนประเภททั้งหมดได้ แต่ประเภท int ไม่สามารถแทน interface{} ได้ ดังนั้นจึงไม่สามารถใช้การแปลงประเภทได้ ส่วนการยืนยันประเภทสามารถตัดสินว่าประเภทพื้นฐานของมัน是否为ประเภทที่ต้องการได้ คำสั่งการยืนยันประเภทมีค่าส่งกลับสองตัว ตัวหนึ่งคือค่าหลังการแปลงประเภท อีกตัวหนึ่งคือค่าบูลีนของผลลัพธ์การแปลง
การตัดสินประเภท
ใน Go คำสั่ง switch ยังรองรับการเขียนแบบพิเศษหนึ่งแบบ ผ่านการเขียนนี้สามารถดำเนินการจัดการตรรกะที่ต่างกันตาม case ที่ต่างกัน เงื่อนไขเบื้องต้นที่ใช้คือพารามิเตอร์เข้าต้องเป็นประเภทอินเทอร์เฟซ ตัวอย่างมีดังนี้:
var a interface{} = 2
switch a.(type) {
case int: fmt.Println("int")
case float64: fmt.Println("float")
case string: fmt.Println("string")
}intTIP
ผ่านการดำเนินการที่จัดเตรียมโดยแพ็กเกจ unsafe สามารถเลี่ยงระบบประเภทของ Go ได้ ก็จะสามารถดำเนินการแปลงประเภทที่เดิมไม่สามารถผ่านการคอมไพล์ได้
