六、函式、包和錯誤處理

zhangsen 發表於 2021-04-20

1-函式的基本語法

六、函式、包和錯誤處理

var f1 float64 = 12.34
   var f2 float64 = 12.00
   var oper byte = '-'
   result := cal(f1, f2, oper)
   fmt.Println(result)
}

func cal(n1 float64, n2 float64, operator byte) float64 {
   var res float64
   switch operator {
   case '+':
      res = n1 + n2
   case '-':
      res = n1 - n2
   case '*':
      res = n1 * n2
   case '/':
      res = n1 / n2
   default:
      fmt.Println("操作符號錯誤")
   }
   return res
}

2-包

go的每一個檔案都屬於一個包,也就是說go是以包的形式來管理檔案和專案目錄結構的

1-包的作用

  1. 區分相同名字的函式、變數等識別符號
  2. 管理專案
  3. 控制函式、變數等訪問範圍,即作用域

2-包的相關說明

Package 包名

Import 包的路徑

  1. 檔案包名通常和檔名稱一致,都為小寫
  2. 第一行package、import引入
  3. 在import包時,路徑從$GOPATH的src下開始,不用帶src,編譯器會自動從src開始引入
  4. 訪問其他包的函式或者變數,包名.函式名或者包名.變數名
  5. 如果包名太長,可以取別名,但是原來的包名就不能使用了
  6. 同一個包裡不能有兩個相同的函式名、全域性變數
  7. 如果要編譯成一個可執行程式檔案,需要將這個包宣告為main,即package main,這個就是一個語法規範,如果是寫一個庫,包名可以自定義
  8. 編譯時需要編譯main包所在的資料夾[[email protected] learn]# go build -o bin\my.exe go_codee\fundemo01\main

六、函式、包和錯誤處理

3-函式

1-函式的呼叫機制

六、函式、包和錯誤處理

上圖說明:

  1. 在呼叫一個函式時,會給該函式分配一個新的空間,編譯器會通過自身的處理讓這個新的空間和其他的棧的空間區分開來
  2. 在每個函式對應的棧中,資料空間是獨立的,不會混淆
  3. 當一個函式呼叫(執行)完畢後,程式會銷燬這個函式對於的棧空間

2-return語句

  1. 如果返回多個值,在接收時,希望忽略某個返回值,則使用_符號表示佔位忽略
  2. 如果返回值只有一個,返回值型別列表可以不寫()

3-函式的遞迴呼叫

一個函式在函式體內又呼叫了本身,我們稱為遞迴呼叫

六、函式、包和錯誤處理

分析圖

六、函式、包和錯誤處理

案例

六、函式、包和錯誤處理

六、函式、包和錯誤處理

遞迴呼叫的總結

  1. 執行一個函式時,就建立一個新的受保護的獨立空間(新函式棧)
  2. 函式的區域性變數是獨立的,不會互相影響
  3. 遞迴必須向退出遞迴的條件逼近,否則就是無限遞迴
  4. 當一個函式執行完畢,或遇到return,就會返回,誰呼叫就返回給誰,函式執行完會被系統銷燬

練習題

  1. 斐波那契數 1,1,2,3,5,8,13,給出一個整數n,返回它的斐波那契數
func fbn(n int) int {
   if n == 1 || n == 2 {
      return 1
   } else {
      return fbn(n-1) + fbn(n-2)

   }
}
  1. 求函式值

f(1)=3

f(n)=2*f(n-1)+1

func fbn(n int) int {
   if n == 1 {
      return 3
   } else {
      return f(n)=2*f(n-1)+1

   }
}
  1. 猴子吃桃子的問題

有一堆桃子,猴子第一天吃了其中的一半,並再多吃了一個!以後每天猴子都吃其中的一半,然後 再多吃一個。當到第十天時,想再吃時(還沒吃),發現只有 1 個桃子了。問題:最初共多少個桃子?

思路分析:

  1. 第 10 天只有一個桃子
  2. 第 9 天有幾個桃子 = (第 10 天桃子數量 + 1) * 2
  3. 規律: 第 n 天的桃子資料 peach(n) = (peach(n+1) + 1) * 2 程式碼:
func peach(n int) int {
   if n > 10 || n < 1 {
      fmt.Println("天數不對")
      return 0
   }
   if n == 10 {
      return 1
   } else {
      return (peach(n+1) + 1) * 2
   }
}

