返回
Featured image of post Go - 基礎語法

Go - 基礎語法

Golang - 併發/封裝/測試集一身的瘋狂

  • 公有 package 的相關資料都可以在這查詢 pkg.go.dev

Hello World!

// package name 一個資料夾底下都叫同一個 package
package main
// import 引用 相依套件
import "fmt"

// 文件主要輸出 都以 main() 為出口
func main() {
    fmt.Println("Hello World!")
}

// $ go run main.go
// > Hello World!

基本類型 DefalutTypes

  • Boolean - true / false
  • Numeric - byte / rune / (u)int / uintptr/ (u)int8 / (u)int16 / (u)int32 / (u)int64 / float32 / float64
  • String - string
  • Derived - Pointer / Array / Structure / Union / Function / Slice / Interface / Map / Channel

變量 Variables

// 定義 value1 和 value2 都類型是 type
var value1, value2 type
// 定義 valueName 是 value
var valueName type = value
// 同時定義多個
var v1, v2, v3 = a, b, c
// 更簡化 自動導入相對應的類型
v1, v2, v3 := a, b, c

常量 Constants

const key type = value
// 同時定義多個
const akey, bkey, ckey = aval, bval, cval
// 也可用在列舉 Examle 性別
const Unknown, Female, Male = 0, 1, 2

算符 Operators

幾乎多數的運算寫法都大同小異 可跳過

  • 算數
  • 關係
  • 邏輯

算數

兩個數字舉例 20 與 2

運算符描述結果
+22
-18
*40
/10
%0
++自加21
--自減19

關係

兩個數字舉例 20 與 2

運算符描述結果
==相等False
!=不相等True
<小於False
>大於True
<=小於等於False
>=大於等於True

邏輯

兩個布林舉例 true 與 false

運算符描述結果
&&相同False
||或著True
!反轉False

條件 Decision

  • if…else - 最標準的判斷
  • switch - 依序滿足條件及跳出程序,需要執行後續的case添加fallthrough
  • select - 類似 switch 但 隨機滿足條件則執行

if…else

example := true
// 以 JS相比 省略 ()
if example == true {
   // func() ....
} else {
   // func() ....
}

switch

default 就有 break,需要執行後續的case添加fallthrough

switch var1 {
    case val1:
        ...
    case val2:
        ...
        // fallthrough
    default:
        ...
}

select

package main

import (
	"fmt"
	"time"
)

func main() {
	c1 := make(chan string)
	go func() {
		time.Sleep(time.Second * 1)
		c1 <- "one"
	}()
	for i := 0; i < 1; i++ {
		select {
		case msg1 := <-c1:
			fmt.Println("receivedcase1", msg1)
		case msg1 := <-c1:
			fmt.Println("receivedcase2", msg1)
		}
	}
}
// 可以測試看看偶而會出現不同的結果

循環 Loop

// 標準
sum := 0
for i := 0; i <= 10; i++ {
        sum += i
}
fmt.Println(sum)

// while 寫法
sum := 0
for sum <= 10 {
        sum += i
}
fmt.Println(sum)

// ForEach 寫法
strings := []string{"google", "runoob"}
for i, s := range strings {
        fmt.Println(i, s)
}

// loops 無限
for {
    fmt.Println("無止境")
}

函式 Function

/*
私域 Function - func 需要小寫開頭
func functionName(x, y type) type {
	return x
}
公域 Fuction - func 需要大寫開頭
func FunctionName(x, y type) type {
	return x
}
*/
// 範例1:可多項 type回傳
func swap(x string, y string) (string, string) {
	return y, x
}

// 範例2:閉包 
func getSequence() func() int {
   i:=0
   fmt.Println("第一次使用會先執行到此行,註冊成變數")
   return func() int {
      i+=1
     fmt.Println("執行 Function")
     return i  
   }
}

