## **函數的基本概念**
為完成某一功能的程序指令(語句)的集合,稱為函數。
在 Go 中,函數分為: 自定義函數、系統函數(查看 Go 編程手冊)


#### **包的引出**
1) 在實際的開發中,我們往往需要在不同的文件中,去調用其它文件的定義的函數,比如 main.go 中,去使用 utils.go 文件中的函數,如何實現? \-》包
2) 現在有兩個程序員共同開發一個 Go 項目,程序員 xiaoming 希望定義函數 Cal ,程序員 xiaoqiang 也想定義函數也叫 Cal。兩個程序員為此還吵了起來,怎么辦? -》包
包的本質實際上就是創建不同的文件夾,來存放程序文件。

#### **包的基本概念**
說明:go 的每一個文件都是屬于一個包的,也就是說 go 是以包的形式來管理文件和項目目錄結構 的
#### **包的三大作用**
區分相同名字的函數、變量等標識符
當程序文件很多時,可以很好的管理項目
控制函數、變量等訪問范圍,即作用域
#### **包的相關說明**
打包基本語法
package 包名
引入包的基本語法
import "包的路徑"
包快速入門
-Go 相互調用函數,我們將 func Cal 定義到文件 utils.go , 將 utils.go 放到一個包中,當 其它文件需要使用到 utils.go 的方法時,可以 import 該包,就可以使用了. 【為演示:新建項目目錄結 構】


#### **包使用的注意事項和細節討論**
1) 在給一個文件打包時,該包對應一個文件夾,比如這里的 utils 文件夾對應的包名就是 utils, 文件的包名**通常**和文件所在的文件夾名一致,**一般**為小寫字母。
2) 當一個文件要使用其它包函數或變量時,需要先引入對應的包
引入方式 1:import "包名"
引入方式 2:
```
import (
"包名"
"包名"
)
```
package 指令在 文件第一行,然后是 import 指令。
在 import 包時,路徑從 $GOPATH 的 src 下開始,不用帶 src , 編譯器會自動從 src 下開始引入
3) 為了讓其它包的文件,可以訪問到本包的函數,則該函數名的**首字母需要大寫**,類似其它語言 的 public ,這樣才能跨包訪問。比如 utils.go 的
4) 在訪問其它包函數,變量時,其語法是 包名.函數名, 比如這里的 main.go 文件中
5) 如果包名較長,Go 支持給包取別名, 注意細節:取別名后,原來的包名就不能使用了
說明: 如果給包取了別名,則需要使用別名來訪問該包的函數和變量
6) 在同一包下,不能有相同的函數名(也不能有相同的全局變量名),否則報重復定義
7) 如果你要編譯成一個可執行程序文件,就需要將這個包聲明為 main , 即 package main .這個就 是一個語法規范,如果你是寫一個庫 ,包名可以自定義

## **函數的調用機制**

(1) 在調用一個函數時,會給該函數分配一個新的空間,編譯器會通過自身的處理讓這個新的空間和其它的棧的空間區分開來
(2) 在每個函數對應的棧中,數據空間是獨立的,不會混淆
(3) 當一個函數調用完畢(執行完畢)后,程序會銷毀這個函數對應的棧空間。
#### **return 語句**

#### **函數的遞歸調用**




**函數遞歸需要遵守的重要原則**
1) 執行一個函數時,就創建一個新的受保護的獨立空間(新函數棧)
2) 函數的局部變量是獨立的,不會相互影響
3) 遞歸必須向退出遞歸的條件逼近,否則就是無限遞歸,死龜了:)
4) 當一個函數執行完畢,或者遇到 return,就會返回,遵守誰調用,就將結果返回給誰,同時當函數執行完畢或者返回時,該函數本身也會被系統銷毀
**函數使用的注意事項和細節討論**
1) 函數的形參列表可以是多個,返回值列表也可以是多個。
2) 形參列表和返回值列表的數據類型可以是值類型和引用類型。
3) 函數的命名遵循標識符命名規范,首字母不能是數字,首字母大寫該函數可以被本包文件和其 它包文件使用,類似 public , 首字母小寫,只能被本包文件使用,其它包文件不能使用,類似 privat
4) 函數中的變量是局部的,函數外不生效【案例說明】
5) **基本數據類型和數組**默認都是值傳遞的,即進行值拷貝。在函數內修改,不會影響到原來的值。

6) 如果希望函數內的變量能修改函數外的變量(**指的是默認以值傳遞的方式的數據類型)**,可以傳 入變量的地址&,函數內以指針的方式操作變量。從效果上看類似引用 。

7) Go 函數不支持函數重載(同一個函數根據形參的個數和類型返回不同的參數)

8) 在 Go 中,函數也是一種數據類型,可以賦值給一個變量,則該變量就是一個函數類型的變量 了。通過該變量可以對函數調用

9) 函數既然是一種數據類型,因此在 Go 中,函數可以作為形參,并且調用