4-函式使用的注意事項和細節討論

  1. 函式的形參列表可以是多個,返回值列表也可以是多個。
  2. 形參列表和返回值列表的資料型別可以是值型別和引用型別。
  3. 函式的命名遵循識別符號命名規範,首字母不能是數字,首字母大小寫問題
  4. 函式中的變數是區域性的,函式外不生效
  5. 基本資料型別和陣列預設都是值傳遞的,即進行值拷貝。在函式內修改,不會影響到原來的值
  6. 如果希望函式內的變數能修改函式外的變數(指的是預設以值傳遞的方式的資料型別),可以傳入變數的地址&,函式內以指標的方式操作變數。從效果上看類似引用
  7. Go 函式不支援函式過載(兩個相同的函式名,引數個數不同,認為是同一個函式)
  8. 函式也是一種資料型別,可以賦值給一個變數,則該變數就是一個函式型別的變數了,通過該變數可以對函式呼叫
  9. 函式既然是一種資料型別,因此在 Go 中,函式可以作為形參,並且呼叫
  10. 使用 _ 識別符號,忽略返回值
  11. 支援對函式返回值命名
  12. 為了簡化資料型別定義,Go 支援自定義資料型別

【基本語法:type 自定義資料型別名 資料型別】

  1. Go 支援可變引數

六、函式、包和錯誤處理

  1. 如果一個函式的形參列表中有可變引數,則可變引數需要放在形參列表最後。
13-----Go 支援可變引數
func main(){
    res4 := sum(10, 0, -1, 90, 10)//109
   fmt.Println(res4)
}

func sum(n1 int, args ...int) int {
   sum := n1
   for i := 0; i < len(args); i++ {
      sum += args[i]
   }
   return sum
}

12------Go 支援自定義資料型別
相當於別名
但是與原來的int是兩種型別,需要轉換
type myInt int
var num1 myInt
var num2 int
num1 = 48
num2 = int(num1)
fmt.Println("num1=", num1, "num2=", num2)

函式別名
func main(){
    res3 := myFun2(getSum, 56, 60) 
    fmt.Println(res3) 
} 

type myFunType func(int, int) int 

func myFun2(funvar myFunType, num1 int, num2 int) int { 
        return funvar(num1, num2) 
} 

func getSum(n1 int, n2 int) int { 
        return n1 + n2 
}

11-----支援對函式返回值命名
 func main(){
   a, b := getSumAndSub(1, 2)
   fmt.Println(a, b)
}

func getSumAndSub(n1 int, n2 int) (sum int, sub int) {
   sub = n1 - n2
   sum = n1 + n2
   return
}

如果希望函式內的變數能修改函式外的變數,可以傳入變數的地址&,函式內以指標的方式操作變數。從效果上看類似引用

六、函式、包和錯誤處理

--------------------------------------------
num := 14
test1(num)
fmt.Println(num) //14

func test1(n int) {
   n = n + 10
   fmt.Println(n)//24
   //return 0
}
---------------------------------------------
//傳入地址修改值
func main(){
    num := 14
   //test1(num)
   test3(&num)
   fmt.Println("main num=", num)//24

}

//n就是 *int型別
func test3(n *int) {
   *n = *n + 10
   fmt.Println("test03 n=", *n)//24
}
//*取值
//&取地址
---------------------------------------------
函式也是一種型別
func main(){
        a := getSum 
        fmt.Printf("a的型別是%T,getSum型別是%T\n", a, getSum) 

        res := a(10, 40) 
        fmt.Println(res) 
} 

func getSum(n1 int, n2 int) int { 
        return n1 + n2 
}
-----------------------------------------------
函式既然是一種型別,也可以作為引數
func main(){
   res2 := myFun(getSum, 50, 60)
   fmt.Println(res2)
}

func getSum(n1 int, n2 int) int {
   return n1 + n2
}

func myFun(funvar func(int, int) int, num1 int, num2 int) int {
   return funvar(num1, num2)
}

案例

六、函式、包和錯誤處理

  1. 如果第一個引數和第二個引數型別一致,第一個後面可以不用帶引數

交換兩個值的位置

 func main(){
   a = 10
   b = 20
   swap(&a, &b)
   fmt.Printf("a=%v,b=%v", a, b)
}

func swap(n3 *int, n4 *int) {
   t := *n3
   *n3 = *n4
   *n4 = t
}

4-init函式