func main() {
	nextNum := getSequence()

	fmt.Println(nextNum()) 
    // 1
	fmt.Println(nextNum()) 
    // 2
	fmt.Println(nextNum()) 
    // 3
}
// 範例3:Struct Func
// 這方式極常用,請熟讀領會。
type Circle struct {
  radius float64
}
func (c Circle) getArea() float64 {
  return 3.14 * c.radius * c.radius
}
func main() {
  var c1 Circle
  c1.radius = 10.00
  fmt.Println("圓面積 = ", c1.getArea())
  // 314
}
// 相似 ES6 Class function
//  class Circle {
//    constructor(radius){
//       this.radius = radius
//    }    
//    getArea(){
//      return 3.14 * this.radius * this.radius
//    }
// }
// var c1 = new Circle(10.00)
// console.log(c1.getArea())

// 範例4:遞歸 Function
func recursion() {
   recursion() /* 使用自身邏輯在重新判斷,通常會含有判斷 */
}

func main() {
   recursion()
}

字串 String

  • strings pkg
  • 在 golang 中字串都是一種切片 (slices)
// 將 []string{} 字串合併
examples := []string{"Hello", "world!"}
fmt.Println(strings.Join(examples, " "))

數組 Array

Array 用來定義涵蓋範圍且範圍是固定

// name := [size]type(val) 
// {} 的數量 <= []的數字
// [] 忽略數字 = 宣告時 {}的數量
// 標準
arrExample1 := [3]string{"one", "two" , "three"}
// 不確定 {} 的數量,但其實也是個註記,因為也不能擴充, example: arrExample2[3] 噴錯
arrExample2 := [...]string{"one", "two", "three"}
// 訪問數字 example: two
fmt.Println(arrExample1[1])

// 進階
// 多維數組
// 標準使用
multidiArrExample1 := [2][3]int{
   {0, 1, 2, 3} ,
   {4, 5, 6, 7} ,
}
// 訪問數字 example: 2
fmt.Println(multidiArrExample1[0][2])

指針 Pointer

指定記憶體的位置,變數是綽號的話,指針變數的專屬ID

var a int = 1       /* a 值為 1 */
add := &a           /* add 為 a 記憶體位置(指針) */
fmt.Printf(*add)    /* 透過 add 記憶體位置(指針) 得知 值為 1*/
package main

import "fmt"

func main() {
   var a int = 100
   var b int = 200

   fmt.Printf("交换前 a 值 : %d\n", a )
   fmt.Printf("交换前 b 值 : %d\n", b )

   swap(&a, &b);

   fmt.Printf("交换後 a 值 : %d\n", a )
   fmt.Printf("交换後 b 值 : %d\n", b )
}

func swap(x *int, y *int) {
   var temp int
   temp = *x    /* 保存 x 地址的值 */
   *x = *y      /* 將 y 賦值給 x */
   *y = temp    /* 將 temp 賦值給 y */
}
// 交换前 a 值 : 100
// 交换前 b 值 : 200
// 交換後 a 值 : 200
// 交换後 b 值 : 100

理解上方的做法後 進階簡潔

func swap(x *int, y *int){
    *x, *y = *y, *x
}

再再簡潔

func main() {
   var a int = 100
   var b int= 200
   a, b = b, a /* 交換 */
}

結構 Struct

自定義type,非常常使用,類似 TS 的 type,用法像 Object 用 . 的方式訪問

type Books struct {
   title string
   author string
   subject string
   book_id int
}
func main() {
    // 可忽略一些定義
	Book := Books{title: "LearnForGo", author: "hifounder"}
	fmt.Println(Book)
}

Struct 搭配 Pointer

package main

import "fmt"

type Books struct {
   title string
   author string
   subject string
   book_id int
}

func main() {
   Book := Books{ title: "LearnForGo", author: "hifounder", subject:"介紹",book_id: 00001 }
   printBook(&Book) /* 給予Book 記憶體位置 */
}
func printBook( book *Books ) {  /* 透過傳入的 book 記憶體位置 理解值 */
   fmt.Printf( "Book title : %s\n", book.title)
   fmt.Printf( "Book author : %s\n", book.author)
   fmt.Printf( "Book subject : %s\n", book.subject)
   fmt.Printf( "Book book_id : %d\n", book.book_id)
}