10) 為了簡化數據類型定義,Go 支持自定義數據類型
基本語法:type 自定義數據類型名 數據類型 // 理解: 相當于一個別名
案例:type myInt int // 這時 myInt 就等價 int 來使用了.

11) 支持對函數返回值命名

12) 使用 _ 標識符,忽略返回值
13) Go 支持可變參數


### **init 函數**
每一個源文件都可以包含一個 init 函數,該函數會在 main 函數執行前,被 Go 運行框架調用,也 就是說 init 會在 main 函數前被調用。
**inti 函數的注意事項和細節**
1) 如果一個文件同時包含全局變量定義,init 函數和 main 函數,則執行的流程全局變量定義\->init 函數\->main 函數
2) init 函數最主要的作用,就是完成一些初始化的工作,比如下面的案例
3) 細節說明: 面試題:案例如果 main.go 和 utils.go 都含有 變量定義,init 函數時,執行的流程 又是怎么樣的呢?

**匿名函數**
Go 支持匿名函數,匿名函數就是沒有名字的函數,如果我們某個函數只是希望使用一次,可以考 慮使用匿名函數,匿名函數也可以實現多次調用。
匿名函數使用方式 1:
在定義匿名函數時就直接調用,這種方式匿名函數只能調用一次。

匿名函數使用方式 2:
將匿名函數賦給一個變量(函數變量),再通過該變量來調用匿名函數 【案例演示】

**全局匿名函數**
如果將匿名函數賦給一個全局變量,那么這個匿名函數,就成為一個全局匿名函數,可以在程序 有效。

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

1) AddUpper 是一個函數,返回的數據類型是 fun (int) int
2) 閉包的說明

返回的是一個匿名函數, 但是這個匿名函數引用到函數外的 n ,因此這個匿名函數就和 n 形成一個整體,構成閉包。
3) 大家可以這樣理解: 閉包是類, 函數是操作,n 是字段。函數和它使用到 n 構成閉包。
4) 當我們反復的調用 f 函數時,因為 n 是初始化一次,因此每調用一次就進行累計。
5) 我們要搞清楚閉包的關鍵,就是要分析出返回的函數它使用(引用)到哪些變量,因為函數和它引 用到的變量共同構成閉包。
6) 對上面代碼的一個修改,加深對閉包的理解

#### **閉包的最佳實踐**
1) 編寫一個函數 makeSuffix(suffix string) 可以接收一個文件后綴名(比如.jpg),并返回一個閉包 2) 調用閉包,可以傳入一個文件名,如果該文件名沒有指定的后綴(比如.jpg) ,則返回 文件名.jpg , 如果已經有.jpg 后綴,則返回原文件名。
3) 要求使用閉包的方式完4) strings.HasSuffix , 該函數可以判斷某個字符串是否有指定的后綴。

1) 返回的匿名函數和 makeSuffix (suffix string) 的 suffix 變量 組合成一個閉包,因為 返回的函數引用 到 suffix 這個變量
2) 我們體會一下閉包的好處,如果使用傳統的方法,也可以輕松實現這個功能,但是傳統方法需要每 次都傳入 后綴名,比如 .jpg ,而閉包因為可以保留上次引用的某個值,所以我們傳入一次就可以反復 使用。大家可以仔細的體會一把!
#### **函數的 defer**
在函數中,程序員經常需要創建資源(比如:數據庫連接、文件句柄、鎖等) ,為了在函數執行完 畢后,及時的釋放資源,Go 的設計者提供 defer (延時機制)。

1) 當 go 執行到一個 defer 時,不會立即執行 defer 后的語句,而是將 defer 后的語句壓入到一個棧 中\[我為了講課方便,暫時稱該棧為 defer 棧\], 然后繼續執行函數下一個語句。
2) 當函數執行完畢后,在從 defer 棧中,依次從棧頂取出語句執行(注:遵守棧 先入后出的機制),所以同學們看到前面案例輸出的順序。
3) 在 defer 將語句放入到棧時,也會將相關的值拷貝同時入棧。請看一段代碼:
**defer的最佳實踐**
defer 最主要的價值是在,當函數執行完畢后,可以及時的釋放函數創建的資源。看下模擬代碼。。

1) 在 golang 編程中的通常做法是,創建資源后,比如(打開了文件,獲取了數據庫的鏈接,或者是 鎖資源), 可以執行 defer file.Close() defer connect.Close()
2) 在 defer 后,可以繼續使用創建資源.
3) 當函數完畢后,系統會依次從 defer 棧中,取出語句,關閉資源.
4) 這種機制,非常簡潔,程序員不用再為在什么時機關閉資源而煩心。
#### **函數參數傳遞方式**
1) 值傳遞 2) 引用傳遞
其實,不管是值傳遞還是引用傳遞,傳遞給函數的都是變量的副本,不同的是,值傳遞的是值的 拷貝,引用傳遞的是地址的拷貝,一般來說,地址拷貝效率高,因為數據量小,而值拷貝決定拷貝的 數據大小,數據越大,效率越低。
**值類型和引用類型**
1) 值類型:基本數據類型 int 系列, float 系列, bool, string 、數組和結構體 struct
2) 引用類型:指針、slice 切片、map、管道 chan、interface 等都是引用類型
**值傳遞和引用傳遞使用特點**
一下講過重新回顧