每一個原始檔都可以包含一個 init 函式,該函式會在 main 函式執行前,被 Go 執行框架呼叫,也

就是說 init 會在 main 函式前被呼叫。

package main

import "fmt"

func init() {
   fmt.Println("init...")
}

func main() {
   fmt.Println("main...")
}
//GOROOT=D:\go #gosetup 
init... 
main... 

1-inti 函式的注意事項和細節

  1. 如果一個檔案同時包含全域性變數定義,init 函式和 main 函式,則執行的流程全域性變數定義->init

函式->main 函式

var age = test()

func test() int {
   fmt.Println("test()")
   return 90
}
func init() {
   fmt.Println("init...")
}

func main() {
   fmt.Println("main...", age)
}

//
test() 
init... 
main... 90
  1. init 函式最主要的作用,就是完成一些初始化的工作(即使是引入的包)

六、函式、包和錯誤處理

5-匿名函式

Go 支援匿名函式,匿名函式就是沒有名字的函式,如果我們某個函式只是希望使用一次,可以考

慮使用匿名函式,匿名函式也可以實現多次呼叫。

5-1 匿名函式使用方式 1

在定義匿名函式時就直接呼叫,這種方式匿名函式只能呼叫一次

func main() {
   res1 := func(n1, n2 int) int {
      return n1 + n2
   }(10, 20)//呼叫
   fmt.Println(res1)
}

5-2匿名函式使用方式 2

匿名函式賦給一個變數(函式變數),再通過該變數來呼叫匿名函式

func main() {
   a := func(n1, n2 int) int {
      return n1 - n2
   }
   res2 := a(10, 5)
   fmt.Println(res2)

   res3 := a(12, 6)
   fmt.Println(res3)
}

5-3 全域性匿名函式

如果將匿名函式賦給一個全域性變數,那麼這個匿名函式,就成為一個全域性匿名函式,可以在程式

有效。

var (
   //fun1就是全域性匿名函式
   Func1 = func(n1, n2 int) int {
      return n1 + n2
   }
)

6-閉包

基本介紹:閉包就是一個函式和與其相關的引用環境組合的一個整體(實體)

案例

func AddUpper() func(int) int {
   var n int = 10
   return func(x int) int {
      n = n + x
      return n
   }
}

func main() {
   f := AddUpper()
   fmt.Println(f(1))
   fmt.Println(f(2))
   fmt.Println(f(3))
}

//
11 
13 
16

----這部分屬於閉包----
  var n int = 10
   return func(x int) int {
      n = n + x
      return n
   }
--------------------
返回的是一個匿名函式, 但是這個匿名函式引用到函式外的 n ,因此這個匿名函式就和 n 形成一
個整體,構成閉包
  1. AddUpper 是一個函式,返回的資料型別是 fun (int) int
  2. 閉包是類, 函式是操作,n 是欄位。函式和它使用到 n 構成閉包
  3. 當我們反覆的呼叫 f 函式時,因為 n 是初始化一次,因此每呼叫一次就進行累計
  4. 我們要搞清楚閉包的關鍵,就是要分析出返回的函式它使用(引用)到哪些變數,因為函式和它引用到的變數共同構成閉包
  5. 對上面程式碼的一個修改,加深對閉包的理解
func AddUpper() func(int) int {
   var n int = 10
   var str = "hello"
   return func(x int) int {
      n = n + x
      str += string(36) //36=>$
      fmt.Println("str=", str)
      return n
   }
}

func main() {
   f := AddUpper()
   fmt.Println(f(1))
   fmt.Println(f(2))
   fmt.Println(f(3))
}
//結果
str= hello$ 
11 
str= hello$$ 
13 
str= hello$$$ 
16

案例:

  1. 編寫一個函式 makeSuffix(suffix string) 可以接收一個檔案字尾名(比如.jpg),並返回一個閉包
  2. 呼叫閉包,可以傳入一個檔名,如果該檔名沒有指定的字尾(比如.jpg) ,則返回檔名.jpg , 如果已經有.jpg 字尾,則返回原檔名
  3. 要求使用閉包的方式完成
  4. strings.HasSuffix , 該函式可以判斷某個字串是否有指定的字尾
func makeSuffix(suffix string) func(string) string {
   return func(name string) string {
      if !strings.HasSuffix(name, suffix) {
         return name + suffix
      }
      return name
   }
}