切片 Slice

基本上是處理Array

// example
arr := [3]string{"one", "two" , "three"}
// Array 指的是 {"one", "two" , "three"} 這些值
// Slice 指的是 arr[] 後方 [] 這個的動作

func printSlice(x []int) {
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}
func main() {
	var mySlice1 [10]int
	printSlice(mySlice1[:])
	mySlice2 := make([]int, 10)
	printSlice(mySlice2)
	mySlice3 := [10]int{}
	printSlice(mySlice3[:])
    // 皆是 => len=10 cap=10 slice=[0 0 0 0 0 0 0 0 0 0]
}
# 定義切片
func main() {
	mySlice1 := make([]int, 8, 10)
	mySlice1[2] = 1
	mySlice1[3] = 10
	mySlice1[5] = 20
	printSlice(mySlice1)
    // len=8 cap=10 slice=[0 0 1 10 0 20 0 0]
	printSlice(mySlice1[:3])
    // len=3 cap=10 slice=[0 0 1]
	printSlice(mySlice1[3:])
    // len=5 cap=7 slice=[10 0 20 0 0]
    // len Array長度 與 起始位置
    // cap 是 指定容量
    // [注意!] error: mySlice1[9] = 50

    // append()
	mySlice1 = append(mySlice1, 50)
	printSlice(mySlice1)
    // len=9 cap=10 slice=[0 0 1 10 0 20 0 0 50]
    // copy()
	mySlice2 := make([]int, len(mySlice1), (cap(mySlice1))*2)
    copy(mySlice2,mySlice1)
    printSlice(mySlice2)
    // len=9 cap=20 slice=[0 0 1 10 0 20 0 0 50]
}
func printSlice(x []int) {
	fmt.Printf("len=%d cap=%d slice=%v\n", len(x), cap(x), x)
}

集合 Map

像是 JS/TS 的Object,且Slice的cap(capacity)範圍

// 默認的 map default: nil 空值
// 使用 var
var obj map[string][string]
// 使用 make
obj := make(map[string][string])
func printMap(x map[string]string) {
	fmt.Printf("len=%d ,map=%v\n", len(x), x)
}
func main() {
	countryCapitalMap := make(map[string]string)

	countryCapitalMap["France"] = "巴黎"
	countryCapitalMap["Italy"] = "羅馬"
	countryCapitalMap["Japan"] = "東京"
	printMap(countryCapitalMap)
    // len=3 ,map=map[France:巴黎 Italy:羅馬 Japan:東京]

	for country := range countryCapitalMap {
		fmt.Println(country, "首都是", countryCapitalMap[country])
        // Italy 首都是 羅馬
        // Japan 首都是 東京
        // France 首都是 巴黎
	}
	capital, ok := countryCapitalMap["American"]
	if ok {
		fmt.Println("American 的首都是", capital)
	} else {
		fmt.Println("American 的首都不存在")
	}
    // American 的首都不存在 

    delete(countryCapitalMap, "France") /*删除元素*/
	printMap(countryCapitalMap)
    // len=2 ,map=map[Italy:羅馬 Japan:東京]
}

範圍 Range

range 是 for 的其中一個使用方式

package main

import "fmt"

func main() {
	nums := []int{1, 2, 3, 4, 5}
    /* 若沒使用 i 可用 _ 去省略*/
	for i, num := range nums {
		fmt.Printf("index=%d num=%d\n", i, num)
	}
}

接口 Interface

如果是 Javascript 學習者 推薦先往 TypeScript 學習,網上很多範例剛開始我卡到不行,interface 剛開始學的初心者簡直羅生門吧我想。
必需說,沒有 interface 也可以完成 商業邏輯的 code 先完成商業邏輯,再來改善即可。
建議先學會 Struct 再學 Interface,然後熟透 再 Pointer 混進使用,才不會搞混亂!

type Phone interface {
    call()
}

type Nokia struct {
}