#### **變量作用域**
1) 函數內部聲明/定義的變量叫局部變量,作用域僅限于函數內部
2) 函數外部聲明/定義的變量叫全局變量,作用域在整個包都有效,如果其首字母為大寫,則作用 域在整個程序有效
3) 如果變量是在一個代碼塊,比如 for / if 中,那么這個變量的的作用域就在該代碼塊


#### **字符串常用的系統函數**
1) 統計字符串的長度,按字節 len(str)

2) 字符串遍歷,同時處理有中文的問題 r := []rune(str)

3) 字符串轉整數: n, err := strconv.Atoi("12")

4) 整數轉字符串 str = strconv.Itoa(12345)

5) 字符串 轉 \[\]byte: var bytes = []byte("hello go")

6) \[\]byte 轉 字符串: str = string(\[\]byte{97, 98, 99})

7) 10 進制轉 2, 8, 16 進制: str = strconv.FormatInt(123, 2) // 2-> 8 , 16

8) 查找子串是否在指定的字符串中: strings.Contains("seafood", "foo") //true

9) 統計一個字符串有幾個指定的子串 : strings.Count("ceheese", "e") //4

10) 不區分大小寫的字符串比較(==是區分字母大小寫的): fmt.Println(strings.EqualFold("abc","Abc")) // true

11) 返回子串在字符串第一次出現的 index 值,如果沒有返回\-1 : strings.Index("NLT\_abc", "abc") // 4

12) 返回子串在字符串最后一次出現的 index,如沒有返回\-1 : strings.LastIndex("go golang", "go")

13) 將指定的子串替換成 另外一個子串: strings.Replace("go go hello", "go", "go 語言", n) n 可以指定你希望替換幾個,如果 n=-1 表示全部替換

14) 按 照 指 定 的 某 個 字 符 , 為 分 割 標 識 , 將 一 個 字 符 串 拆 分 成 字 符 串 數 組 :strings.Split("hello,wrold,ok", ",")

15) 將字符串的字母進行大小寫的轉換: strings.ToLower("Go") // go strings.ToUpper("Go") // GO

16) 將字符串左右兩邊的空格去掉: strings.TrimSpace(" tn a lone gopher ntrn ")

17) 將字符串左右兩邊指定的字符去掉 : strings.Trim("! hello! ", " !") // \["hello"\] //將左右兩邊 !和 " "去掉

18) 將字符串左邊指定的字符去掉 : strings.TrimLeft("! hello! ", " !") // \["hello"\] //將左邊 ! 和 " "去掉
19) 將字符串右邊指定的字符去掉 :strings.TrimRight("! hello! ", " !") // \["hello"\] //將右邊 ! 和 " "去掉
20) 判斷字符串是否以指定的字符串開頭: strings.HasPrefix("ftp://192.168.10.1", "ftp") // true

21) 判斷字符串是否以指定的字符串結束: strings.HasSuffix("NLT\_abc.jpg", "abc") //false
#### **時間和日期相關函數**
1) 時間和日期相關函數,需要導入 time 包
2) time.Time 類型,用于表示時間

3) 如何獲取到其它的日期信息

4) 格式化日期時間
方式 1: 就是使用 Printf 或者 SPrintf

方式二: 使用 time.Format() 方法完成

5) 時間的常量

6) 結合 Sleep 來使用一下時間常量

7) time 的 Unix 和 UnixNano 的方法

#### **內置函數**
Golang 設計者為了編程方便,提供了一些函數,這些函數可以直接使用,我們稱為 Go 的內置函 數。文檔:https://studygolang.com/pkgdoc -> builtin
1) len:用來求長度,比如 string、array、slice、map、channel 2) new:**用來分配內存,主要用來分配值類型**,比如 int、float32,struct...返回的是指針 new


3) make:用來分配內存,**主要用來分配引用類型**,比如 channel、map、slice。這個我們后面講解
#### **錯誤處理**
1) Go 語言追求簡潔優雅,所以,Go 語言不支持傳統的 try…catch…finally 這種處理。
2) Go 中引入的處理方式為:defer, panic, recover

3) 這幾個異常的使用場景可以這么簡單描述:Go 中可以拋出一個 panic 的異常,然后在 defer 中 通過 recover 捕獲這個異常,然后正常處理
**自定義錯誤**
Go程序中,也支持自定義錯誤,使用errors.New和panic內置函數。
1)errors.New("錯誤說明"),會返回一個error類型的值,表示一個錯誤
2)panic內置函數,接收一個interface{}類型的值(也就是任何值了)作為參數。可以接收error類型的變量,輸出錯誤信息,并退出程序.