f1 := makeSuffix(".jpg")
fmt.Println("檔名處理後=", f1("winter"))
fmt.Println("檔名處理後=", f1("winter.jpg"))
fmt.Println("檔名處理後=", f1("winter.avi"))

//
檔名處理後= winter.jpg 
檔名處理後= winter.jpg 
檔名處理後= winter.avi.jpg
  1. 返回的匿名函式和 makeSuffix (suffix string) 的 suffix 變數組合成一個閉包,因為返回的函式引用到 suffix 這個變數
  2. 我們體會一下閉包的好處,如果使用傳統的方法,也可以輕鬆實現這個功能,但是傳統方法需要每次都傳入字尾名,比如 .jpg ,而閉包因為可以保留上次引用的某個值,所以我們傳入一次就可以反覆使用。大家可以仔細的體會一把

7-defer

為什麼需要 defer

在函式中,程式設計師經常需要建立資源(比如:資料庫連線、檔案控制程式碼、鎖等) ,為了在函式執行完畢後,及時的釋放資源,Go 的設計者提供 defer (延時機制)。

func main() {
   res := sum(10, 20)
   fmt.Println("main4 res=", res)
}

func sum(n1 int, n2 int) int {

   //當執行到defer時,暫時不執行,會將defer後面的語句亞茹到獨立的棧(defer棧)
   //當函式執行完畢後,再從defer棧,按照先入後出的方式出棧,執行
   defer fmt.Println("sum3 n1=", n1)
   defer fmt.Println("sum2 n2=", n2)
   res := n1 + n2
   fmt.Println("sum1 res=", res)
   return res
}

//
sum1 res= 30 
sum2 n2= 20 
sum3 n1= 10 
main4 res= 30

總結:

  1. 當 go 執行到一個 defer 時,不會立即執行 defer 後的語句,而是將 defer 後的語句壓入到一個棧 中[我為了講課方便,暫時稱該棧為 defer 棧], 然後繼續執行函式下一個語句
  2. 當函式執行完畢後,在從 defer 棧中,依次從棧頂取出語句執行(注:遵守棧先入後出的機制),所以同學們看到前面案例輸出的順序
  3. 在 defer 將語句放入到棧時,也會將相關的值拷貝同時入棧
func main() {
   res := sum(10, 20)
   fmt.Println("main4 res=", res)
}

func sum(n1 int, n2 int) int {

   //當執行到defer時,暫時不執行,會將defer後面的語句亞茹到獨立的棧(defer棧)
   //當函式執行完畢後,再從defer棧,按照先入後出的方式出棧,執行
   defer fmt.Println("sum3 n1=", n1)
   defer fmt.Println("sum2 n2=", n2)
   //增加
   n1++
   n2++
   defer fmt.Println("sum3 n1=", n1)
   defer fmt.Println("sum2 n2=", n2)
   res := n1 + n2
   fmt.Println("sum1 res=", res)
   return res
}

//

sum1 res= 32 
sum2 n2= 21 
sum3 n1= 11 
sum2 n2= 20 
sum3 n1= 10 
main4 res= 32

defer 最主要的價值是在,當函式執行完畢後,可以及時的釋放函式建立的資源。

六、函式、包和錯誤處理

  1. 在 golang 程式設計中的通常做法是,建立資源後,比如(開啟了檔案,獲取了資料庫的連結,或者是鎖資源),可以執行 defer file.Close() defer connect.Close()
  2. 在 defer 後,可以繼續使用建立資源.
  3. 當函式完畢後,系統會依次從 defer 棧中,取出語句,關閉資源.
  4. 這種機制,非常簡潔,程式設計師不用再為在什麼時機關閉資源而煩心。

8-函式引數傳遞方式

值型別引數預設就是值傳遞,而引用型別引數預設就是引用傳遞

  1. 兩種傳遞方式

    1. 值傳遞
    2. 引用傳遞

其實,不管是值傳遞還是引用傳遞,傳遞給函式的都是變數的副本,不同的是,值傳遞的是值的拷貝,引用傳遞的是地址的拷貝,一般來說,地址拷貝效率高,因為資料量小,而值拷貝決定拷貝的資料大小,資料越大,效率越低

2-值型別和引用型別

  1. 值型別:基本資料型別 int 系列, float 系列, bool, string 、陣列和結構體 struct
  2. 引用型別:指標、slice 切片、map、管道 chan、interface 等都是引用型別

2-值傳遞和引用傳遞使用特點