func (nokia Nokia) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}
func phoneCall(p Phone) {
    p.call()
}
func main() {

    fmt.Println("This use Struct Call.")
    phone1 := Nokia{}
    phone1.call()

    phone2 := IPhone{}
    phone2.call()
    
    fmt.Println("This use Interface Call.")
    phoneCall(Nokia{})
    phoneCall(IPhone{})
}

併發 Routine

這項功能,算是我在學習 Go 這個語言覺得最酷的地方 好文分享:

go funcName(value type)
package main

import (
        "fmt"
        "time"
)

func say(s string) {
        for i := 0; i < 5; i++ {
                time.Sleep(100 * time.Millisecond)
                fmt.Println(s)
        }
}

func main() {
        go say("1")
        say("2")
}
// 會沒有固定的先後順序輸出 1 與 2

最多執行與 CPU 相等的 Goroutine,取得CPU數目: runtime.NumCPU()

channel 通道

ch1 := make(chan int)   // 創建 channel (ReadWrite)
ch1 <- v                // v 發送至 ch (Write)
v := <-ch1              // ch 收 Data 賦值給 v (Read)

// 緩衝區,但注意 緩衝區容量有限,一定要有接收者把 Data 拿走,否則就會阻塞,發送者也無法送出
ch2 := make(chan int, 10)
close(ch2)              // 關閉 channel

錯誤 Error

這地方偏向寫 Log

func example(num int) (bool, error) {
    if int < 0 {
        return false, errors.New("num < 0")
    } 
        return true, _
}

func main(){
    _, err := example(-1)

    if err != nil {
        fmt.Sprintf("Error: %s", err.Error())
    } 
}

defer / panic / recover

錯誤處理,以前用 try…catch…finally 往回吐,這三個的組成方式就很像是這項功能。

推遲 defer

  • 後進先出
  • 可用於 func 回傳變數
  • 當 func 結束最後執行 像 try…catch…finally
func main() {
    language := "中文最高"
    defer fmt.Print(language + "\n")

    language = "English the bast"
    fmt.Print("那個 ,")
    // 那個 , 中文最高
}
func c() (i int) {
    defer func() { i++ }()  // 先出
    return 1                // 後進
    // 2
}

恐慌 panic

遇到這Func 程式直接 crash ,立刻終止當前的 process

func panicFunc(){
    panic('Message')
}
func main(){
    fmt.Plantln("before.")
    panicFunc()             // exit
    fmt.Plantln("after.")
}

恢復 recover

func main() {
	f()
	fmt.Println("Returned normally from f.")	// 6. 最後輸出
}

func f() {
	defer func() {								// 3. defer 類似 finally() 被觸發
		r := recover()							// 4. 恢復
		if r != nil {
			fmt.Println("Recovered in f", r)
		}
	}()											// 5. 結束

	fmt.Println("Calling g.")
	g(0)
	fmt.Println("Returned normally from g.")
}

func g(i int) {
	if i > 3 {
		panicNumber := fmt.Sprintf("%v", i)

		fmt.Println("Panicking!", panicNumber)
		panic(panicNumber)						// 2. 執行恐慌
	}
	defer fmt.Println("Defer in g", i)
	fmt.Println("Printing in g", i)
	g(i + 1) 									// 1. 重複執行
}

重點整理

Array Slice Map 三角關係

// slice
sliceExample := []int{1, 2, 3}
// map
mapExample := map[int][string]{ 1: "one", 2: "two", 3: "three" }
  • Array 指的是講 {} 這裡面的 val
  • delete() 只有 Map 可以使用, Slice 不能刪除只能切區段
  • append() 只有 Slice 可以使用, Map 直接 varMap[type] = val 定義即可
  • Slice 有範圍限定 length 最多只能多少
  • Map 無範圍限定 length 像似 JS的Array 無限制 append

goroutine 使用時機

  • go func(){} 中若賦予變數傳出會因此順序大亂,由於線程在過程中會同時對變數做賦值,而導致過程亂掉,可用 sync.MutexLock()Unlock(),去做佇列鎖。
  • 若是兩個 goroutine 線程要交換訊息,waitGroup + Lock 會比較好用。
Licensed under CC BY-NC-SA 4.0
comments powered by Disqus