###### tags: `GoLang` # GoLang 02.進階語法: ### Go 陣列(Array)宣告 > 一維陣列的宣告方式如下: > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">陣列名稱</span> = [<span style="color: lightgreen">陣列長度</span>]<span style="color: lightgreen">元素型別</span>{<span style="color: yellow">元素初始值1</span>,<span style="color: yellow">元素初始值2</span>}** \ > > *陣列長度可以使用 ```[...]``` 讓編譯器自動計算長度\ > > *元素型別可為任何資料型別的\ > > *元素初始值若不賦予則為元素型別的預設值\ > > *元素初始值可以依序使用 ```,``` 分隔並可以向右增加元素,或是直接指定元素位置賦值 > > 例如 : **<span style="color: LemonChiffon">ar := [...]int{</span><span style="color: Orange">0</span><span style="color: LemonChiffon">:99}</span>** 其中<span style="color: Orange">0</span>表示為第一個元素 > >二維陣列的宣告方式如下: >``` go > package main > import "fmt" > func main() { > vArr1 := [3][3]int{ > {1, 2, 3}, > {4, 5, 6}, > {7, 8, 9}} > fmt.Println(vArr1) > } >``` >陣列本身為值類型變數其記憶體位址與元素[0]為位址相同,陣列的宣告與記憶體運作: >``` go >package main >import "fmt" >func main() { > > var vArray = [...]int{1, 2, 3} > fmt.Printf("陣列位址 : %p\n", &vArray) > > fmt.Printf("元素[0] 位址 : %p 數值 : %v\n", &vArray[0], vArray[0]) > fmt.Printf("元素[1] 位址 : %p 數值 : %v\n", &vArray[1], vArray[1]) > fmt.Printf("元素[2] 位址 : %p 數值 : %v\n", &vArray[2], vArray[2]) > >} > >``` ><img src="http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuSf9JIjHACbNACfCpoXHICaiIaqkoSpFu-8gI4pEJanFLL1wENltYzPbIWhAAM1GpSN5cLQKc9kgy6B5Z6TjsrwkdbBDnGaLUhfkyTE9NIzdBdxQCJqphyJOwEcyResdSpOydTjWPL1XO92Oac9We0bIpvcNdvAgu5XKb5ZfcLSI3HBXrDK5KpP9Ti-thtWnSxvLj46iWiBJXZrMBnTCUB5YBP2bW6uO3es661eLqBLQuD1gH52h8Ogjx_Vqeg773GrFTYzzjdR9ouNpdlMj078nrQ2PG0O2QOtbuihBBqc5nbv-6Zn05PJpYnBpqbEvdaxeWKJ7qzRbpsUrF6rjVhvbmTEzUu2kUxAp--MqoH2lSJsTlUlfkbdFP-zuEMlAanqDJnkwmV47qjAxuFcK5SzNxW8gvahDIr40sGYql9OBY6gv7812m8G40000" style="background-color:#2e2e2e;padding:0px;"> > > > 1. GoLang陣列是屬於值類型其長度固定無法改變,不同長度的陣列會被視為不同資料型別 > 2. GoLang陣列是屬於值類型其值的傳遞,即表示將其陣列複製到另一新的陣列中 > 3. GoLang陣列宣告利用鍵值(ASCII)或正整數當作元素編號並且直接賦值,其餘未賦值則會保持預設值 >``` go >package main >import "fmt" >func main() { > vArray := [...]int{1, 2, 3} > fmt.Println("原始 :", vArray) > fcArrayChange(vArray) > fmt.Println("傳遞後 :", vArray) > > // 原始 : [1 2 3] > // 函式內 : [0 0 0] > // 傳遞後 : [1 2 3] > > vArrayKey := [...]int{3: 555} > fmt.Println("\n鍵值 :", vArrayKey) > //[0 0 0 555] > //陣列可利用鍵值或數字當作元素編號並且直接賦值,其餘未賦值則會保持預設值 > > var vArrayNew [3]int = [...]int{4, 5, 6} > fmt.Println("\n複製 :", vArrayNew) > // 最複雜宣告方式 > > var vArrayCopy [3]int = vArray > fmt.Println("\n複製 :", vArrayCopy) > // 陣列複製 > > if vArray == vArrayCopy { > fmt.Println("\nArray和vArrayCopy兩者相等") > } > >} > >func fcArrayChange(inputArr [3]int) { > inputArr[0] = 0 > inputArr[1] = 0 > inputArr[2] = 0 > fmt.Println("函式內 :", inputArr) > //傳遞函式內之陣列即為新的陣列 >} >``` ### Go 陣列(Array)使用範例(for-range) ``` go package main import "fmt" func main() { vArr1 := [3][3]int{ {1, 2, 3}, {4, 5, 6}, {7, 8, 9}} fmt.Printf("vArr1[0] (%p)\n", &vArr1[0]) fmt.Printf("vArr1[1] (%p)\n", &vArr1[1]) fmt.Printf("vArr1[3] (%p)\n", &vArr1[2]) //使用for迴圈解析二維以上陣列需要使用兩層以上迴圈 for index1 := 0; index1 < len(vArr1); index1++ { for index2 := 0; index2 < len(vArr1[index1]); index2++ { fmt.Printf("[%v][%v]= %v (%p)\n", index1, index2, vArr1[index1][index2], &vArr1[index1][index2]) } } fmt.Println("\n") // for-range 使用於arrary範例1 for index, value := range vArr1 { fmt.Printf(" index = %v , value = %v ;", index, value) } // index = 0 , value = [1 2 3] ; index = 1 , value = [4 5 6] ; index = 2 , value = [7 8 9] ; fmt.Println("\n") // for-range 使用於arrary範例2 for index1, value := range vArr1 { for index2, value := range value { fmt.Printf("vArr1[%v][%v] = %v\n", index1, index2, value) } } // vArr1[0][0] = 1 // vArr1[0][1] = 2 // vArr1[0][2] = 3 // vArr1[1][0] = 4 // vArr1[1][1] = 5 // vArr1[1][2] = 6 // vArr1[2][0] = 7 // vArr1[2][1] = 8 // vArr1[2][2] = 9 } ``` ### Go 陣列(Array)應用範例: > > ``` go > package main > import "fmt" > func main() { > > vArray := [...]int{1, 99, 3} > fmt.Println("原始 :", vArray) > > // 求陣列內所有元素最大值 > vMaxNum := vArray[0] > for i := 0; i < len(vArray); i++ { > if vMaxNum < vArray[i] { > vMaxNum = vArray[i] > } > > } > fmt.Println("最大值 :", vMaxNum) > > // 求陣列內所有元素平均值 > var vSum int > for _, value := range vArray { > vSum += value > > } > fmt.Printf("平均值 : %v", float64(vSum)/float64(len(vArray))) > } > ``` > > ``` go > package main > import ( > "fmt" > "math/rand" > "time" > ) > > func main() { > > // 生成一隨機元素(值0~99)之陣列 > var vArray [5]int > rand.Seed(time.Now().UnixNano()) > for i := 0; i < len(vArray); i++ { > vArray[i] = rand.Intn(100) > > } > fmt.Println("原始 :", vArray) > > // 將陣列的元素位置倒轉 > var vArrayLen = len(vArray) > for i := 0; i < (vArrayLen / 2); i++ { > vArray[i], vArray[vArrayLen-1-i] = vArray[vArrayLen-1-i], vArray[i] > } > > fmt.Println("倒轉 :", vArray) > > } > > ``` --- ### Go 切片(slice)宣告 > 切片(slice)可視作可變動長度的陣列,但其本身為引用型別 > > 切片的第一種宣告方式(直接創建無名稱陣列): > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">切片名稱</span> = []<span style="color: lightgreen">元素型別</span>{<span style="color: yellow">元素初始值1</span>,<span style="color: yellow">元素初始值2</span>}** \ > > *元素型別可為資料型別的 ```邏輯值、數值、字串```???引用類型?\ > > *元素初始值若不賦予則為元素型別的預設值\ > > *元素初始值可以依序使用 ```,``` 分隔並可以向右增加元素 > > 切片的第二種宣告方式(引入已被創建的陣列): > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">切片名稱</span> = 陣列名稱[<span style="color: yellow">開始元素位置</span>:<span style="color: Orange">結束元素位置</span>]** \ > > *"開始元素位置"即選擇被引入陣列的元素位置作為引入起始\ > > *"結束元素位置"即選擇被引入陣列的"元素位置"(第幾個元素)作為引入結束\ > > *元素位置可以使用 ``` ``` (空白)作為"第一個元素位置"或"最後一個元素位置"作為簡寫 > > 切片的第三種宣告方式(使用make函式創建): > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">切片名稱</span> = make([]<span style="color: lightgreen">元素型別</span>,<span style="color: yellow">切片長度</span>,<span style="color: yellow">切片容量</span>)** \ > > *切片長度<=切片容量,切片容量可藉由指令變動大小(記憶體位址也會改變)\ > > *使用make函式所創建的切片,相當於直接創建無名稱且0值陣列做為切片 > > 切片的字串宣告方式(限用於宣告字串): > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">切片名稱</span> = []<span style="color: lightgreen">字串型別</span>(<span style="color: yellow">字串</span>)** \ > > *字串型別為 ```byte```、```int32```、```rune``` > >切片的宣告方式如下: >```go >package main >import "fmt" >func main() { > > //切片的第一種宣告方式(直接創建無名稱陣列): > var vSlice1 = []int{0, 1, 2} > fmt.Printf("vSlice1切片值 : %v\n", vSlice1) > fmt.Printf("vSlice1的長度 : %v\n", len(vSlice1)) > fmt.Printf("vSlice1的容量 : %v\n", cap(vSlice1)) > fmt.Printf("vSlice1的位址 : %p\n", &vSlice1) > fmt.Printf("vSlice1[0] 位址=%p 值=%v\n", &vSlice1[0], vSlice1[0]) > fmt.Printf("vSlice1[1] 位址=%p 值=%v\n", &vSlice1[1], vSlice1[1]) > fmt.Printf("vSlice1[2] 位址=%p 值=%v\n", &vSlice1[2], vSlice1[2]) > > fmt.Printf("\n") > //切片的第二種宣告方式(引入已被創建的陣列): > var vArray2 = [...]int{0, 1, 2} > var vSlice2 = vArray2[0:3] > fmt.Printf("vSlice2切片值 : %v\n", vSlice2) > fmt.Printf("vSlice2的長度 : %v\n", len(vSlice2)) > fmt.Printf("vSlice2的容量 : %v\n", cap(vSlice2)) > fmt.Printf("vSlice2的位址 : %p\n", &vSlice2) > fmt.Printf("vSlice2[0] 位址=%p 值=%v\n", &vSlice2[0], vSlice2[0]) > fmt.Printf("vSlice2[1] 位址=%p 值=%v\n", &vSlice2[1], vSlice2[1]) > fmt.Printf("vSlice2[2] 位址=%p 值=%v\n", &vSlice2[2], vSlice2[2]) > > vSlice2[0] = 4 > vSlice2[1] = 5 > vSlice2[2] = 6 > fmt.Printf("對元素賦值\n") > fmt.Printf("vSlice2切片值 : %v\n", vSlice2) > fmt.Printf("vArray2陣列值 : %v\n", vArray2) > > fmt.Printf("\n") > //切片的第三種宣告方式(使用make函式創建): > var vSlice3 = make([]int, 3, 3) > vSlice3[0] = 0 > vSlice3[1] = 1 > vSlice3[2] = 2 > fmt.Printf("vSlice3切片值 : %v\n", vSlice3) > fmt.Printf("vSlice3的長度 : %v\n", len(vSlice3)) > fmt.Printf("vSlice3的容量 : %v\n", cap(vSlice3)) > fmt.Printf("vSlice3的位址 : %p\n", &vSlice3) > fmt.Printf("vSlice3[0] 位址=%p 值=%v\n", &vSlice3[0], vSlice3[0]) > fmt.Printf("vSlice3[1] 位址=%p 值=%v\n", &vSlice3[1], vSlice3[1]) > fmt.Printf("vSlice3[2] 位址=%p 值=%v\n", &vSlice3[2], vSlice3[2]) >} > >``` > ><img src="http://www.plantuml.com/plantuml/svg/bLBDhj9055vtdk84Dc0iARZHfY6uz1a8Yx4TeA5CoJ0c6cBIXR4e_c02KOoW1j4c1k96YShRwBHqvIiuhPZSsnjoxvsSpC_vpdVcp9clrkUOyJjT1pcaoO6xmEnMcuDbCs9oswK8eHusExX58BSFNeKFlyMV9pd8M-aO84LfCjmb83PByU9nF5kAuKkRyciLiHWLn9ilOkxfsbrDmvQbQSKaNg6kHU0cO_ZUBShhYKTHrNGnRbUZRR2VlTNhMWD0U3iTudVZVR2MMm38vZl2BL-0U_q8jt8-jtpgtZ-xfndxvNc_Vwx2zOVu_I9y5iZZsPXiXijRGct8wJwIXlwt4mnL1J7qeqT-zFf1z7KSVXeXP3gutuUi7mm32hMhBZFRjbK4E3plXbQjmW0bJGcV-B4_1nskr4kbKaF-AqgBbb0ytOeV7oNa40ed0R7v7llF9M3YNWeWMHvrEG6NWcFJ3kHTXccBw0VD_Akg87RJQ198rOZXgeXgX5fIbNy1" style="background-color:#2e2e2e;padding:0px;"> > ### Go 切片append()& copy 使用範例 >切片可藉由append()函式進行擴容,擴容後其切片長度與容量都會增加, >append()函式僅能拼接相同型別元素或切片於新的切片當中, >其擴容後將新切片之元素分配新的記憶體位址(在原切片引入長度超過陣列長度下), >如果新切片名稱與原切片名稱相同,則切片位址將不會改變。 > > > append()函式擴容方式(拼接新的元素): > > > > **<span style="color: DodgerBlue">擴容後切片名稱</span> = append(<span style="color: lightgreen">被擴容切片名稱</span>,<span style="color: yellow">元素初始值1</span>,<span style="color: yellow">元素初始值2</span>}** > > > > *元素型別必須與擴容後切片型別相同\ > > *元素初始值若可以依序使用 ```,``` 分隔並可以向右增加元素 > > > append()函式擴容方式(拼接原有的切片): > > > > **<span style="color: DodgerBlue">擴容後切片名稱</span> = append(<span style="color: lightgreen">被擴容切片名稱</span>,<span style="color: yellow">拼接切片名稱1</span>,<span style="color: yellow">拼接切片名稱2</span><span style="color: red">...</span>}** > > > > *切片型別必須與擴容後切片型別相同\ > > *拼接切片可以依序使用 ```,``` 分隔並可以向右增加切片\ > > *拼接切片時append()指令中 ```...``` 不可省略 > > >切片可藉由copy()函式將切片中的元素值寫入另一切片中, >其過程依序將元素寫入直至切片長度結束為止, >完成後其切片的長度將不會減少或是增加,前後記憶體位址都將保持不變 > > copy()用法如下: > > > > **copy(<span style="color: lightgreen">被寫入切片名稱</span>,<span style="color: yellow">寫入切片名稱</span>}** > > > > *切片型別必須與copy後切片型別相同 > >``` go >package main >import "fmt" >func main() { > var vArray1 = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} > var vSlice1 = vArray1[1:8] > > fmt.Printf("vArray1陣列值 : %v\n", vArray1) //[0 1 2 3 4 5 6 7 8 9] > fmt.Printf("vSlice1切片值 : %v\n", vSlice1) //[1 2 3 4 5 6 7] > fmt.Printf("vSlice1的長度 : %v\n", len(vSlice1)) //7 > fmt.Printf("vSlice1的容量 : %v\n", cap(vSlice1)) //9 > fmt.Printf("vSlice1的位址 : %p\n", &vSlice1) > fmt.Println() > > //vSlice1引入vArray1 其元素是直接指向的(對陣列進行切片) > for i := 0; i < len(vArray1); i++ { > fmt.Printf("vArray1[%v] 值:%v 位址:%p\n", i, vArray1[i], &vArray1[i]) > } > > fmt.Println() > //使用for迴圈列出所有元素值 > for i := 0; i < len(vSlice1); i++ { > fmt.Printf("vSlice1[%v] 值:%v 位址:%p\n", i, vSlice1[i], &vSlice1[i]) > } > > fmt.Println() > //使用for-range迴圈列出所有元素值 > for index, value := range vSlice1 { > fmt.Printf("vSlice1[%v] 值:%v 位址:%p\n", index, value, &vSlice1[index]) > } > > fmt.Println() > > //利用append函式直接擴充切片元素成為一個新切片 > vSlice2 := append(vSlice1, 10, 11, 12, 13) > fmt.Printf("vSlice2切片值 : %v\n", vSlice2) //[1 2 3 4 5 6 7 10 11 12 13] > fmt.Printf("vSlice2的長度 : %v\n", len(vSlice2)) //11 > fmt.Printf("vSlice2的容量 : %v\n", cap(vSlice2)) //18 > > fmt.Println() > //利用append函式拼接兩個切片成為一個新切片 > vSlice3 := append(vSlice1, vSlice2...) > > fmt.Printf("vSlice3切片值 : %v (將Slice1與Slice2拼接成Slice3)\n", vSlice3) // [1 2 3 4 5 6 7 1 2 3 4 5 6 7 10 11 12 13] > fmt.Printf("vSlice3的長度 : %v\n", len(vSlice3)) //18 > fmt.Printf("vSlice3的容量 : %v\n", cap(vSlice3)) //18 > > fmt.Println() > fmt.Printf("vSlice2的位址 : %p\n", &vSlice2) > for index, value := range vSlice2 { > fmt.Printf("vSlice2[%v] 值:%v 位址:%p\n", index, value, &vSlice2[index]) > } > fmt.Println() > fmt.Printf("vSlice3的位址 : %p\n", &vSlice3) > for index, value := range vSlice3 { > fmt.Printf("vSlice3[%v] 值:%v 位址:%p\n", index, value, &vSlice3[index]) > } > > //利用append函式拼接之新切片其元素所指向位址將與原先切片完全不同 > //(在切片長度超過原本的陣列長度狀況下) > //意味著拼接之新切片與原先的舊切片記憶體位址完全不同 > > copy(vSlice3, vSlice2) > fmt.Println() > fmt.Printf("vSlice3的位址 : %p\n", &vSlice3) > for index, value := range vSlice3 { > fmt.Printf("vSlice3[%v] 值:%v 位址:%p\n", index, value, &vSlice3[index]) > } > fmt.Println(vSlice3) > // copy前 vSlice2: [1 2 3 4 5 6 7 10 11 12 13] > // copy前 vSlice3: [1 2 3 4 5 6 7 1 2 3 4 5 6 7 10 11 12 13] > // copy後 vSlice3: [1 2 3 4 5 6 7 10 11 12 13 5 6 7 10 11 12 13] > > copy(vSlice2, vSlice3) > fmt.Println(vSlice2) > // copy前 vSlice2: [1 2 3 4 5 6 7 10 11 12 13] > // copy後 vSlice3: [1 2 3 4 5 6 7 10 11 12 13] > > // copy切片長度即依序將元素覆蓋被copy之切片元素,直至切片長度結束為止, > // copy完成後切片的長度將不會減少或是增加 > >} > >``` ### Go 切片作為可變參數(Variadic)驗證 > *可變參數實用性較 >``` go >package main >import "fmt" >func main() { > var a byte = 'a' > var b = []byte{'b', 'b'} > c := append([]byte{a}, b...) > c = append(c, b...) > fmt.Printf("%s", c) > //abbbb > } >``` > >```go >package main > import ( > "log" > "time" > ) > > func StartTimer() time.Time { > return time.Now() > } > > func SubTimer(input time.Time) float64 { > return time.Now().Sub(input).Seconds() * 1000 > } > > func main() { > > var testCount = 9999999999 > sli := []int{1, 2, 3, 4, 5, 6, 7, 8, 9} > > time.Sleep(time.Second * 2) > var vTime2 = StartTimer() > for index := 0; index < testCount; index++ { > add_slice(1, 2, sli) > } > log.Printf(" 耗費:%.4f ms ", SubTimer(vTime2)) > //2020/07/13 22:35:01 耗費:23448.0000 ms > > time.Sleep(time.Second * 2) > var vTime1 = StartTimer() > for index := 0; index < testCount; index++ { > add_variadic(1, 2, sli...) > } > log.Printf(" 耗費:%.4f ms ", SubTimer(vTime1)) > //2020/07/13 22:35:53 耗費:49902.0000 ms > > } > > func add_slice(a, b int, nums []int) int { > res := a + b > if nums == nil { > for _, num := range nums { > res += num > } > } > return res > } > > func add_variadic(a, b int, nums ...int) int { > res := a + b > for _, num := range nums { > res += num > } > return res > } > ``` ### Go 切片複製方式驗證 >```go= > package main > import ( > "fmt" > "log" > "time" > ) > var originalSlice []int > const iterations = 10000 > > func init() { > for index := 0; index < iterations; index++ { > originalSlice = append(originalSlice, index) > } > } > > func StartTimer() time.Time { > return time.Now() > } > func SubTimer(input time.Time) float64 { > return time.Now().Sub(input).Seconds() * 1000 > } > > func main() { > > var vTime1 = StartTimer() > for index := 0; index < iterations; index++ { > CreatSliceWithAppend() > } > log.Printf(" 耗費:%.4f ms ", SubTimer(vTime1)) > //2021/11/08 21:44:37 耗費:693.3594 ms > time.Sleep(time.Second * 1) > > var vTime2a = StartTimer() > for index := 0; index < iterations; index++ { > CreatSliceWithMakeIndex() > } > log.Printf(" 耗費:%.4f ms ", SubTimer(vTime2a)) > //2021/11/08 21:44:38 耗費:179.6875 ms > time.Sleep(time.Second * 1) > > var vTime2b = StartTimer() > for index := 0; index < iterations; index++ { > CreatSliceWithMakeRange() > } > log.Printf(" 耗費:%.4f ms ", SubTimer(vTime2b)) > //2021/11/08 21:44:39 耗費:168.9453 ms > time.Sleep(time.Second * 1) > > var vTime3 = StartTimer() > var s3 []int > for index := 0; index < iterations; index++ { > s3 = append(s3, index) > fmt.Sprintln(AppendVariable(s3, originalSlice)) > } > s3 = nil > log.Printf(" 耗費:%.4f ms ", SubTimer(vTime3)) > //2021/11/08 21:44:53 耗費:13563.4766 ms > time.Sleep(time.Second * 1) > > var vTime4 = StartTimer() > var s4 []int > for index := 0; index < iterations; index++ { > s4 = append(s4, index) > fmt.Sprintln(AppendWithMake(s4, originalSlice)) > } > s4 = nil > log.Printf(" 耗費:%.4f ms ", SubTimer(vTime4)) > //2021/11/08 21:45:09 耗費:13940.4297 ms > time.Sleep(time.Second * 1) > > var vTime5 = StartTimer() > var s5 []int > for index := 0; index < iterations; index++ { > s5 = append(s5, index) > fmt.Sprintln(AppendWithCopy(s5, originalSlice)) > } > s5 = nil > log.Printf(" 耗費:%.4f ms ", SubTimer(vTime5)) > //2021/11/08 21:45:23 耗費:13523.4375 ms > time.Sleep(time.Second * 1) > > var vTime6 = StartTimer() > var s6 []int > for index := 0; index < iterations; index++ { > s6 = append(s6, index) > fmt.Sprintln(AppendWithAssign(s6, originalSlice)) > } > s6 = nil > log.Printf(" 耗費:%.4f ms ", SubTimer(vTime6)) > //2021/11/08 21:45:38 耗費:13794.9219 ms > } > > func CreatSliceWithAppend() (outputtSlice []int) { > for index := 0; index < iterations; index++ { > outputtSlice = append(outputtSlice, index) > } > return > } > > func CreatSliceWithMakeIndex() (outputtSlice []int) { > outputSlice := make([]int, iterations) > for index := 0; index < iterations; index++ { > outputSlice[index] = index > } > return > } > > func CreatSliceWithMakeRange() (outputtSlice []int) { > outputSlice := make([]int, iterations) > for index := range outputSlice { > outputSlice[index] = index > } > return > } > > func AppendVariable(inputSlice []int, appendSlice []int) (outputtSlice []int) { > outputtSlice = append(inputSlice, appendSlice...) > inputSlice = nil > appendSlice = nil > return > } > > func AppendWithMake(inputSlice []int, appendSlice []int) (outputtSlice []int) { > outputtSlice = make([]int, len(inputSlice)+len(appendSlice)) > outputtSlice = append(inputSlice, appendSlice...) > inputSlice = nil > appendSlice = nil > return > } > > func AppendWithCopy(inputSlice []int, appendSlice []int) (outputtSlice []int) { > outputtSlice = make([]int, len(inputSlice)+len(appendSlice)) > copy(outputtSlice, inputSlice) > copy(outputtSlice[len(inputSlice):], appendSlice) > inputSlice = nil > appendSlice = nil > return > } > > func AppendWithAssign(inputSlice []int, appendSlice []int) (outputtSlice []int) { > outputtSlice = make([]int, len(inputSlice)+len(appendSlice)) > for index := 0; index < len(inputSlice); index++ { > outputtSlice[index] = inputSlice[index] > } > > for index := 0; index < len(appendSlice); index++ { > outputtSlice[index+len(inputSlice)] = appendSlice[index] > } > inputSlice = nil > appendSlice = nil > return > } > ``` ### Go 切片&陣列擴容[append()]陷阱 : > 切片若引入原有的陣列當作元素後其使用append()函式擴容需要注意:\ > ==切片擴容未超過原先陣列長度元素,則所擴容的元素將會溢出並向下覆蓋原本的陣列元素== > 可直接參考 : https://play.golang.org/p/2noxYSTVe__Y >``` go >package main >import "fmt" >func main() { > > //切片&陣列陷阱 > vArray1 := [...]int{0, 1, 2, 3, 4} > vSlice1 := vArray1[1:3] > fmt.Printf("原始宣告vArray1 = %v (%p)\n", vArray1, &vArray1) > fmt.Printf("切片[0:2]vSlice1 = %v (%p)\n", vSlice1, &vSlice1) > vSlice1 = append(vSlice1, 10) > fmt.Printf("append 未超過原先陣列長度元素後↓ \n") > fmt.Printf("append 後vSlice1 = %v (%p)\n", vSlice1, &vSlice1) > fmt.Printf("append 後vSlice1[0] = %v (%p)\n", vSlice1[0], &vSlice1[0]) > fmt.Printf("append 後vSlice1[2] = %v (%p)\n", vSlice1[2], &vSlice1[2]) > fmt.Printf("原始宣告vArray1 = %v (%p)\n", vArray1, &vArray1) > fmt.Printf("原始宣告vArray1[1] = %v (%p)\n", vArray1[1], &vArray1[1]) > fmt.Printf("原始宣告vArray1[3] = %v (%p)\n", vArray1[3], &vArray1[3]) > // 原始宣告vArray1 = [0 1 2 3 4] (0xc000068060) > // 切片[0:2]vSlice1 = [1 2] (0xc000048420) > // append 未超過原先陣列長度元素後↓ > // append 後vSlice1 = [1 2 10] (0xc000048420) > // append 後vSlice1[0] = 1 (0xc000068068) > // append 後vSlice1[2] = 10 (0xc000068078) > // 原始宣告vArray1 = [0 1 2 10 4] (0xc000068060) > // 原始宣告vArray1[1] = 1 (0xc000068068) > // 原始宣告vArray1[3] = 10 (0xc000068078) > // vSlice1藉由append 擴容未超過原先陣列長度元素,則所擴容的元素將會溢出並向下覆蓋原本的陣列元素 > // (0xc000068068)&(0xc000068078)=(0xc000068068)&(0xc000068078) > > fmt.Println() > fmt.Println() > > //新手預想狀況 > vArray2 := [...]int{0, 1, 2, 3, 4} > vSlice2 := vArray2[1:3] > fmt.Printf("原始宣告vArray2 = %v (%p)\n", vArray2, &vArray2) > fmt.Printf("切片[0:2]vSlice2 = %v (%p)\n", vSlice2, &vSlice2) > vSlice2 = append(vSlice2, 10, 11, 12) > fmt.Printf("append 超過原先陣列長度元素後↓ \n") > fmt.Printf("append 後vSlice2 = %v (%p)\n", vSlice2, &vSlice2) > fmt.Printf("append 後vSlice2[0] = %v (%p)\n", vSlice2[0], &vSlice2[0]) > fmt.Printf("append 後vSlice2[2] = %v (%p)\n", vSlice2[2], &vSlice2[2]) > fmt.Printf("原始宣告vArray2 = %v (%p)\n", vArray2, &vArray2) > fmt.Printf("原始宣告vArray2[1] = %v (%p)\n", vArray2[1], &vArray2[1]) > fmt.Printf("原始宣告vArray2[3] = %v (%p)\n", vArray2[3], &vArray2[3]) > > // 原始宣告vArray2 = [0 1 2 3 4] (0xc000068120) > // 切片[0:2]vSlice2 = [1 2] (0xc0000484a0) > // append 超過原先陣列長度元素後↓ > // append 後vSlice2 = [1 2 10 11 12] (0xc0000484a0) > // append 後vSlice2[0] = 1 (0xc00006c0c0) > // append 後vSlice2[2] = 10 (0xc00006c0d0) > // 原始宣告vArray2 = [0 1 2 3 4] (0xc000068120) > // 原始宣告vArray2[1] = 1 (0xc000068128) > // 原始宣告vArray2[3] = 3 (0xc000068138) > // vSlice2藉由append 擴容超過原先陣列長度元素,vSlice2切片所指向元素的位址將與原先完全不同 > // (0xc00006c0c0)&(0xc00006c0d0)≠(0xc000068128)&(0xc000068138) >} >``` > ### Go 切片排序(使用sort包): ```go= package main import ( "fmt" "sort" ) func main() { var visl_Slice = []int{9, 1, 3, 5} if !sort.IntsAreSorted(visl_Slice) { sort.Ints(visl_Slice) //[1 3 5 9] fmt.Println(visl_Slice) sort.Sort(sort.Reverse(sort.IntSlice(visl_Slice))) fmt.Println(visl_Slice) //[9 5 3 1] } var vfsl_Slice = []float64{9.9, 1.1, 3.3, 5.0} if !sort.Float64sAreSorted(vfsl_Slice) { sort.Float64s(vfsl_Slice) //[1.1 3.3 5 9.9] fmt.Println(vfsl_Slice) sort.Sort(sort.Reverse(sort.Float64Slice(vfsl_Slice))) fmt.Println(vfsl_Slice) //[9.9 5 3.3 1.1] } var vssl_Slice = []string{"一", "b", "c", "二"} if !sort.StringsAreSorted(vssl_Slice) { sort.Strings(vssl_Slice) //[b c 一 二] fmt.Println(vssl_Slice) sort.Sort(sort.Reverse(sort.StringSlice(vssl_Slice))) fmt.Println(vssl_Slice) //[二 一 c b] } } ``` ### Go 切片&字串關係: > 字串(String)在GoLang當中以唯讀byte型別的切片存在,相當於切片引入一個陣列所有元素皆是byte型別之常數(const),其中GoLang預設以UTF-8作預設編碼,每個字元(character)占用1~6個byte(無固定容量),故使用byte型別進行切片可能產生亂碼,需使用rune(int32)做切片才能取得正確的UTF-8編號。 > > 切片的字串宣告方式(限用於宣告字串): > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">切片名稱</span> = []<span style="color: lightgreen">字串型別</span>(<span style="color: yellow">字串</span>)** > > *字串型別為 ```byte```、```int32```、```rune``` ``` go package main import "fmt" func main() { vStr := "你好 Hello World!" vSliceRuneStr := []rune(vStr) vSliceInt32Str := []int32(vStr) fmt.Printf("'%v'\n", vStr) fmt.Println("字元長度(byte) : ", len(vStr)) fmt.Println("字元長度(rune) : ", len(vSliceRuneStr)) fmt.Println("字元長度(int32) : ", len(vSliceInt32Str)) fmt.Print("使用for 再以byte分解vStr : '") for i := 0; i < len(vStr); i++ { fmt.Printf("%c", vStr[i]) } //ä½ å¥½ Hello World! fmt.Println("'") fmt.Print("使用for-range分解vStr : '") for _, vRange := range vStr { fmt.Printf("%c", vRange) } //你好 Hello World! fmt.Println("'") fmt.Print("使用for 分解vSliceRuneStr切片 : '") for i := 0; i < len(vSliceRuneStr); i++ { fmt.Printf("%c", vSliceRuneStr[i]) } //你好 Hello World! fmt.Println("'") fmt.Print("使用for 分解vSliceInt32Str切片 : '") for i := 0; i < len(vSliceInt32Str); i++ { fmt.Printf("%c", vSliceInt32Str[i]) } //你好 Hello World! fmt.Println("'") fmt.Println() fmt.Printf("%v\n", []byte("你好 Hello World!")) //將字元轉成Unicode編碼數字 fmt.Printf("%v\n", string([]byte{228, 189, 160, 229, 165, 189, 32, 72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100, 33})) //將Unicode編碼數字轉成字元 fmt.Print("使用for 再以string(byte)分解vStr : '") for i := 0; i < len(vStr); i++ { fmt.Printf("%v", string(vStr[i])) } //ä½ å¥½ Hello World! fmt.Println("'") fmt.Println() vSliceRuneStr[0] = '測' vSliceRuneStr[1] = '試' fmt.Println(vSliceRuneStr) fmt.Println(string(vSliceRuneStr)) //[28204 35430 32 72 101 108 108 111 32 87 111 114 108 100 33] //測試 Hello World! vSliceInt32Str[0] = '成' vSliceInt32Str[1] = '功' fmt.Println(vSliceInt32Str) fmt.Println(string(vSliceInt32Str)) // [25104 21151 32 72 101 108 108 111 32 87 111 114 108 100 33] // 成功 Hello World! } ``` ### Go 雜湊表(Map)宣告 > > 雜湊表(Map)是一種無排序鍵值(key-vaule)資料結構為引用型別,因該變數內的鍵值記憶體位址處於動態變化(鍵值位址無法獲取),必須先分配記憶體空間(手動或自動)後才能使用。分配完空間後往後Map鍵值增加會自動擴容,不會發生panic。 > > Map的第一種宣告方式(僅能宣告Map名稱未分配記憶體空間): > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">map名稱</span> map[<span style="color: lightgreen">鍵型別</span>]<span style="color: yellow">值型別</span>** \ > > *鍵的資料型別可為 ```邏輯值、數值、字串、指標、陣列、結構體、接口、通道```\ > > *值的資料型別可為任何資料型別包括map自己本身\ > > *該方式僅宣告Map名稱尚未分配記憶體,宣告完後無法直接使用(賦值、增加...) > > Map的第二種宣告方式(使用make()函式賦值,並分配記憶體空間): > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">map名稱</span> = make(map[<span style="color: lightgreen">鍵型別</span>]<span style="color: yellow">值型別</span>,<span style="color: orange">map容量</span>])** \ > > *map容量省略或為空皆會預設為零值,該值必須為正整數 > > Map的第三種宣告方式(直接賦予鍵值,將後自動分配記憶體空間)): > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">map名稱</span> = map[<span style="color: lightgreen">鍵型別</span>]<span style="color: yellow">值型別</span>{<span style="color: lightgreen">鍵1</span>:<span style="color: yellow">值1</span>,<span style="color: lightgreen">鍵3</span>:<span style="color: yellow">值2</span>}**\ > > *鍵型別與值型別必須與key和value相符且一致\ > > *鍵名稱不能夠重複 > ``` go > package main > import "fmt" > func main() { > > var vMap1 map[string]string > vMap1 = make(map[string]string) > vMap1["1A"] = "1aaAA" > vMap1["1B"] = "1bbBB" > fmt.Printf("vMap1 → %v (%p)", vMap1, &vMap1) > fmt.Println() > > var vMap2 = make(map[string]string, 0) > vMap2["2A"] = "2aaAA" > vMap2["2B"] = "2bbBB" > fmt.Printf("vMap2 → %v (%p)", vMap2, &vMap2) > fmt.Println() > > var vMap3 = map[string]string{"3A": "3aaAA", "3B": "3bbBB"} > fmt.Printf("vMap3 → %v (%p)\n", vMap3, &vMap3) > fmt.Printf("vMap3[\"3B\"] = %v", vMap3["3B"]) > fmt.Println() > fmt.Println() > > var vMap4 = make(map[string]map[string]string) > vMap4["人名1"] = make(map[string]string) > vMap4["人名1"]["體重"] = "10" > vMap4["人名1"]["身高"] = "100" > vMap4["人名2"] = make(map[string]string) > vMap4["人名2"]["體重2"] = "20" > vMap4["人名2"]["身高2"] = "200" > > fmt.Printf("vMap4 → %v (%p)\n", vMap4, &vMap4) //vMap4 → map[人名1:map[身高:100 體重:10] 人名2:map[身高2:200 體重2:20]] (0xc000080038) > fmt.Println(vMap4["人名1"]) //map[身高:100 體重:10] > fmt.Println(vMap4["人名2"]["身高2"]) //200 > > } > ``` > ### Go 雜湊表(map)使用(for-range) Map因key與value值位置為無序,解析方式僅能使用for-range迴圈遍歷 ```go package main import "fmt" func main() { var vMap1 = make(map[string]string) vMap1["1A"] = "1aaAA" vMap1["1B"] = "1bbBB" for index, value := range vMap1 { fmt.Printf("%v → %v\n", index, value) } fmt.Println() //map值類型也可以為map自身,成為樹狀資料結構 var vMap2 = make(map[string]map[string]string) vMap2["人名1"] = make(map[string]string) vMap2["人名1"]["體重"] = "10" vMap2["人名1"]["身高"] = "100" vMap2["人名2"] = make(map[string]string) vMap2["人名2"]["體重2"] = "20" vMap2["人名2"]["身高2"] = "200" for index1, value1 := range vMap2 { fmt.Printf("%v\n", index1, value1) for index2, value2 := range value1 { fmt.Printf("%v → %v\n", index2, value2) } } // 人名1 → map[身高:100 體重:10] // 身高 → 100 // 體重 → 10 // 人名2 → map[身高2:200 體重2:20] // 體重2 → 20 // 身高2 → 200 } ``` ### Go 雜湊表(Map)次序問題 Map因key與value值位置為無序,可利用切片截取Map之key再利用sort()函式排序: ```go package main import ( "fmt" "sort" ) func main() { var vMap1 = make(map[int]string) vMap1[99] = "九九" vMap1[9] = "九" vMap1[1] = "一" vMap1[10] = "十" vMap1[2] = "二" vMap1[11] = "十一" delete(vMap1, 9) fmt.Println(vMap1) for index, value := range vMap1 { fmt.Printf("vMap[%v] → %v\n", index, value) } //map[1:一 2:二 9:九 10:十 11:十一 99:九九] //map因key與value值位置為無序,難以預測取值順序 fmt.Println() fmt.Println() //可創建一切片再利用sort排序 var vMap1_key []int for index, _ := range vMap1 { vMap1_key = append(vMap1_key, index) } sort.Ints(vMap1_key) for _, value := range vMap1_key { fmt.Printf("vMap[%v] → %v\n", value, vMap1[value]) } fmt.Println() fmt.Println() // map[1:一 2:二 10:十 11:十一 99:九九] // vMap[2] → 二 // vMap[11] → 十一 // vMap[99] → 九九 // vMap[1] → 一 // vMap[10] → 十 // vMap[1] → 一 // vMap[2] → 二 // vMap[10] → 十 // vMap[11] → 十一 // vMap[99] → 九九 sort.Sort(sort.Reverse(sort.IntSlice(vMap1_key))) for _, value := range vMap1_key { fmt.Printf("vMap[%v] → %v\n", value, vMap1[value]) } // vMap[99] → 九九 // vMap[11] → 十一 // vMap[10] → 十 // vMap[2] → 二 // vMap[1] → 一 } ``` ```go= package main import ( "fmt" ) type ty_Custom struct { F0 string F1 string } func main() { /////////////////////////////////////////////////////////////////////////////// var mst_Map1 = make(map[ty_Custom]map[string]string) var st_Custom ty_Custom st_Custom.F0 = "Tom" st_Custom.F1 = "湯姆" mst_Map1[st_Custom] = make(map[string]string) mst_Map1[st_Custom]["衣服"] = "襯衫" mst_Map1[st_Custom]["愛喝酒"] = "威士忌" println(mst_Map1[st_Custom]["衣服"]) //襯衫 for index1, value1 := range mst_Map1 { fmt.Printf("%v → %v\n", index1, value1) for index2, value2 := range value1 { fmt.Printf("%v → %v\n", index2, value2) } } // {Tom 湯姆} → map[愛喝酒:威士忌 衣服:襯衫] // 衣服 → 襯衫 // 愛喝酒 → 威士忌 println() //////////////////////////////////////////////////////////////////////////////// var mst_Map2 = make(map[interface{}]map[string]string) var if_Custom interface{} mst_Map2[if_Custom] = make(map[string]string) mst_Map2[if_Custom]["空值"] = "空值的字串" println(mst_Map2[if_Custom]["空值"]) //空值的字串 println(mst_Map2[nil]["空值"]) //空值的字串 for index1, value1 := range mst_Map2 { fmt.Printf("%v → %v\n", index1, value1) for index2, value2 := range value1 { fmt.Printf("%v → %v\n", index2, value2) } } // <nil> → map[空值:空值的字串] // 空值 → 空值的字串 println() if_Custom = "anything" println(mst_Map2[if_Custom]["空值"]) //nil println(mst_Map2["anything"]["空值"]) //nil println(mst_Map2[nil]["空值"]) //空值的字串 for index1, value1 := range mst_Map2 { fmt.Printf("%v → %v\n", index1, value1) for index2, value2 := range value1 { fmt.Printf("%v → %v\n", index2, value2) } } // <nil> → map[空值:空值的字串] // 空值 → 空值的字串 println() //////////////////////////////////////////////////////////////////////////////// var mst_Map3 = make(map[chan interface{}]map[string]string) var ifch_Custom = make(chan interface{}, 4) mst_Map3[ifch_Custom] = make(map[string]string) ifch_Custom <- "anything" ifch_Custom <- 1 ifch_Custom <- 2 ifch_Custom <- 3 mst_Map3[ifch_Custom]["CH-Key"] = "CH-Value" println(mst_Map3[ifch_Custom]["CH-Key"]) //CH-Value for index1, value1 := range mst_Map3 { fmt.Printf("%v → %v\n", index1, value1) for index2, value2 := range value1 { fmt.Printf("%v → %v\n", index2, value2) } } // 0xc00009e120 → map[CH-Key:CH-Value] // CH-Key → CH-Value } ``` ### Go 切片雜湊表(map)型別(slice of map) 切片的資料型別可為map,可讓資料結構第一層計入編號,使用注意事項有: 1.切片map型別必須事先使用make()函式手動分配空間給(無法自動) ```go package main import "fmt" func main() { // 切片map型別必須手動分配容量,超過指定容量後將無法新增 var vSliceMap1 = make([]map[string]string, 2) if vSliceMap1[0] == nil { vSliceMap1[0] = make(map[string]string, 2) vSliceMap1[0]["name"] = "N001" vSliceMap1[0]["age"] = "99" } if vSliceMap1[1] == nil { vSliceMap1[1] = make(map[string]string, 2) vSliceMap1[1]["age"] = "100" vSliceMap1[1]["name"] = "N002" } //map的key為無序故難以預測排列狀況 fmt.Printf("%v", vSliceMap1) fmt.Println() // var vSliceMap2 = make([]map[string]string, 0) vSliceMap2 = append(vSliceMap2, map[string]string{"Name": "N01", "Age": "1"}) vSliceMap2 = append(vSliceMap2, map[string]string{"Name": "N02", "Age": "2"}) vSliceMap2 = append(vSliceMap2, map[string]string{"Name": "N03", "Age": "3"}) fmt.Printf("%v\n", vSliceMap2) //[map[age:99 name:N001] map[age:100 name:N002]] fmt.Printf("%v\n", vSliceMap2[0]) //map[Age:1 Name:N01] fmt.Println() fmt.Println() for index1 := 0; index1 < len(vSliceMap2); index1++ { for index2, value := range vSliceMap2[index1] { fmt.Printf("%v %v\n", index2, value) } } //使用切片map型別可依序將資料列出 // Name N01 // Age 1 // Name N02 // Age 2 // Name N03 // Age 3 } ``` ### Go 結構體(Struct) 定義與宣告 > > Golang支持物件導向程式設計(Object-oriented programming)特性,不使用其他語言常見的class指令,改使用結構體(Struct)來實現OO特性,GoLang與其他語言相比簡化了:繼承、重載(Overload)、構造函式(Constructor)、析構函式(Destructor)、隱藏參數this指針。結構體為值資料型別內可具備多種資料型別的屬性(Field),需用type指令定義其內的屬性, > > > **<span style="color: DeepSkyBlue">type </span><span style="color: DodgerBlue">結構體名稱</span> struct{**\ > >**<span style="color: lightgreen">  屬性名稱1</span> <span style="color: yellow">屬性型別1</span>**\ > >**<span style="color: lightgreen">  屬性名稱2</span> <span style="color: yellow">屬性型別2</span>**\ > > **}** > > > > *屬性的資料型別可為 ==邏輯值、數值、字串、指標、陣列、Map、結構體==\ > > *結構體的資料型別可為任何資料型別包括結構體自己本身 \ > > *定義完結構體後還須透過宣告才能成為變數   > 結構體的第一種宣告方式(僅宣告而未賦值): > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">變數名稱</span> <span style="color: lightgreen">結構體名稱</span>** > > 結構體的第二種宣告方式(依序直接賦值): > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">變數名稱</span> = <span style="color: lightgreen">結構體名稱</span>{<span style="color: yellow">屬性值1</span>,<span style="color: yellow">屬性值2</span>}** > > 結構體的第三種宣告方式(以key-value方式賦值): > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">變數名稱</span> = <span style="color: lightgreen">結構體名稱</span>{<span style="color: yellow">屬名稱1</span>:<span style="color: orange">屬性值1</span>,<span style="color: yellow">屬性值2</span>:<span style="color: orange">屬性值2</span>}** > > > > *屬性名稱以key-value方式賦值,不需依照順序 > > > 結構體的第四種宣告方式(使用new()宣告而未賦值): > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">變數名稱</span> *<span style="color: lightgreen">結構體名稱</span> = new(<span style="color: lightgreen">結構體名稱</span>)** > > > > *==(*變數名稱).屬名稱 = 屬性值== 賦值等同 ==變數名稱.屬名稱 = 屬性值== > > > 結構體的第五種宣告方式(使用指標方式宣告並賦值): > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">變數名稱</span> *<span style="color: lightgreen">結構體名稱</span> = &<span style="color: lightgreen">結構體名稱</span>{<span style="color: yellow">屬名稱1</span>:<span style="color: orange">屬性值1</span>,<span style="color: yellow">屬性值2</span>:<span style="color: orange">屬性值2</span>}** > > > > *==(*變數名稱).屬名稱 = 屬性值== 賦值等同 ==變數名稱.屬名稱 = 屬性值== > > > ```go > package main > import "fmt" > type TyStruct struct { > F0 uint > F1 string > F2,F3 string //同類型可簡寫成一行 > } > > func main() { > > var vStructDF1 TyStruct > vStructDF1.F0 = 1 > vStructDF1.F1 = "F1" > vStructDF1.F2 = "F2" > fmt.Printf("%T\nvStructDF1:\n%v\n%q\n%q\n%q", vStructDF1, vStructDF1.F0, vStructDF1.F1, vStructDF1.F2, vStructDF1.F3) > fmt.Println() > fmt.Println(&vStructDF1) > fmt.Println() > > var vStructDF2 = TyStruct{1, "F1", "F2", ""} > fmt.Printf("%T\nvStructDF2:\n%v\n%q\n%q\n%q", vStructDF2, vStructDF2.F0, vStructDF2.F1, vStructDF2.F2, vStructDF2.F3) > fmt.Println() > fmt.Println(&vStructDF2) > fmt.Println() > > var vStructDF3 = TyStruct{F0: 1, F2: "F2", F1: "F1"} > fmt.Printf("%T\nvStructDF3:\n%v\n%q\n%q\n%q", vStructDF3, vStructDF3.F0, vStructDF3.F1, vStructDF3.F2, vStructDF3.F3) > fmt.Println() > fmt.Println(&vStructDF3) > fmt.Println() > > var vStructDF4 *TyStruct = new(TyStruct) > (*vStructDF4).F0 = 1 //(*vStructDF4).F0 在這等同 vStructDF4.F0 > vStructDF4.F1 = "F1" > vStructDF4.F2 = "F2" > > fmt.Printf("%T\nvStructDF4:\n%v\n%q\n%q\n%q", vStructDF4, vStructDF4.F0, vStructDF4.F1, vStructDF4.F2, vStructDF4.F3) > fmt.Println() > fmt.Println(*vStructDF4) > fmt.Println() > > var vStructDF5 *TyStruct = &TyStruct{1, "F1", "F2", ""} > (*vStructDF5).F0 = 2 //(*vStructDF5).F0 不可寫成 *vStructDF5.F0 因運算子優先不同 > vStructDF5.F1 = "F1-" > vStructDF5.F2 = "F2-" > > fmt.Printf("%T\nvStructDF5:\n%v\n%q\n%q\n%q", vStructDF5, vStructDF5.F0, vStructDF5.F1, vStructDF5.F2, vStructDF5.F3) > fmt.Println() > fmt.Println(*vStructDF5) > > } > ``` > ### Go 結構體與指標(Pointer to struct) <img src="http://www.plantuml.com/plantuml/svg/SoWkIImgAStDuSf9JIjHACbNACfCpoXHICaiIaqkoSpFu-BYAaXCpavCJrLGUh5YnhEsRIzNJr5IK4u1WsekJiwqeiJSL8MdwnO_dTZrVEk4PykxWIfUhEjwDdM7njCv6vxERR0og30mI4n9CJ3G16dWpCjFILLmB2fAhFJCAua6YN3gwe8fcoIxfnlNF9tG-tp5HaC5XQSDUwmKNYwOy6B5MY1J0IY0PXuklKReUZ6qMk3IQapGMaCKQz_lwKL3ZniQdknU-cpjafSBvpthMm7aOgf1Cu4C1D8Q2oHgkO282nQQ2ZQwkWhmM7ZMq_vinSpdireW8bnSoJc9nSKAE1KesDWeQ3Zev1SbPsIcQ2eeG3riQM1dfr3D1GecY0O5AmL45701hS_BBqbLoCzJoCdCorPGpYzAp4jFjK9F4n2zN0wfUId010S0" style="background-color:#2e2e2e;padding:0px;"> ```go package main import "fmt" type TyStruct1 struct { F0 uint F1 string } type TyStructUser struct { start TyStructPoint end TyStructPoint } type TyStructPoint struct { x, y int } func main() { var vStructDF1 TyStruct1 vStructDF1.F0 = 1 vStructDF1.F1 = "F1" var vPointer *TyStruct1 = &vStructDF1 vPointer.F0 = 2 vPointer.F1 = "F11" fmt.Printf("%v (%p)\n", vStructDF1, &vStructDF1) //{2 F11} (0xc00004a420) fmt.Printf("%v (%p)\n", vPointer, &vPointer) //&{2 F11} (0xc000080018) fmt.Printf("vPointer → %p\n", vPointer) //vPointer → 0xc00004a420 fmt.Println() fmt.Printf("%v (%p)\n", vStructDF1.F0, &vStructDF1.F0) //2 (0xc00004a420) fmt.Printf("%v (%p)\n", vStructDF1.F1, &vStructDF1.F1) //F11 (0xc00004a428) fmt.Println() var vStructDF2 = TyStructUser{TyStructPoint{1, 2}, TyStructPoint{3, 4}} fmt.Printf("vStructDF2 (%p)\n", &vStructDF2) //vStructDF2 (0xc000052140) fmt.Printf("Start Point = (%v,%v) (%p,%p)\n", vStructDF2.start.x, vStructDF2.start.y, &vStructDF2.start.x, &vStructDF2.start.y) //Start Point = (1,2) (0xc000052140,0xc000052148) fmt.Printf("End Point = (%v,%v) (%p,%p)\n", vStructDF2.end.x, vStructDF2.end.y, &vStructDF2.end.x, &vStructDF2.end.y) //End Point = (1,2) (0xc000052140,0xc000052148) } ``` ### Go 結構體與打包問題(Package struct) ``` Package_Struct/PackageStruct.go``` ```go package PackageStruct type GlobalTyStruct struct { F0, F1, F2, F3 string } type GlobalButLocalTyStruct struct { f0, f1, f2, f3 string } type localTyStruct struct { f0, f1, f2, f3 string } type localButGlobalTyStruct struct { F0, F1, F2, F3 string } func LocalExportPort(InputF0 string, InputF1 string, InputF2 string, InputF3 string) *localTyStruct { return &localTyStruct{f0: InputF0, f1: InputF1, f2: InputF2, f3: InputF3} } ``` ``` main.go``` ```go package main import ( PackageStruct "Lesson3/Struct與打包/Package_Struct" "fmt" ) func main() { var vStructDF1 = PackageStruct.GlobalTyStruct{F0: "F0", F1: "F1", F2: "F2"} fmt.Println(vStructDF1) fmt.Println() // var vStructDF2 = PackageStruct.GlobalButLocalTyStruct{F0: "F0", F1: "F1", F2: "F2"} // fmt.Println(vStructDF2) //unexported // fmt.Println() // var vStructDF3 = PackageStruct.localTyStruct{F0: "F0", F1: "F1", F2: "F2"} // fmt.Println(vStructDF3) //unexported // fmt.Println() // var vStructDF4 = PackageStruct.localButGlobalTyStruct{F0: "F0", F1: "F1", F2: "F2"} // fmt.Println(vStructDF4) //unexported // fmt.Println() fmt.Println(*PackageStruct.LocalExportPort("funcF0", "funcF1", "funcF2", "")) fmt.Println() } ``` ### Go 結構體雜湊表(map)型別(struct of map) ```go package main import "fmt" type st_Example struct { id uint64 name string data interface{} } func FC_newStruct(id uint64, name string, data interface{}) *st_Example { return &st_Example{id, name, data} } func (r *st_Example) Update(id uint64, name string, data interface{}) { r.id = id r.name = name r.data = data return } func main() { var ex1 *st_Example ex1 = FC_newStruct(1, "name1", "data1") fmt.Println(ex1, ex1.name) //&{1 name1 data1} name1 var ex2 st_Example //struct必須作為變數必須是實參 ex2.Update(2, "name2", "data2") fmt.Println(ex2, ex2.name) //{2 name2 data2} name2 var mpExample = make(map[string]*st_Example) mpExample["key"] = &st_Example{} //Update前必須存入struct mpExample["key"].Update(91, "name91", "data91") fmt.Println(mpExample["key"], mpExample["key"].name) //&{92 name92 data92} name92 delete(mpExample, "key") mpExample["key"] = &st_Example{0, "name92", "data92"} mpExample["key"].id = 92 fmt.Println(mpExample["key"], mpExample["key"].name) //&{91 name91 data91} name91 //map-*struct寫入非常簡便 } ``` ### Go 方法與函式 (Method&Function) > 1. 函式使用方式為 ``` 函式名(變數列表) ```,方法使用方式為 ``` 變數.方法名(變數列表) ``` > 2. 當資料型別為值型別時,函式(Function)不能直接將指標當作變數輸入,反之方法(Method)可將變數與指標當作自訂型別變數直接輸入,方法(Method)可定義自訂型別變數影響範圍 > > Method宣告方式: > > **<span style="color: DeepSkyBlue">func </span>(<span style="color: DodgerBlue">自訂型別名稱</span> <span style="color: DeepSkyBlue">自訂型別</span>) <span style="color: lightgreen">方法名稱</span>(<span style="color: yellow">輸入變數列表</span>) (<span style="color: orange">輸出變數列表</span>) {\ > ><span style="color: red">  方法內容...</span>\ > >  return <span style="color: orange">輸出值</span>\ > > }** > > > > *輸入、輸出變數非必需的,有輸出變數則一定要使用 ``` return ``` 返回值\ > > *方法(Method)必須綁定於自訂型別,必須有自訂型別變數才能使用\ > > *編譯器對於 ```(&變數名稱).方法名稱()``` 與 ``` 變數名稱.方法名稱()``` 兩者等價\ > > *方法之自訂型別可定義 ```引用或值型別```,設定其作用範圍(不因指標而影響) > > ```go > package main > import "fmt" > > func fcInputString(input string) { > input = "Changed" > fmt.Printf("fcInputString = %v\n", input) > } > > func fcInputPString(input *string) { > *input = "Changed" > fmt.Printf("fcInputPString = %v\n", *input) > } > > type TyInput string > > func (input TyInput) mtInputString() { > input = "Changed" > fmt.Printf("mtInputString = %v\n", input) > } > > func (input *TyInput) mtInputPString() { > *input = "Changed" > fmt.Printf("mtInputPString = %v\n", *input) > } > > func main() { > > var vString1 string > > vString1 = "未改變" > fcInputString(vString1) > fmt.Printf("檢查vString1 = %v\n", vString1) > fmt.Println() > vString1 = "未改變" > fcInputPString(&vString1) > fmt.Printf("檢查vString1 = %v\n", vString1) > // fcInputString = Changed > // 檢查vString1 = 未改變 > // fcInputPString = Changed > // 檢查vString1 = Changed > // func傳入之值型別變數影響範圍僅在func內(除非使用全域變數) > // func若傳入指標則影響範圍可直接改寫該指標指定的變數 > > fmt.Println() > fmt.Println() > var vString2 TyInput > > vString2 = "未改變" > vString2.mtInputString() > fmt.Printf("檢查vString1 = %v\n", vString2) > fmt.Println() > vString2 = "未改變" > (&vString2).mtInputString() > fmt.Printf("檢查vString1 = %v\n", vString2) > // mtInputString = Changed > // 檢查vString1 = 未改變 > // mtInputString = Changed > // 檢查vString1 = 未改變 > // 因編譯器中(&vString2).mtInputString()與 vString2.mtInputString()等價 > // Method傳入之自訂型別可藉由指定值型別,將影響範圍僅在func內(除非使用全域變數) > > fmt.Println() > fmt.Println() > vString2 = "未改變" > vString2.mtInputPString() > fmt.Printf("檢查vString1 = %v\n", vString2) > fmt.Println() > vString2 = "未改變" > (&vString2).mtInputPString() > fmt.Printf("檢查vString1 = %v\n", vString2) > // mtInputPString = Changed > // 檢查vString1 = Changed > // mtInputPString = Changed > // 檢查vString1 = Changed > // 因編譯器中(&vString2).mtInputString()與 vString2.mtInputString()等價 > // Method傳入之自訂型別可藉由指定引用型別,將影響範圍擴展至func外 > } > ``` ### Go 方法與結構體應用 (Method&struct): ```go package main import ( "fmt" ) const pi = 3.14159265359 type Radius struct { Radius float64 } func (r Radius) mtAreaV() float64 { return pi * r.Radius * r.Radius } func (r *Radius) mtAreaR() float64 { return pi * r.Radius * r.Radius } func main() { var vR = Radius{10} fmt.Printf("%v", vR.mtAreaV()) fmt.Println() fmt.Printf("%v", vR.mtAreaR()) //雖然兩者方法輸出相同,但引用型別在處理上所耗費記憶體較低(因省略變數值的複製) } ``` ### Go 方法調用驗證性能: ```go= package main import ( "fmt" "log" "reflect" "time" ) /////////////////////////////////////////// func StartTimer() time.Time { return time.Now() } func SubTimer(input time.Time) float64 { return time.Now().Sub(input).Seconds() * 1000 } ///////////////////////////////////////// type ty_Json struct { Name string `json:"jsname"` Age int `json:"jsage"` } func main() { st_User := ty_Json{ "測試", 20, } var TestCount = 99999 var vTime1 = StartTimer() for index := 0; index < TestCount; index++ { for index := 0; index < reflect.ValueOf(st_User).NumField(); index++ { if reflect.ValueOf(st_User).Field(index).CanInterface() { //判斷是否可導出屬性 fmt.Sprintf("%v (%v %v %v)\n", reflect.ValueOf(st_User).Field(index).Interface(), reflect.TypeOf(st_User).Field(index).Name, reflect.TypeOf(st_User).Field(index).Type, reflect.TypeOf(st_User).Field(index).Tag) } } } log.Printf(" 耗費:%.4f ms ", SubTimer(vTime1)) //2019/09/30 15:22:26 耗費:433.0247 ms v := reflect.ValueOf(st_User) t := reflect.TypeOf(st_User) var vTime2 = StartTimer() for index := 0; index < TestCount; index++ { for index := 0; index < v.NumField(); index++ { if v.Field(index).CanInterface() { //判斷是否可導出屬性 fmt.Sprintf("%v (%v %v %v)\n", v.Field(index).Interface(), t.Field(index).Name, t.Field(index).Type, t.Field(index).Tag) } } } log.Printf(" 耗費:%.4f ms ", SubTimer(vTime2)) //2019/09/30 15:22:26 耗費:195.0111 ms // //宣告變數進行方法調用效能較佳 } ``` ### Go 方法String()使用特例: ```go package main import ( "fmt" ) type tyID struct { id int name string } func (vID *tyID) String() string { return fmt.Sprintf("log: id=%v name=%v", (*vID).id, (*vID).name) } func (vID *tyID) Test() string { return fmt.Sprintf("log: id=%v name=%v", (*vID).id, (*vID).name) } func main() { var dataID tyID dataID.id = 1 dataID.name = "User001" fmt.Println(&dataID) //log: id=1 name=User001 fmt.Println(dataID.Test()) //log: id=1 name=User001 //String()將會作為fmt.Println與fmt.Printf的預設輸出格式 //相比Test()可以簡化指令 } ``` ### Go 方法重寫與繼承: ```go= package main import ( "fmt" ) type animal struct { name string spark string } type dog struct { animal //繼承(Inheritance) age uint } type cat struct { animal age int } func (ty_input animal) Spark() { //重寫(Override) fmt.Printf("%v → 任何動物都會叫 :「 %v」 \n", ty_input.name, ty_input.spark) } func (ty_input dog) Spark() { fmt.Printf("%v → %v歲 的狗會汪汪叫 :「 %v」 \n", ty_input.name, ty_input.age, ty_input.spark) } func main() { st_Animal := animal{"Unknow", "Wu Wu"} st_Animal.Spark() //Unknow → 任何動物都會叫 :「 Wu Wu」 dog := dog{animal{"土豆", "wang~ wang~ wang~"}, 4} dog.Spark() //土豆 → 4歲 的狗會汪汪叫 :「 wang~ wang~ wang~」 cat := cat{animal{"小黑", "喵~ 喵~"}, 4} cat.Spark() //小黑 → 任何動物都會叫 :「 喵~ 喵~」 } ``` ### Go 嵌入結構體組合型別(Embedded Struct Field): Golang沒有類(class),Go語言的結構體(struct)和其它程式語言的類(class)有同等的地位,可以理解Golang是基於struct來實現OOP繼承(Inheritance)特性。 1.結構體嵌入了另一個結構體並且定義該屬性名稱,稱為「嵌入具名結構體組合型別」\ 2.結構體嵌入了另一個結構體並且無定義該屬性名稱,稱為「嵌入匿名結構體組合型別」 ``` Package_Struct/PackageStruct.go``` ```go package PackageStruct type NDUser struct { ID uint Name string NData NDUserdata //具名屬性 } type NDUserdata struct { Level uint Age uint } type AyUser struct { ID uint Name string AyUserdata //匿名屬性 } type AyUserdata struct { Level uint Age uint } ``` ``` main.go``` ``` go package main import ( PackageStruct "Lesson3/Struct組合型別(具名vs匿名)/Package_Struct" "fmt" ) type NDUser struct { ID uint Name string NData NDUserdata } type NDUserdata struct { Level uint Age uint } type AyUser struct { ID uint Name string AyUserdata } type AyUserdata struct { Level uint Age uint } func main() { ////////////////////////////////////////////////////////////////////////////// var tyNewNDUser = NDUser{ ID: 1, Name: "NDUser001", NData: NDUserdata{Level: 0, Age: 11}, } fmt.Printf("嵌入具名結構體組合型別 : \n%v", tyNewNDUser) fmt.Println() var tyNewAyUser = AyUser{ ID: 1, Name: "AyUser001", AyUserdata: AyUserdata{Level: 0, Age: 11}, } fmt.Printf("嵌入匿名結構體組合型別 : \n%v", tyNewAyUser) fmt.Println() fmt.Println() // 嵌入具名結構體組合型別 : // {1 NDUser001 {0 11}} // 嵌入匿名結構體組合型別 : // {1 AyUser001 {0 11}} ////////////////////////////////////////////////////////////////////////////// var tyPKNewAyUser = PackageStruct.AyUser{ ID: 2, Name: "AyUser002", AyUserdata: PackageStruct.AyUserdata{Level: 1, Age: 22}, } fmt.Printf("嵌入具名結構體組合型別(引入包) : \n%v", tyPKNewAyUser) fmt.Println() var tyPKNewNDUser = PackageStruct.NDUser{ ID: 2, Name: "NDUser002", NData: PackageStruct.NDUserdata{Level: 1, Age: 22}, } fmt.Printf("嵌入匿名結構體組合型別(引入包) : \n%v", tyPKNewNDUser) fmt.Println() // 嵌入具名結構體組合型別(引入包) : // {2 AyUser002 {1 22}} // 嵌入匿名結構體組合型別(引入包) : // {2 NDUser002 {1 22}} } ``` 3.結構體嵌入了多個匿名結構體,可透過這個結構直接訪問多個匿名結構體,以實現多重繼承 ```go package main import "fmt" type Camera struct { Name string } func (_ *Camera) takePicture() string { return "Click" } type Phone struct { Name string } func (_ *Phone) call() string { return "Ring Ring" } type CameraPhone struct { Name string Camera Phone } func main() { var vDevice1 = Camera{Name: "NewCamera"} fmt.Printf("%v : %v\n", vDevice1.Name, vDevice1.takePicture()) var vDevice2 = CameraPhone{Name: "NewCameraPhone", Camera: Camera{Name: "NewCamera"}} //vDevice2.Camera.Name = "NewCamera" //左段語法較為簡潔實現匿名屬性之結構體組合 vDevice2.Phone.Name = "NewPhone" fmt.Printf("%v : %v → %v\n", vDevice2.Name, vDevice2.Phone.Name, vDevice2.Phone.call()) fmt.Printf("%v : %v → %v\n", vDevice2.Name, vDevice2.Camera.Name, vDevice2.Camera.takePicture()) fmt.Printf("%v : %v → %v\n", vDevice2.Name, vDevice2.Phone.Name, vDevice2.call()) fmt.Printf("%v : %v → %v\n", vDevice2.Name, vDevice2.Camera.Name, vDevice2.takePicture()) //多個匿名結構體所形成多重繼承,編譯器會自動搜尋關鍵字簡化語法 // NewCamera : Click // NewCameraPhone : NewPhone → Ring Ring // NewCameraPhone : NewCamera → Click // NewCameraPhone : NewPhone → Ring Ring // NewCameraPhone : NewCamera → Click } ``` ### Go 接口定義(Interface): > Go的官方文件並未明確指出接口(Interface)動態、靜態的型別分類(Dynamic、Static),但《Go语言核心编程》(李文塔) 的分析指出接口可由Duck型別做出動靜態分類 (https://blog.golang.org/laws-of-reflection) 接口為引用型別之多態變數,它可隨實踐方法(Method)的不同定義各種變數型態(type)的行為。舉例如下宣告一空接口並賦值,其輸出結果之變數型態將隨賦值而改變(相當於使用 ``` var := value``` 的效果), ```go= package main import ( "fmt" ) type Phone interface { Who() } type NokiaPhone struct { } func (_ *NokiaPhone) Who() { fmt.Println("I am Nokia!") } type ApplePhone struct { } func (_ *ApplePhone) Who() { fmt.Println("I am ApplePhone!") } func main() { var vP Phone vP = &NokiaPhone{} vP.Who() //I am Nokia! vP = &ApplePhone{} vP.Who() //I am ApplePhone! } ``` ```go= package main type if_A interface { mt1() } type if_B interface { if_A mt2() } type if_C interface { String() } type ty_Example struct{} func (ty_Example) mt1() { println("use mt1") } func (ty_Example) mt2() { println("use mt2") } func main() { var st_Exampe = ty_Example{} st_Exampe.mt1() //use mt1 st_Exampe.mt2() //use mt2 println() var if_ty_Example interface{} = ty_Example{} if TypeAssertion, check := if_ty_Example.(if_A); check { TypeAssertion.mt1() //TypeAssertion.mt2() //if_A接口無mt2()之方法 } //use mt1 if TypeAssertion, check := if_ty_Example.(if_B); check { TypeAssertion.mt1() TypeAssertion.mt2() } //use mt1 //use mt2 if TypeAssertion, check := if_ty_Example.(if_C); check { println(TypeAssertion) //此段亦不會執行,因if_C斷言失敗 } TypeAssertion := if_ty_Example.(if_C) //panic: interface conversion: main.ty_Example is not main.if_C: missing method String println(TypeAssertion) //此段亦不會執行,因if_C斷言失敗 } ``` ### Go 接口型別斷言與型別查詢(Type assertions&Switches): ```go= package main import "fmt" func main() { var vInterface interface{} fcDisplay(vInterface) // Value='<nil' Type='<nil') if vInterface == vInterface { println("相等") } vInterface = 0 fcDisplay(vInterface) // Value='0' Type='int') vInterface = -1 fcDisplay(vInterface) // Value='-1' Type='int') vInterface = 1.1 fcDisplay(vInterface) // Value='1.1' Type='float64') vInterface = 10e10 fcDisplay(vInterface) // Value='1e+11' Type='float64') vInterface = 'c' fcDisplay(vInterface) // Value='99' Type='int32') vInterface = '北' fcDisplay(vInterface) // Value='21271' Type='int32') vInterface = "ssss" fcDisplay(vInterface) // Value='ssss' Type='string') vInterface = [...]int{1, 2, 3} fcDisplay(vInterface) // Value='[1 2 3]' Type='[3]int') vInterface = []int{1, 2, 3} fcDisplay(vInterface) // Value='[1 2 3]' Type='[]int') vInterface = func(input int) int { return input } fcDisplay(vInterface) // Value='0x493350' Type='func(int) int') vInterface = map[string]string{"A": "aaAA", "B": "bbBB"} fcDisplay(vInterface) // Value='map[A:aaAA B:bbBB]' Type='map[string]string') vInterface = TyStruct{0} fcDisplay(vInterface) // Value='{0}' Type='main.TyStruct') vInterface = &TyStruct{0} fcDisplay(vInterface) //Value='&{0}' Type='*main.TyStruct') vInterface2 := &TyStruct{0} fmt.Println(vInterface == vInterface2) //false //介面作為實值型別變數皆可做邏輯判斷,參考型別除指標以外其餘皆不行 // var vInterface3 interface{} // vInterface3 = []int{1, 2, 3} // if vInterface3 == vInterface3 { // println("相等") // } //runtime error: comparing uncomparable type []int // 空接口斷言為參考型別則不能做邏輯比較 } type TyStruct struct { F0 uint } func fcDisplay(this interface{}) { fmt.Printf("Value='%v' Type='%T')\n", this, this) } ``` ```go= package main import "fmt" func fc_BaseTypeAssertion(emif interface{}) { switch value := emif.(type) { case bool: fmt.Printf("bool: %v(%T)\n", value, value) case complex128: fmt.Printf("complex128: %v(%T)\n", value, value) case complex64: fmt.Printf("complex64: %v(%T)\n", value, value) case float32: fmt.Printf("float32: %v(%T)\n", value, value) case float64: fmt.Printf("float64: %v(%T)\n", value, value) case int: fmt.Printf("int: %v(%T)\n", value, value) case int16: fmt.Printf("int16: %v(%T)\n", value, value) case int32: fmt.Printf("int32: %v(%T)\n", value, value) case int64: fmt.Printf("int64: %v(%T)\n", value, value) case int8: fmt.Printf("int8: %v(%T)\n", value, value) case string: fmt.Printf("string: %v(%T)\n", value, value) case uint: fmt.Printf("uint: %v(%T)\n", value, value) case uint16: fmt.Printf("uint16: %v(%T)\n", value, value) case uint32: fmt.Printf("uint32: %v(%T)\n", value, value) case uint64: fmt.Printf("uint64: %v(%T)\n", value, value) case uint8: fmt.Printf("uint8: %v(%T)\n", value, value) case uintptr: fmt.Printf("uintptr: %v(%T)\n", value, value) default: fmt.Printf("%v(%T)\n", value, value) } } func main() { fc_BaseTypeAssertion(nil) fc_BaseTypeAssertion(1) var vu64 uint64 = 18446744073709551615 fc_BaseTypeAssertion(vu64) fc_BaseTypeAssertion("xxxString") fc_BaseTypeAssertion(&vu64) fcA := func(x int) { println(x) } fc_BaseTypeAssertion(fcA) //<nil>(<nil>) // int: 1(int) // uint64: 18446744073709551615(uint64) // string: xxxString(string) // 0xc0000540b8(*uint64) // 0x493ed0(func(int)) } ``` ### Go 管道(channel)宣告與使用 > 管道(channel)作為Go的一引用型資料型別,可以存放(寫入)任何資料型別作為數據(元素)保存,其特點為取出管道內元素必須遵守「**依序先進先出**」、「**進入管道後不可修改或截取**」、「**管道關閉後只能取出不能再寫入**」,管道必須透過 ``make`` 函式創建: > > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">管道名稱</span> = make(chan <span style="color: lightgreen">元素型別</span>,<span style="color: yellow">元素容量</span>)** > > 管道可透過 ``<-`` 符號寫入值或取出元素,也可作為宣告該管道限定寫入或取出,期宣告方式如下: > > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">限寫管道名稱</span> = make(chan <- <span style="color: lightgreen">元素型別</span>,<span style="color: yellow">元素容量</span>)** > > **<span style="color: DeepSkyBlue">var </span><span style="color: DodgerBlue">限取管道名稱</span> = make(<-chan <span style="color: lightgreen">元素型別</span>,<span style="color: yellow">元素容量</span>)** > > > > *宣告管道時若無限定寫入或取出,即表示該管道是雙向的(可寫入和取出)\ > > *管道元素型別相同則視為相同型別,不受限定寫入或取出影響\ > > *雙向的管道可轉換為限定寫入或取出(單向);單向的管道後不可轉換成雙向或異相; > > ```go= > package main > import "fmt" > func main() { > > var ifch_Test = make(chan interface{}, 10) > > fmt.Printf("%v(%T)\n", ifch_Test, ifch_Test) //0xc000088000(chan interface {}) > > fmt.Printf("%v\n", len(ifch_Test)) //0 > ifch_Test <- "jack" > fmt.Printf("%v\n", len(ifch_Test)) //1 > ifch_Test <- 2 > fmt.Printf("%v\n", len(ifch_Test)) //2 > type ty_Example struct { > F0 string > F1 string > } > ifch_Test <- ty_Example{F0: "屬性值0", F1: "屬性值1"} > fmt.Printf("%v/%v\n", len(ifch_Test), cap(ifch_Test)) //3/10 > var if_empty interface{} > ifch_Test <- if_empty > fmt.Printf("%v/%v\n", len(ifch_Test), cap(ifch_Test)) //4/10 > > var if_cache interface{} > if_cache = <-ifch_Test > fmt.Printf("%v(%T)\n", if_cache, if_cache) //jack(string) > if_cache = <-ifch_Test > fmt.Printf("%v(%T)\n", if_cache, if_cache) //2(int) > if_cache = <-ifch_Test > fmt.Printf("%v(%T)\n", if_cache, if_cache) //{屬性值0 屬性值1}(main.ty_Example) > fmt.Printf("%v(%T)\n", if_cache.(ty_Example).F0, if_cache.(ty_Example).F0) //屬性值0(string),對空接口之結構體型別斷言 > if_cache = <-ifch_Test > fmt.Printf("%v(%T)\n", if_cache, if_cache) //<nil>(<nil>) 回傳空接口 > if_cache = 10 > fmt.Printf("%v(%T)\n", if_cache, if_cache) //10(int) 對空接口賦值同時斷言 > > // if_cache = <-ifch_Test //channel提取為空而導致錯誤 > // fmt.Printf("%v(%T)\n", if_cache, if_cache) > > } > > ``` > > ``` go= > package main > > import ( > "fmt" > ) > > func main() { > > vsch_1 := make(chan string) > vsch_2 := make(chan string) > > for index := 0; index < 100; index++ { > go func() { > vsch_1 <- "one" > }() > go func() { > vsch_2 <- "two" > }() > > for i := 0; i < 2; i++ { > select { > case select1 := <-vsch_1: > fmt.Println("received:", select1) > case select2 := <-vsch_2: > fmt.Println("received:", select2) > default: > fmt.Println("default") > } > } > } > > // default > // default > // default > // received: one > // received: one > // received: one > // received: one > // received: one > // received: one > // received: two > // received: two > // received: two > // received: one > // ...... 隨機選擇通道 > } > > ``` ### Go 管道(channel)型別驗證: > ```go= > package main > > import "fmt" > > type intChan chan int > type intWChan chan<- int > > func main() { > ch01 := make(chan int) // 0xc00003e060 (chan int) > ch02 := make(chan int) // 0xc00003e0c0 (chan int) > if ch01 != ch02 { > fmt.Printf("%v != %v\n", ch01, ch02) > } //0xc00003e060 != 0xc00003e0c0 > fmt.Println() > > chSameType1 := (chan int)(ch01) > chSameType2 := (chan int)(chSameType1) > chSameType1 = chSameType2 // 0xc00003e060 (chan int) > chSameType2 = ch02 // 0xc00003e0c0 (chan int) > > chCustomType := (intChan)(ch01) > chCustomType = ch02 // 0xc00003e0c0 (main.intChan) > //同型別可直接賦值,也可轉換成相同型別(包括自定型別) > > fmt.Printf("%v (%T)\n", ch01, ch01) // 0xc00003e060 (chan int) > fmt.Printf("%v (%T)\n", ch02, ch02) // 0xc00003e0c0 (chan int) > fmt.Printf("%v (%T)\n", chSameType1, chSameType1) // 0xc00003e060 (chan int) > fmt.Printf("%v (%T)\n", chSameType2, chSameType2) // 0xc00003e0c0 (chan int) > fmt.Printf("%v (%T)\n", chCustomType, chCustomType) // 0xc00003e0c0 (main.intChan) > fmt.Println() > > chWriteOnly1 := (chan<- int)(ch01) > chTakeOnly1 := (<-chan int)(ch01) > > //chWriteOnly2 := (chan int)(chWriteOnly1) //cannot convert chWriteOnly1 (type chan<- int) to type chan int > //chTakeOnly2 := (chan int)(chWriteOnly1) //cannot convert chWriteOnly1 (type chan<- int) to type chan int > //chWriteOnly2 = (chan<- int)(chTakeOnly1) //cannot convert chTakeOnly1 (type <-chan int) to type chan<- int > //chTakeOnly2 := (<-chan int)(chWriteOnly1) //cannot convert chWriteOnly1 (type chan<- int) to type <-chan int > > chWriteOnly2 := (chan<- int)(ch01) > chTakeOnly2 := (<-chan int)(ch01) > chConvertCheck := (intWChan)(chWriteOnly2) > fmt.Printf("%v (%T)\n", chConvertCheck, chConvertCheck) // 0xc00003e060 (main.intWChan) > fmt.Println() > > fmt.Printf("%v (%T)\n", chWriteOnly2, chWriteOnly2) // 0xc00003e060 (chan<- int) > fmt.Printf("%v (%T)\n", chTakeOnly2, chTakeOnly2) // 0xc00003e060 (<-chan int) > fmt.Println() > > fmt.Printf("%v (%T)\n", chWriteOnly1, chWriteOnly1) // 0xc00003e060 (chan<- int) > fmt.Printf("%v (%T)\n", chTakeOnly1, chTakeOnly1) // 0xc00003e060 (<-chan int) > chWriteOnly1 = ch01 > chTakeOnly1 = ch01 > fmt.Printf("%v (%T)\n", chWriteOnly1, chWriteOnly1) // 0xc00003e060 (chan<- int) > fmt.Printf("%v (%T)\n", chTakeOnly1, chTakeOnly1) // 0xc00003e060 (<-chan int) > fmt.Printf("%v (%T)\n", ch01, ch01) // 0xc00003e060 (chan int) > //chWriteOnly = chTakeOnly //指向不同無法直接賦值 : cannot use chTakeOnly (type <-chan int) as type chan<- int in assignment > > func01(chWriteOnly1, chTakeOnly1) > // 0xc00003e060 (chan<- int) > // 0xc00003e060 (<-chan int) > func01(ch01, ch01) > // 0xc00003e060 (chan<- int) > // 0xc00003e060 (<-chan int) > //fmt.Println() > > func02(ch01, ch01) > //0xc00003e060 (chan<- int) > //0xc00003e060 (<-chan int) > > } > > func func01(f_chWriteOnly chan<- int, f_chTakeOnly <-chan int) { > fmt.Printf("%v (%T)\n", f_chWriteOnly, f_chWriteOnly) > fmt.Printf("%v (%T)\n", f_chTakeOnly, f_chTakeOnly) > } > > func func02(f_chA chan int, f_chB chan int) { > f_chWriteOnly := (chan<- int)(f_chA) > f_chTakeOnly := (<-chan int)(f_chB) > fmt.Printf("%v (%T)\n", f_chWriteOnly, f_chWriteOnly) > fmt.Printf("%v (%T)\n", f_chTakeOnly, f_chTakeOnly) > } > > ``` > ### Go 管道(channel)之panic問題: > 管道的使用必須謹慎,以下五種狀況皆會讓程式執行發生 **panic**: > > 1. 向裝滿元素的管道繼續寫入元素(**deadlock**) > > 2. 向已關閉的管道繼續寫入元素(**deadlock**) > > 3. 向無元素且未關閉的管道繼續取出元素(**deadlock**) > > 4. 向無元素且未關閉的管道繼續取出元素(**deadlock**) > > 5. 向已關閉的管道再次發出關閉 (重複關閉管道)(**panic: close of closed channel**) > > 管道可透過 ``close(管道名稱)`` 函式關閉,關閉後的管道不能再寫入元素,向無元素且已關閉的管道取值會得到該管道元素型別的預設值;利用 ``<-`` 取值時會同時回傳一邏輯值用以判斷該管道是否已關閉(**需注意:必須透過向管道取值,才能真正確認管道是否關閉**),為了安全使用管道的功能,可透過 ``len(管道名稱)`` 計算管道元素數、 ``cap(管道名稱)`` 計算管道容量,以即defer-recover...等技巧配合使用。 > > ```go= > package main > import "fmt" > func main() { > > var vich_Example = make(chan int, 10) > for index, max := 0, cap(vich_Example); index < max; index++ { > vich_Example <- index > } //寫入前先計算長度,避免管道容量已滿再寫入導致panic > > close(vich_Example) //關閉管道避免,管道元素取光後產生panic(deadlock)) > for value := range vich_Example { //close 管道 + for-range解析管道可避免管道內無元素產生panic > fmt.Printf("%v(%T)\n", value, value) > } > > fmt.Printf("%v\n", len(vich_Example)) //0 取值完後管道內資料將消失 > > if _, check := <-vich_Example; !check { > fmt.Printf("通道已關閉\n") > } > //注意對管道取值檢測可得到回傳邏輯值, > //但若邏輯值為true則表示管道元素將被取出(無法避免) > > // 0(int) > // 1(int) > // 2(int) > // 3(int) > // 4(int) > // 5(int) > // 6(int) > // 7(int) > // 8(int) > // 9(int) > // 0 > // 通道已關閉 > > } > > ``` > > ```go= > package main > import "fmt" > func main() { > > var ifch_Example = make(chan interface{}, 10) > //空接口型別的管道是為最簡便的使用方式 > > /////////////////////////////////////////////////////////// > ifch_Example <- "寫入字串" > for index, max := 0, cap(ifch_Example)-len(ifch_Example); index < max; index++ { > ifch_Example <- index > } //寫入前先計算長度與容量,避免管道容量已滿再寫入導致panic > fmt.Println() > /////////////////////////////////////////////////////////// > > /////////////////////////////////////////////////////////// > close(ifch_Example) //關閉管道避免,管道元素取光後產生panic > var err error > err = fc_CloseEIFChannel(ifch_Example) > if err != nil { > fmt.Printf("管道關閉異常 - %v\n", err) > } //設置二次關閉通道警告,避免panic產生中斷執行 > fmt.Println() > /////////////////////////////////////////////////////////// > > /////////////////////////////////////////////////////////// > for index, max := 0, len(ifch_Example); index < max; index++ { > fmt.Printf("%v\n", <-ifch_Example) > } //管道關閉後仍能取值,但關閉後的管道無法再寫值 > fmt.Println() > /////////////////////////////////////////////////////////// > > /////////////////////////////////////////////////////////// > err = fc_WriteToEIFChannel(ifch_Example, 99) > if err != nil { > fmt.Printf("管道寫入異常 - %v\n", err) > } //設置通道寫入警告,避免panic產生中斷執行 > fmt.Println() > /////////////////////////////////////////////////////////// > > /////////////////////////////////////////////////////////// > if fc_IsEIFChannelEmpytClose(ifch_Example) { > fmt.Printf("管道內已無元素且已關閉\n") > } //可對管道進行元素數檢測和關閉檢測,避免因檢測同時元素取出 > fmt.Println() > /////////////////////////////////////////////////////////// > //本程式碼展示如何利用空接口安全的關閉/寫入管道: > //---------------------------------------------------- > // 管道關閉異常 - close of closed channel > //---------------------------------------------------- > // 寫入字串 > // 0 > // 1 > // 2 > // 3 > // 4 > // 5 > // 6 > // 7 > // 8 > //---------------------------------------------------- > // 管道寫入異常 - send on closed channel > //---------------------------------------------------- > // 管道內已無元素且已關閉 > //---------------------------------------------------- > > } > > func fc_WriteToEIFChannel(channel chan interface{}, value interface{}) (err error) { > defer func() { > if err_recover := recover(); err_recover != nil { > //fmt.Printf("%v(%T)\n", err_recover, err_recover) > //管道寫入失敗,通常原因為該管道已經關閉 > err = err_recover.(error) > } > }() > > channel <- value > return > } > > func fc_CloseEIFChannel(channel chan interface{}) (err error) { > defer func() { > if err_recover := recover(); err_recover != nil { > //fmt.Printf("%v(%T)\n", err_recover, err_recover) > //管道關閉失敗,通常原因為該管道已經關閉,或者輸入資料並非管道 > err = err_recover.(error) > } > }() > > close(channel) > return > } > > func fc_IsEIFChannelEmpytClose(channel chan interface{}) bool { > if _, check := <-channel; !check && len(channel) == 0 { > return true > } else { > return false > } > //可對管道進行元素數檢測和關閉檢測,避免因檢測同時元素取出 > } > > ``` > 通道-map 應用 : https://play.golang.org/p/eXwppMgHR3 ### Go 實現多核並行計算(goroutines): > > > > 各作業系統大多將程式的運作分為:「程序(process)」、「執行緒(thread)」、「服務(Background process)」,Golang並行處理機制使用 **goroutines** 是為golang中最基本的執行單元,使用 ``go`` 作為使用語法。 > > > 無控制使用: > ```go= > package main > func main() { > > for index := 0; index < 10; index++ { > go fc_Test01() > go fc_Test02() > print("X") > } > // XAABBXXXBBAXBXAXBBAAXBAXBAXB > // XXBAABBXABAXXBBAXXBAXBAXBABXA > // XBABXABAXXBBAXABAXXABAXBBAXX > // XBBXAABXABAXXBABXABAXXBBAXABAX > // 執行結果之順序與次數將無法獲得保證 > } > > func fc_Test01() { > print("A") > } > > func fc_Test02() { > print("B") > } > > ``` > 利用sync包(保證每個回權執行次數相等): > ```go= > package main > import "sync" > > var syncWG sync.WaitGroup > > func main() { > > for index := 0; index < 1000; index++ { > > syncWG.Add(2) > > go fc_Test01() > go fc_Test02() > > syncWG.Wait() > print("X") > } > > //ABXBAXBAXBAXBAXBAXBAXBAXBAXBAX > //BAXABXABXABXBAXBAXBAXBAXBAXBAX > //執行結果之次數能獲得保證,但次序仍不一定 > > } > > func fc_Test01() { > defer syncWG.Done() > print("A") > } > > func fc_Test02() { > defer syncWG.Done() > print("B") > > } > ``` > 利用select關閉(goroutines): > ```go= > package main > > import ( > "fmt" > "log" > "runtime" > "time" > ) > > var vbch_Done = make(chan bool) > > func main() { > > var input bool > go func() { > for { > select { > case <-vbch_Done: > fmt.Println("vbch_Done elemt check(A) : ", len(vbch_Done)) > return > default: > log.Println("A Still working !") > time.Sleep(time.Second * 2) > } > > } > }() > > go func() { > for { > select { > case <-vbch_Done: > fmt.Println("vbch_Done elemt check(B) : ", len(vbch_Done)) > return > default: > log.Println("B Still working !") > time.Sleep(time.Second * 2) > } > > } > }() > > go func() { > for { > select { > case <-vbch_Done: > fmt.Println("vbch_Done elemt check(C) : ", len(vbch_Done)) > return > default: > log.Println("C Still working !") > time.Sleep(time.Second * 2) > } > > } > }() > > fmt.Printf("input anything to stop it,number of goroutines:%v\n", runtime.NumGoroutine()) > vi_MaxNumGoroutine := runtime.NumGoroutine() //紀錄最高Goroutine數量 > for { > fmt.Scanf("%v", &input) > fmt.Printf("Passed : %v , number of goroutines:%v\n", input, runtime.NumGoroutine()) > if vi_MaxNumGoroutine-runtime.NumGoroutine() < (vi_MaxNumGoroutine - 1) { //偵測Goroutine數量是否低於已將 A/B/C關閉數 > vbch_Done <- true > } else { > fmt.Println("All Goroutines stopped") > } > > } > > // input anything to stop it: > // 2020/02/10 21:47:58 A Still working ! > // 2020/02/10 21:47:58 B Still working ! > // 2020/02/10 21:47:58 C Still working ! > // Scan任一輸入後會將其中一個goroutines中斷 > > } > > ``` ### Go 驗證多核並行計算(goroutines): > 不要通过共享内存来通信, 而要通过通信来共享内存” --- 出自《 Effective Go > golang 的設計讓 concurrency 非常容易。 > https://blog.golang.org/concurrency-is-not-parallelism > > > 驗證多核心運算(切片加總計算): > ```go= > package main > > import ( > "log" > "runtime" > "time" > ) > > func StartTimer() time.Time { > return time.Now() > } > func SubTimer(input time.Time) float64 { > return time.Now().Sub(input).Seconds() * 1000 > } > > func main() { > > var visl_Test = []int{} > > for index := 0; index < 20000000; index++ { > visl_Test = append(visl_Test, index) > } > > var vf_Time1 = StartTimer() > println(fc_SLSUM_N(visl_Test)) > log.Printf("\n耗費:%.4f ms", SubTimer(vf_Time1)) > // 199999990000000 > // 2019/09/26 15:57:33 > // 耗費:17.0010 ms > > time.Sleep(time.Millisecond * 500) > > var vf_Time2 = StartTimer() > println(fc_SLSUM_MaxParallel(visl_Test)) > log.Printf("\n耗費:%.4f ms", SubTimer(vf_Time2)) > // 199999990000000 > // 2019/09/26 15:57:34 > // 耗費:10.0006 ms > > } > > func fc_SLSUM_N(visl_numbers []int) int { > var vi_total int > for _, vi_number := range visl_numbers { > vi_total += vi_number > } > return vi_total > } > > func fc_SLSUM_MaxParallel(visl_numbers []int) int { > var vi_nCPU = runtime.NumCPU() > > var vi_nNum = len(visl_numbers) > > vich_Channel := make(chan int) > for i := 0; i < vi_nCPU; i++ { > vi_L := i * vi_nNum / vi_nCPU > vi_R := (i + 1) * vi_nNum / vi_nCPU > go func() { > var vi_cache int > for _, vi_number := range visl_numbers[vi_L:vi_R] { > vi_cache += vi_number > } > vich_Channel <- vi_cache > }() > } > > var vi_Sum int > for i := 0; i < vi_nCPU; i++ { > vi_Sum += <-vich_Channel > } > return vi_Sum > } > > ``` > > 驗證多核心運算(取值運算): > > ```go= > package main > > import ( > "fmt" > "log" > "strconv" > "time" > ) > > func StartTimer() time.Time { > return time.Now() > } > func SubTimer(input time.Time) float64 { > return time.Now().Sub(input).Seconds() * 1000 > } > > func main() { > > var vsar_Strings [1000]string > for index := 0; index < 1000; index++ { > vsar_Strings[index] = "元素" + strconv.FormatInt(int64(index), 10) > println("元素" + strconv.FormatInt(int64(index), 10)) > } > > var vf_Time1 = StartTimer() > > for index := 0; index < len(vsar_Strings); index++ { > println(vsar_Strings[index]) > } > > log.Printf("\n耗費:%.4f ms", SubTimer(vf_Time1)) > fmt.Println("[結束]") > > // 2019/09/26 18:29:29 > // 耗費:457.0261 ms > // [結束] > > } > > ``` > > ```go= > package main > > import ( > "fmt" > "log" > "strconv" > "time" > ) > > func StartTimer() time.Time { > return time.Now() > } > func SubTimer(input time.Time) float64 { > return time.Now().Sub(input).Seconds() * 1000 > } > > func main() { > > var vsch_Strings = make(chan string, 1000) > for index := 1; index <= 1000; index++ { > vsch_Strings <- "元素" + strconv.FormatInt(int64(index), 10) > println("元素" + strconv.FormatInt(int64(index), 10)) > } > > var vf_Time1 = StartTimer() > go func() { > defer func() { > println("執行A中止!!") > }() > for { > println("執行A", <-vsch_Strings) > } > > }() > > go func() { > defer func() { > println("執行B中止!!") > }() > for { > println("執行B", <-vsch_Strings) > } > }() > > go func() { > for { > if len(vsch_Strings) == 0 { > log.Printf("\n耗費:%.4f ms", SubTimer(vf_Time1)) > fmt.Println("[結束]") > break > } > } > }() > > // 2019/09/26 18:19:19 > // 耗費:113.0098 ms > // [結束] > > var vs_stop string > fmt.Scanln(&vs_stop) //Scanln防止程式提前中斷 > } > > ``` > > ```go= > package main > > import ( > "fmt" > "log" > "strconv" > "time" > ) > > func StartTimer() time.Time { > return time.Now() > } > func SubTimer(input time.Time) float64 { > return time.Now().Sub(input).Seconds() * 1000 > } > > func main() { > > var vsch_Strings = make(chan string, 1000) > for index := 1; index <= 1000; index++ { > vsch_Strings <- "元素" + strconv.FormatInt(int64(index), 10) > println("元素" + strconv.FormatInt(int64(index), 10)) > } > > var vf_Time1 = StartTimer() > go func() { > defer func() { > println("執行A中止!!") > }() > for { > println("執行A", <-vsch_Strings) > } > > }() > > go func() { > defer func() { > println("執行B中止!!") > }() > for { > println("執行B", <-vsch_Strings) > } > }() > > go func() { > defer func() { > println("執行C中止!!") > }() > for { > println("執行C", <-vsch_Strings) > } > > }() > > go func() { > defer func() { > println("執行D中止!!") > }() > for { > println("執行D", <-vsch_Strings) > } > }() > > go func() { > for { > if len(vsch_Strings) == 0 { > log.Printf("\n耗費:%.4f ms", SubTimer(vf_Time1)) > fmt.Println("[結束]") > break > } > } > }() > > // 2019/09/26 18:53:33 > // 耗費:272.0155 ms > // [結束 > > var vs_stop string > fmt.Scanln(&vs_stop) //Scanln防止程式提前中斷 > } > > ```