六、函式、包和錯誤處理

  1. 如果希望函式內的變數能修改函式外的變數,可以傳入變數的地址&,函式內以指標的方式操作變數。從效果上看類似引用
//傳入地址修改值
func main(){
    num := 14
   //test1(num)
   test3(&num)
   fmt.Println("main num=", num)//24

}

//n就是 *int型別
func test3(n *int) {
   *n = *n + 10
   fmt.Println("test03 n=", *n)//24
}
//*取值
//&取地址

9-變數作用域

  1. 函式內部宣告/定義的變數叫區域性變數,作用域僅限於函式內部
  2. 函式外部宣告/定義的變數叫全域性變數,作用域在整個包都有效,如果其首字母為大寫,則作用域在整個程式有效
  3. 如果變數是在一個程式碼塊,比如 for / if 中,那麼這個變數的的作用域就在該程式碼塊
賦值語句不能在函式外執行
package main

import "fmt"
var Age int = 20
Name := "test" //先宣告後賦值  var Name string / Name = "test",執行語句需在函式體內執行
func main() {
    fmt.Println("name",Name)
}

課堂練習

var age int = 50         //其他包不可用
var Name string = "jack" //其他包可用

func main() {
   fmt.Println("age=", age)
   fmt.Println("Name=", Name)
   fmt.Println()
   test()
}

func test() {
   age := 10
   Name := "time"
   fmt.Println("age=", age)
   fmt.Println("Name=", Name)
}

-------------------
age= 50 
Name= jack 

age= 10 
Name= time
//如果變數是在一個程式碼塊,比如 for / if 中,那麼這個變數的的作用域就在該程式碼塊
for i := 0; i < 10; i++ {
   fmt.Println(i)
}

for i := 0; i < 10; i++ {
   fmt.Println(i)
}
fmt.Println(i) //未定義

要區域性使用
var i int
for i = 0; i < 10; i++ {
   fmt.Println(i)
}

---------------------------
var age int = 50         //其他包不可用
fmt.Println(age)
test01()
test02()
test03()

func test01() {
   fmt.Println(age)
}

func test02() {//就近原則
   age := 12
   fmt.Println(age)
}

func test03() {
   fmt.Println(age)
}

函式課堂聯絡題

  1. )函式可以沒有返回值案例,編寫一個函式,從終端輸入一個整數列印出對應的金子塔
  2. 編寫一個函式,從終端輸入一個整數(1—9),列印出對應的乘法表
//todo

10-字串常用的系統函式

  1. 統計字串的長度,按位元組len(str)
  2. 字串遍歷,同時處理中文問題,r:=[]rune(str)
  3. 字串轉整數
  4. 整數轉字串
  5. 字串轉 []byte
  6. []byte 轉字串
  7. 10進駐轉2,8,16進位制
  8. 查詢字串中是否存在指定的字串中
  9. 統計一個字串有幾個指定的子串
  10. 不區分大小寫的字串比較
  11. 返回子串在字串第一次出現的index值,如果沒有返回-1:strings.Index
  12. 返回子串在字串最後一次出現的index,如果沒有返回-1
  13. 將指定的子串替換成 另一個子串:strings.Replace(“go go hello”,”go”,”go語言”,n) n可以指你希望替換幾個,如果n=-1表示全部替換
  14. 按照指定的某個字元,為分割標示,將一個字串拆分成字串陣列:strings.Split(“hello,world,ok”,”,”)
  15. 將字串的字母進行大小寫的轉換strings.ToLower() 、 strings.ToUpper()
//1-取長度
str := "hello 北京"
//golang的編碼是utf8,ascii的字元(字嗎和數字)佔一個位元組,漢字佔用三個位元組
fmt.Println("str len=", len(str)) //5+1+3+3=12

//2-中文遍歷
str2 := "hello北京"
r := []rune(str2) //切片
for i := 0; i < len(r); i++ {
   fmt.Printf("字元=%c\n", r[i]) //中文亂碼 一個漢字三個字元
}
/**
字元=h
字元=e
字元=l
字元=l
字元=o
字元=北
字元=京
*/
//3-字串轉整數
str3 := "123"
n, err := strconv.Atoi(str3)
if err != nil {
   fmt.Println("錯誤結果", err)
} else {
   fmt.Println("結果是", n)
} //結果是 123

//4-整數轉字串
m := strconv.Itoa(12345)
fmt.Println("結果是", m) //結果是 12345

//5-字串轉切片 []byte
var bytes = []byte("hello go")
fmt.Printf("bytes=%v\n", bytes) // [104 101 108 108 111 32 103 111]

//6-切片 []byte 到字串
str = string([]byte{97, 98, 99})
fmt.Printf("str=%v\n", str) //str=abc

//7- 10 進位制轉 2, 8, 16 進位制: str = strconv.FormatInt(123, 2) // 2-> 8 , 16 返回對應的字串
str = strconv.FormatInt(123, 2)
fmt.Printf("123對應的二進位制是=%v\n", str) //123對應的二進位制是=1111011
str = strconv.FormatInt(123, 16)
fmt.Printf("123對應的16進位制是=%v\n", str) //123對應的16進位制是=7b

//8-查詢子串是否在指定的字串中: strings.Contains("seafood", "foo") //bool
b := strings.Contains("seafood", "sea")
fmt.Printf("b=%v\n", b) //b=true

//9-統計一個字串有幾個指定的子串 : strings.Count("ceheese", "e") //4
num := strings.Count("ceheeeee", "e")
fmt.Printf("b=%v\n", num) //b=6

//10-不區分大小寫的字串比較(==是區分字母大小寫的): fmt.Println(strings.EqualFold("abc", "Abc")) // true
b = strings.EqualFold("abc", "ABc")
fmt.Printf("b=%v\n", b)           //b=true
fmt.Println("結果", "abc" == "Abc") //區分大小寫 //結果 false

//12-返回子串在字串第一次出現的 index 值,如果沒有返回-1 : strings.Index("NLT_abc", "abc") // 4
index := strings.Index("NTL_abcabcabc", "abc")
fmt.Printf("index=%v\n", index) //index=4

//13-返回子串在字串最後一次出現的 index,如沒有返回-1 : strings.LastIndex("go golang", "go")
indexL := strings.LastIndex("NTL_abcabcabc", "abc")
fmt.Printf("index=%v\n", indexL) //index=10

//14-將指定的子串替換成 另外一個子串: strings.Replace("go go hello", "go", "go 語言", n)
// n 可以指定你希望替換幾個,如果 n=-1 表示全部替換
str3 = "go go hello"
str = strings.Replace(str3, "go", "北京", -1)
fmt.Printf("str=%v str3=%v\n", str, str3) //str=北京 北京 hello str3=go go hello

//15-按照指定的某個字元,為分割標識,將一個字串拆分成字串陣列:strings.Split("hello,wrold,ok",",")
strArr := strings.Split("hello,hello,test", ",")
for i := 0; i < len(strArr); i++ {
   fmt.Printf("str[%v]=%v\n", i, strArr[i])
   /**
   str[0]=hello
   str[1]=hello
   str[2]=test
   */
}
fmt.Printf("strArr=%v\n", strArr) //strArr=[hello hello test] 值拷貝不改變本身

//16-將字串的字母進行大小寫的轉換: strings.ToLower("Go") // go strings.ToUpper("Go") // GO
str4 := "golang hello"
str4 = strings.ToLower(str4)  //改變了str4
fmt.Printf("str4=%v\n", str4) //str4=golang hello
str4 = strings.ToUpper(str4)
fmt.Printf("str4=%v\n", str4) //str4=GOLANG HELLO
  1. 將字串左右兩邊的空格去掉 strings.TrimSpace()
  2. 將字串左右兩邊指定的字元去掉【包括空格】 strings.Trim(“!hello! “,”!”)
  3. 將字串左邊指定的字元去掉: strings.TrimLeft(“!hello! “,”!”)
  4. 將字串右邊指定的字元去掉: strings.TrimRight(“!hello! “,”!”)
  5. 判斷字串是否以指定的字串開頭 strings.HasPrefix(“ftp://192.168.1.1","ftp")
  6. 判斷字串是否以指定的字串結束 strings.HasSuffix(“ftp://192.168.1.1/aa","aa") //false
//17-將字串左右兩邊的空格去掉: strings.TrimSpace(" tn a lone gopher ntrn ")
str = strings.TrimSpace(" tn a lone gopher ntrn ")
fmt.Printf("str=%q\n", str) //str="tn a lone gopher ntrn"

//18-將字串左右兩邊指定的字元去掉 : strings.Trim("! hello! ", " !") // ["hello"] //將左右兩邊 !
//和 " "去掉
str = strings.Trim("! hello  !! ", " !")
fmt.Printf("str=%q\n", str) //str="hello"

//19-將字串左邊指定的字元去掉 : strings.TrimLeft("! hello! ", " !") // ["hello"] //將左邊 ! 和 "
//"去掉
str = strings.TrimLeft("! hello! ", " !")
fmt.Printf("str=%q\n", str) //str="hello! "

//20-將字串右邊指定的字元去掉 :strings.TrimRight("! hello! ", " !") // ["hello"] //將右邊 ! 和 "
//"去掉
str = strings.TrimRight("! hello! ", " !")
fmt.Printf("str=%q\n", str) //str="! hello"

//21-判斷字串是否以指定的字串開頭: strings.HasPrefix("ftp://192.168.10.1", "ftp") // true
b = strings.HasPrefix("ftp://192.168.10.1", "ftp")
fmt.Printf("str=%v\n", b) //str=true

//22-判斷字串是否以指定的字串結束: strings.HasSuffix("NLT_abc.jpg", "abc") //false
b = strings.HasSuffix("NLT_abc.jpg", "abc")
fmt.Printf("str=%v\n", b) //str=false

11-時間和日期相關函式(202)

  1. time.Time型別,用於表示時間

  2. 當前時間、年月日時分秒

now := time.Now()
fmt.Printf("now=%v now type=%T", now, now)

//now=2021-03-25 16:40:28.2768429 +0800 CST m=+0.003021201 now type=time.Time

//獲取年月日時分秒
fmt.Printf("年=%v\n", now.Year())
fmt.Printf("月=%v\n", now.Month())
fmt.Printf("月=%v\n", int(now.Month()))
fmt.Printf("日=%v\n", now.Day())
fmt.Printf("時=%v\n", now.Hour())
fmt.Printf("分=%v\n", now.Minute())
fmt.Printf("秒=%v\n", now.Second())=March 
月=3=25=16=43=42
  1. 格式化日期時間

    1. printf或者sprintf (sprintf 字串)
    2. 使用time.Format()方法

2006-01-02 15:04:05這個字串必須這樣寫!

下面的自由調整以獲取靈活的時間組合

now := time.Now()
fmt.Printf("當前年月日 %d-%d-%d %d:%d:%d\n", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())

datastr := fmt.Sprintf("當前年月日 %d-%d-%d %d:%d:%d\n", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
fmt.Printf("datastr=%v", datastr)

fmt.Printf(now.Format("2006-01-02 15:04:05"))
fmt.Println()
fmt.Printf(now.Format("2006-01-02"))
fmt.Println()
fmt.Printf(now.Format("15:04:05"))
fmt.Println()

---------------------------------------------------------------
當前年月日 2021-4-15 19:52:44 
datastr=當前年月日 2021-4-15 19:52:44 
2021-04-15 19:52:44 
2021-04-15 
19:52:44

//fmt.Printf(now.Format("2006-02-01 15:04:05"))
  1. 時間的常量

不能除,所以產生那麼多常量,不能用 Second /10

const (
   Nanosecond  Duration = 1                  //納秒
   Microsecond          = 1000 * Nanosecond  //微妙
   Millisecond          = 1000 * Microsecond //毫秒
   Second               = 1000 * Millisecond //秒
   Minute               = 60 * Second        //分鐘
   Hour                 = 60 * Minute        //小時
)

//time.secod 秒

i := 0
for {
   i++
   fmt.Println(i)
   time.Sleep(time.Second)
   time.Sleep(time.Millisecond*100) //100毫秒
}
  1. time的Unix和UnixNano的方法(作用:獲取到隨機數)
    1. Uinx int64 從1970年到現在經過的時間 單位秒
    2. UnixNano 從1970年到現在經過的時間 單位納秒
      1. 如果值超過了int64返回未定義
fmt.Printf("unix=%v unixnano=%v", time.Now().Unix(), time.Now().UnixNano())
//unix=1616731171 unixnano=1616731171139452200
  1. 時間和日期的課堂練習

統計函式執行的時間

func main(){
   start := time.Now().Unix()
   fmt.Println(start)

   test01()
   end := time.Now().Unix()
   fmt.Println(end)
   fmt.Printf("執行test01()耗時%v秒", end-start)
}
func test01() {
   str := ""
   for i := 0; i < 10000; i++ {
      str += "hello" + strconv.Itoa(i)
      fmt.Println(str)
   }
}
  1. 內建函式

studygolang.com/pkgdoc

  1. len:用來求長度,比如 string、array、slice、map、channel
  2. new:用來分配記憶體,主要用來分配值型別,比如 int、float32,struct…返回的是指標
  3. make:用來分配記憶體,主要喲用來分配引用型別,比如chan、map、slice
陣列:v中元素的數量
陣列指標:*v中元素的數量(v為nil時panic)
切片、對映:v中元素的數量;若v為nil,len(v)即為零
字串:v中位元組的數量
通道:通道快取中佇列(未讀取)元素的數量;若v為 nil,len(v)即為零

指標存的是一個地址,這個地址指向了一個值
num1 := 100
fmt.Printf("num1的型別%T,num1的值%v,num1的地址%v\n", num1, num1, &num1)

num2 := new(int)
//num2的型別 *ine
//num2的值 = 一個地址A(系統分配)
//num2的地址  = 地址A指向的地址B(系統分配)
//num2指向的值 = 0
fmt.Printf("num2的型別%T,num2的值%v,num2的地址%v", num2, num2, &num2,*num2)

------------------------------------------------------------------
num1的型別int,num1的值100,num1的地址0xc00000a0a0 
num2的型別*int,num2的值0xc00000a0a8,num2的地址0xc000006030

變數存的是一個值,值有個地址

指標存的是一個地址A,這個地址對應的有個值B,這個值自身也有地址C

六、函式、包和錯誤處理

六、函式、包和錯誤處理

  1. Go錯誤處理機制

func test() {
   //使用defer + recover 來捕獲和處理異常
   defer func() {
      err := recover() //recover()內建函式,可以捕獲到異常
      if err != nil {
         fmt.Println("err=", err)
      }
   }()

   num1 := 10
   num2 := 0
   res := num1 / num2
   fmt.Println("res=", res)
}

func main() {
   test()
   fmt.Println("main下的方法")
}

----------------------------------------------------------
err= runtime error: integer divide by zero 
main下的方法
  1. 在預設情況下,當發生錯誤後(panic) ,程式就會退出(崩潰.)
  2. 如果我們希望:當發生錯誤後,可以捕獲到錯誤,並進行處理,保證程式可以繼續執行
  3. 這裡引出我們要將的錯誤處理機制
基本說明
  1. Go 語言不支援傳統的 try…catch…finally 這種處理
  2. Go 中引入的處理方式為:defer, panic, recover
  3. Go 中可以丟擲一個 panic 的異常,然後在 defer 中通過 recover 捕獲這個異常,然後正常處理
使用 defer+recover 來處理錯誤

8-自定義錯誤

Go 程式中,也支援自定義錯誤, 使用 errors.New 和 panic 內建函式。

  1. errors.New(“錯誤說明”) , 會返回一個 error 型別的值,表示一個錯誤
  2. panic 內建函式 ,接收一個 interface{}型別的值(也就是任何值了)作為引數。可以接收 error 類

型的變數,輸出錯誤資訊,並退出程式.


func main() {
   test02()
}

func test02() {
   err := readConf("config2.ini")
   if err != nil {
      panic(err)
   }

   fmt.Println("test02繼續執行")
}

func readConf(name string) (err error) {
   if name == "config.ini" {
      return nil
   } else {
      return errors.New("讀取檔案錯誤")
   }
}

六、函式、包和錯誤處理

函式作業

迴圈列印輸入的月份的天數   使用continue實現
要有判斷輸入的月份是否錯誤的語句

六、函式、包和錯誤處理

編寫一個函式
隨機猜數遊戲:
隨機生成一個1--100的整數
有十次機會
如果第一次就猜中,提示:“天才”
如果第2--3次猜中,提示“聰明”
如果第4-9次猜中,提示“一般”
如果最後一次猜中,提示“猜對”
一次都沒有猜對,提示“哈哈”
編寫一個函式,輸出100以內的所有素數(素數就是隻能被1和本身整除的書)。每行顯示5個,並求和
編寫一個函式,判斷是打魚還是曬網
如果從199011日起開始執行“三天打魚兩天曬網”,如何判斷在以後的某一天中是打魚還是曬網
輸出小寫的a-z以及大寫的Z-A,使用for迴圈。Asic碼
本作品採用《CC 協議》,轉載必須註明作者和本文連結