
1. 概述
函數(shù)是將實(shí)現(xiàn)單一或者相關(guān)聯(lián)功能的代碼組織起來(lái),內(nèi)部實(shí)現(xiàn)具有封閉性的代碼集合,函數(shù)可以提高應(yīng)用程序的模塊性和功能代碼的復(fù)用性.對(duì)大多數(shù)編程語(yǔ)言而言,函數(shù)是很重要的部分.
2. 聲明函數(shù)
Go語(yǔ)言中函數(shù)聲明用
func標(biāo)識(shí)這是一個(gè)函數(shù) 后面跟著函數(shù)名參數(shù)列表返回參數(shù)列表函數(shù)主體內(nèi)容
// 形式如下
func 函數(shù)名稱(參數(shù)列表) (返回參數(shù)列表) {
函數(shù)執(zhí)行體
}
對(duì)函數(shù)簡(jiǎn)單說(shuō)明 :
函數(shù)名的命名和我們聲明一個(gè)變量是一樣的,由字母數(shù)字下劃線組成.函數(shù)名不能以數(shù)字開(kāi)頭,同一個(gè)包中函數(shù)名不能重復(fù)
函數(shù)名盡量做到見(jiàn)名知意,函數(shù)名中字母的大小寫(xiě)不同,會(huì)被認(rèn)為是不同的函數(shù)
一個(gè)包中的函數(shù)名首字母大寫(xiě),表示該函數(shù)能被外部包訪問(wèn)到
//函數(shù)add 和 函數(shù) Add 是兩個(gè)不同的函數(shù) func add(){} func Add(){}
參數(shù)列表 : 表示可以存在多個(gè)
形式參數(shù), 每個(gè)形式參數(shù)都是由參數(shù)變量名和參數(shù)數(shù)據(jù)類型組成func demo(x int ,y float64, s string){}
返回參數(shù)列表 : 可以是返回值的數(shù)據(jù)類型列表,也可以類似參數(shù)列表一樣
返回參數(shù)變量名和數(shù)據(jù)類型的組成,函數(shù)聲明了有返回值,那就必須用return語(yǔ)句 提供返回值列表func demo1(x int ,y float64)(int,error){ ... ... return x+y error }
- 函數(shù)執(zhí)行體 : 能復(fù)用的代碼片段
2.1 函數(shù)基本形式
package main
import (
"fmt"
"math"
)
// 基本函數(shù)格式寫(xiě)法
func hypot(x float64 ,y float64) (res float64){
res = math.Sqrt(x*x+y*y)
// 因?yàn)榉祷刂德暶髁俗兞縭es ,所以return 默認(rèn)將res返回
return
}
// 如果參數(shù)列表中參數(shù)類型相同,可以簡(jiǎn)寫(xiě)
func swop(x,y int,s1,s2 string) (int,int,string,string){
x,y = y,x
s1 ,s2 = s1 ,s2
// Go語(yǔ)言是支持多返回值得
// 以為返回值列表中沒(méi)有聲明返回變量,所以return要將返回的值寫(xiě)清楚
// 定義了多少返回值,就必須將它們都返回,否則編譯報(bào)錯(cuò)`not enough arguments to return`
return x,y,s1,s2
}
func main(){
// 此處都是函數(shù)調(diào)用
fmt.Println(hypot(7,9))
fmt.Println(swop(10,99,"hello","golang"))
}
go run main.go
11.40175425099138
99 10 hello golang
示例:
package main
import "fmt"
const(
M = 60
H = M*60
D = H*24
)
func parseTime(s float64) (m ,h,d float64){
m = s/M
h = s/H
d = s/D
return
}
func main(){
s := 1797979
m,h,d := parseTime(float64(s))
fmt.Printf("%d秒約等于%f分鐘,%f小時(shí),%f天",s,m,h,d)
}
go run main.go
1797979秒約等于29966.316667分鐘,499.438611小時(shí),20.809942天
2.2 值傳遞與引用傳遞
基本數(shù)據(jù)類型和數(shù)組默認(rèn)是值傳遞,在函數(shù)內(nèi)部修改,不影響原有的值
希望函數(shù)函數(shù)內(nèi)部修改,能影響到原有的變量,那就采用引用傳遞,將變量的地址
&傳給函數(shù),函數(shù)內(nèi)部以指針形式操作
:star: 不論是值傳遞還是引用傳遞,其實(shí)給函數(shù)的都是變量副本,不同的是,值傳遞的是值得拷貝副本,而引用傳遞則是地址的拷貝,通常地址拷貝因?yàn)閿?shù)據(jù)量小,故效率更高.而值拷貝則是數(shù)據(jù)量越大,效率越差
值傳遞類型 : 所有
int類型 , 所有float類型 ,bool類型 ,數(shù)組,結(jié)構(gòu)體引用類型 :
指針,slice,map,chan,interface等類型
值傳遞的示例:
package main
import "fmt"
//都是值傳遞,拷貝了一個(gè)副本數(shù)據(jù)
func passValue(n int, f float64, s string, arr [3]int) (int, float64, string,[3]int) {
arr[1] = arr[1] * 100
return n * 2, f * f, s + "~~~~" ,arr
}
func main() {
n := 10
f := 99.9
s := "hello"
arr := [3]int{22, 33, 44}
// 調(diào)用函數(shù)
n1, f1, s1 ,arr1:= passValue(n, f, s, arr)
fmt.Println(n1, f1, s1, arr1)
// 原來(lái)變量的值沒(méi)有任何影響
fmt.Println(n, f, s, arr)
}
go run main.go
20 9980.010000000002 hello~~~~ [22 3300 44]
10 99.9 hello [22 33 44]
引用傳遞示例 :
package main
import "fmt"
// 引用傳遞1 數(shù)據(jù)類型本身就時(shí)引用類型
func citePass(sli []string, m map[string]string) {
sli[0] = "AAA"
m["job"] = "programmer"
}
// 引用傳遞2 指針形式
func citePass2(n *int) {
*n = *n +100
}
func main() {
//定義一個(gè)slice
var sli []string = []string{"aa", "bb", "cc", "dd"}
//定義一個(gè)map
var person = make(map[string]string)
person["name"] = "tom"
person["addr"] = "Provence"
fmt.Printf("sli的len = %d,cap = %d value = %v\n", len(sli), cap(sli), sli)
fmt.Println(person)
//執(zhí)行函數(shù)citePass之后
citePass(sli, person)
fmt.Printf("sli的len = %d,cap = %d value = %v\n", len(sli), cap(sli), sli)
fmt.Println(person)
n := 10
citePass2(&n)
fmt.Println(n)
}
go run main.go
sli的len = 4,cap = 4 value = [aa bb cc dd]
map[addr:Provence name:tom]
sli的len = 4,cap = 4 value = [AAA bb cc dd]
map[addr:Provence job:programmer name:tom]
110
3. 函數(shù)變量
在Go語(yǔ)言中,函數(shù)像值一樣擁有類型,可以賦值給變量,傳遞給函數(shù),從函數(shù)中返回
package main
import (
"fmt"
"strings"
)
func demo1(n int) int {
return n * n
}
func demo2(s string) string{
return strings.ToUpper(s)
}
func main(){
// 定義一個(gè)函數(shù)變量
f := demo1
fmt.Printf("f type = %T\n",f)
fmt.Println(f(9))
// 聲明一個(gè)函數(shù)類型的變量 s
var s func(string)string
s = demo2
fmt.Printf("s type = %T\n s = %s",s,s("hello golang"))
}
go run main.go
f type = func(int) int
81
s type = func(string) string
s = HELLO GOLANG
函數(shù)作為一個(gè)數(shù)據(jù)類型在其他函數(shù)中作為參數(shù)的數(shù)據(jù)類型
package main
import "fmt"
func getSum(x, y int) int {
return x + y
}
// demo1 的參數(shù)類型是func()類型
func demo1(f func(int,int)int, a int,b int) int {
res := f(a,b)
return a+res
}
func main() {
// 定義一個(gè)函數(shù)類型
f := getSum
// 函數(shù)類型作為實(shí)參傳遞
res := demo1(f,20,80)
fmt.Println(res)
}
go run main.go
120
函數(shù)作為返回類型
package main
import "fmt"
func getSum(x, y int) int {
return x + y
}
// 函數(shù)作為返回類型
func demo2() (f func(int,int)int){
return getSum
}
func main() {
rf := demo2()
fmt.Printf("rf type = %T\n",rf)
fmt.Println(rf(20,100))
}
go run main.go
rf type = func(int, int) int
120
4. 匿名函數(shù)
匿名函數(shù)就是沒(méi)有函數(shù)名的函數(shù),只有函數(shù)體,匿名函數(shù)具有函數(shù)的一般特性.可以被傳遞,賦值給變量等
4.1 定義形式1
定義匿名函數(shù)時(shí)直接使用
package main
import "fmt"
func main() {
res := func(x,y int) int {
return x+y
}(90,10)
fmt.Println(res)
}
4.2 定義形式2
將匿名函數(shù)賦值給一個(gè)變量
package main
import "fmt"
func main() {
sum := func(x,y int) int {
return x+y
}
fmt.Printf("sum type is %T\n",sum)
fmt.Println(sum(90,90))
}
4.3 定義形式3
定義成全局匿名函數(shù)
package main
import "fmt"
var func1 = func(x,y int) int {
max :=0
if x>y{
max = x
}else if x<y{
max = y
}
return max
}
func main() {
fmt.Printf("func1 type is %T\n",func1)
fmt.Println(func1(90,190))
}
4.4 匿名函數(shù)作為回調(diào)函數(shù)
package main
import "fmt"
func demo(s []int,f func(int)int) []int{
for k,v:=range s{
s[k] = f(v)
}
return s
}
func main() {
res := demo([]int{1,2,3,4,5}, func(i int) int {
return i*i
})
fmt.Println(res)
}
4.5 匿名函數(shù)示例
package main
import (
"flag"
"fmt"
)
var skillString = flag.String("skill","","programmer have skill")
func main(){
flag.Parse()
var skill = map[string]func(){
"sing": func() {
fmt.Println("會(huì)唱歌")
},
"dance":func(){
fmt.Println("會(huì)跳舞")
},
"rap": func() {
fmt.Println("會(huì)rap")
},
"code": func() {
fmt.Println("會(huì)寫(xiě)code")
},
}
if f,ok := skill[*skillString];ok{
f()
}else{
fmt.Println("這個(gè)真沒(méi)有...")
}
}
5. 閉包
閉包是引用了自由變量的函數(shù),被引用的自由變量和函數(shù)一同存在,即使己經(jīng)離開(kāi)了
自由變量的環(huán)境也不會(huì)被釋放或者刪除,在閉包中可以繼續(xù)使用這個(gè)自由變量。因此,簡(jiǎn)
單的說(shuō) :
函數(shù)+引用環(huán)境=閉包
package main
import "fmt"
func counter(n int) func() int{
// 返回一個(gè)匿名函數(shù),該匿名函數(shù)內(nèi)引用了函數(shù)外的變量n,因此匿名函數(shù)和n構(gòu)成了一個(gè)閉包
return func() int {
n++
return n
}
}
func main(){
f := counter(1)
fmt.Printf("%p\n",f)
fmt.Println(f())
fmt.Println(f())
fmt.Println(f())
fmt.Printf("%p\n",f)
f1 := counter(10)
fmt.Printf("%p\n",f1)
fmt.Println(f1())
fmt.Println(f1())
fmt.Println(f1())
fmt.Printf("%p\n",f1)
}
go run main.go
0x4932a0
2
3
4
0x4932a0
0x4932a0
11
12
13
0x4932a0
另外一個(gè)示例
package main
import (
"fmt"
"strings"
)
func demo(s string) func(string) string {
return func(n string) string {
if !strings.HasSuffix(n, s) {
return n + s
}
return n
}
}
func main() {
d := demo(".png")
fmt.Println(d("aaa"))
fmt.Println(d("bbb.png"))
}
go run main.go
aaa.png
bbb.png
6. 函數(shù)的可變參數(shù)
函數(shù)的參數(shù)列表沒(méi)有固定的個(gè)數(shù)的參數(shù)
定義格式如下 :
func 函數(shù)名(固定參數(shù)列表,V ...T)(返回參數(shù)列表){ 函數(shù)執(zhí)行體 }
- V表示可變參數(shù)變量,T表示參數(shù)的數(shù)據(jù)類型, 那么V的類型是 []T,數(shù)據(jù)類型為T的切片
...固定語(yǔ)法表示參數(shù)個(gè)數(shù)不確定- 可變參數(shù)一般放在參數(shù)列表最尾部
示例 1:
package main
import (
"fmt"
)
func demo1(s string, sliString ...string) {
// 可變參數(shù)是數(shù)據(jù)類型為string的切片
fmt.Printf("%T\n",sliString)
for _,v:= range sliString{
fmt.Println(s+" "+v)
}
}
func demo2(s ...string) string{
res := ""
for _,v:= range s{
res += v
}
return res
}
func main(){
demo1("hello","ok","nice","yes","good")
fmt.Println(demo2("Let ","Us ","Go"))
}
go run main.go
[]string
hello ok
hello nice
hello yes
hello good
Let Us Go
示例2: 可變參數(shù)函數(shù)之間參數(shù)傳遞
package main
import "fmt"
func pri (s ...interface{}){
for _,v := range s{
fmt.Println(v)
}
}
func dump(s ...interface{}){
pri(s...)
}
func main(){
dump("golang",2.0)
}
示例3: (獲取數(shù)據(jù)類型)
package main
import (
"bytes"
"fmt"
)
func getType(s ...interface{}) string {
var b bytes.Buffer
for _, v := range s {
str := fmt.Sprintf("%v", v)
var typeS string
switch v.(type) {
case bool:
typeS = "bool"
case int:
typeS = "int"
case string:
typeS = "string"
case float64:
typeS = "float64"
case []string:
typeS = "slice"
default:
typeS = "unknow"
}
b.WriteString("value : ")
b.WriteString(str)
b.WriteString(" , type : ")
b.WriteString(typeS)
b.WriteString("\n")
}
return b.String()
}
func main() {
res := getType(90, "golang", true, 123.234, []string{"demo"})
fmt.Println(res)
}
go run mian.go
value : 90 , type : int
value : golang , type : string
value : true , type : bool
value : 123.234 , type : float64
value : [demo] , type : slice
7.延遲調(diào)用defer
Go語(yǔ)言中關(guān)鍵字
defer表示延遲跟在其后面的語(yǔ)句的執(zhí)行,被延遲的語(yǔ)句按照defer調(diào)用的順序逆向執(zhí)行,既:最后調(diào)用的defer語(yǔ)句,優(yōu)先執(zhí)行
defer語(yǔ)句調(diào)用遵照先進(jìn)后出的原則defer延遲機(jī)制,通常講是為了釋放資源,一些常見(jiàn)的創(chuàng)建資源的操作
打開(kāi)文件,連接數(shù)據(jù)庫(kù),枷鎖等在最后都需要關(guān)閉資源句柄,斷開(kāi)連接釋放鎖等操作,這些都可以交給defer來(lái)做
7.1 defer的執(zhí)行順序
package main
import "fmt"
func demo(s string) {
fmt.Println(s)
}
func main(){
fmt.Println("program start")
defer fmt.Println("one")
defer demo("two")
defer func() {
fmt.Println("three")
}()
fmt.Println("program over")
}
go run main.go
program start
program over
three
two
one
7.2 defer 使用示例
示例1 : 文件復(fù)制
從A文件復(fù)制到B文件,B文件不存在就創(chuàng)建
package main
import (
"fmt"
"io"
"os"
)
func copyFile(dstName, srcName string) (written int64, err error) {
// open打開(kāi)一個(gè)文件用于讀取,讀取成功,返回文件對(duì)象
src, err := os.Open(srcName)
// open錯(cuò)誤就直接返回錯(cuò)誤
if err != nil {
return
}
// defer操作關(guān)閉文件
defer src.Close()
//openFile也是打開(kāi)文件的函數(shù),可以使用指定項(xiàng)(os.O_RDONLY 只讀 os.O_CREATE 不存在就創(chuàng)建新文件 )和指定模式打開(kāi)文件
dst, err := os.OpenFile(dstName, os.O_RDONLY|os.O_CREATE, 0644)
if err != nil {
return
}
// defer操作關(guān)閉文件
defer dst.Close()
// io.copy()函數(shù)將src的數(shù)據(jù)拷貝到dst,直到src上到達(dá)EOF或者錯(cuò)誤
return io.Copy(dst, src)
}
func main() {
// 調(diào)用自定義函數(shù)copyFile
// Args 保存用戶命令行參數(shù) []string 類型
copyFile(os.Args[1], os.Args[2])
fmt.Println("賦值完成~~")
}
go run main.go "demo.txt" "english.log"
|_main.go
|_demo.txt
|_english.log
示例2 : 獲取文件大小
package main
import (
"fmt"
"os"
)
func getFileSize(filename string) int64{
f,err :=os.Open(filename)
defer f.Close()
if err != nil{
return 0
}
info ,err := f.Stat()
if err != nil{
return 0
}
size := info.Size()
return size
}
func main(){
// 獲取文件的大小(byte)
fmt.Println(getFileSize(os.Args[1]))
}
8. 錯(cuò)誤處理
錯(cuò)誤處理是幾乎所有編程語(yǔ)言都必須考慮的,Go語(yǔ)言中的
錯(cuò)誤就是可能出現(xiàn)錯(cuò)誤的地方真就出現(xiàn)了錯(cuò)誤,我們需要對(duì)這樣的錯(cuò)誤進(jìn)行處理,這么看錯(cuò)誤也是我我們業(yè)務(wù)代碼的一部分,錯(cuò)誤處理也將是開(kāi)發(fā)中必須中的必要環(huán)節(jié).正確的處理錯(cuò)誤,能保障程序更加健壯.Go語(yǔ)言中引入錯(cuò)誤處理的標(biāo)準(zhǔn)模式 ,
error接口,是Go語(yǔ)言內(nèi)建的接口類型Go語(yǔ)言中錯(cuò)誤處理有如下的特點(diǎn)
- 可能造成錯(cuò)誤的函數(shù),在返回值列表中應(yīng)該返回一個(gè)錯(cuò)誤接口
error如果函數(shù)調(diào)用成功,錯(cuò)誤接口返回的是nil- 函數(shù)在調(diào)用之后需要檢查錯(cuò)誤,如果有錯(cuò)誤,需要進(jìn)行錯(cuò)誤處理
package main
import (
"fmt"
"os"
)
func getFileSize(name string) (size int64, err error) {
size = 0
f, err := os.Open(name)
if err != nil {
return size, err
}
info, err := f.Stat()
if err != nil {
return size, err
}
size = info.Size()
return size, nil
}
func main() {
// aa.log 文件不存在
s, e := getFileSize("aa.log")
if e != nil {
fmt.Println(e)
} else {
fmt.Println(s)
}
fmt.Println("go on ...")
}
go run main.go
open aa.log: The system cannot find the file specified.
go on ...
8.1 自定義錯(cuò)誤
Go語(yǔ)言中支持自定義錯(cuò)誤,使用
errors.New和panic內(nèi)置函數(shù)
errors.New("錯(cuò)誤說(shuō)明")返回一個(gè)error類型的值,表示一個(gè)錯(cuò)誤panic()內(nèi)置函數(shù) 可以接收任意類型的值,作為參數(shù),輸出錯(cuò)誤信息,且退出程序
package main
import (
"errors"
"fmt"
)
func readConfig(name string) (err error) {
if name == "config.ini" {
return nil
} else {
return errors.New("讀取配置文件錯(cuò)誤..")
}
}
func test() {
err := readConfig("config2.ini")
if err != nil {
// 輸出錯(cuò)誤信息,并退出程序
panic(err)
}
fmt.Println("test() go on ..")
}
func main() {
test()
fmt.Println("client process go on ...")
}
go run main.go
panic: 讀取配置文件錯(cuò)誤..
goroutine 1 [running]:
main.test()
/Go/src/GoNote/chapter6/panic-operate/main.go:18 +0x79
main.main()
/Go/src/GoNote/chapter6/panic-operate/main.go:23 +0x29
8.2 異常的處理
Go語(yǔ)言程序有些錯(cuò)誤只能在運(yùn)行時(shí)檢查,像數(shù)組訪問(wèn)越界,空指針引用等,這些在運(yùn)行會(huì)引起panic異常,一旦發(fā)生panic異常,程序就會(huì)中斷執(zhí)行,并輸出日志,日志包括panic value和函數(shù)調(diào)用的堆棧跟蹤信息
Go語(yǔ)言中處理異常的關(guān)鍵函數(shù)是
deferpanicrecover簡(jiǎn)單描述是 : Go語(yǔ)言在運(yùn)行的時(shí)候拋出一個(gè)panic的異常,然后在defer中通過(guò)recover捕獲這個(gè)異常,然后正常的處理掉
8.2.1 觸發(fā)panic
panic 觸發(fā)一般有三種
- 手動(dòng)觸發(fā)
- 主動(dòng)觸發(fā)
- 延遲觸發(fā)
示例 : 手動(dòng)觸發(fā)panic
package main
import "fmt"
func main(){
fmt.Println("program start ...")
panic("手動(dòng)觸發(fā)panic")
fmt.Println("program end ...")
}
go run main.go
panic: 手動(dòng)觸發(fā)panic
goroutine 1 [running]:
main.main()
E:/Go/src/GoNote/chapter6/demo2/main/main.go:7 +0x9d
示例 : 自動(dòng)觸發(fā)panic
package main
import "fmt"
func main() {
defer fmt.Println("")
var arr = [...]string{"zero", "one", "two", "three", "four"}
length := len(arr)
for i := 0; i <= length; i++ {
fmt.Println(arr[i])
}
}
go run main.go
zero
one
two
three
four
panic: runtime error: index out of range
goroutine 1 [running]:
main.main()
E:/Go/src/GoNote/chapter6/demo3/main/main.go:10 +0x177
示例 : panic 在defer之后執(zhí)行
package main
import (
"fmt"
"time"
)
func demo1() {
defer func() {
fmt.Println(time.Now().Format("2006-01-02 15:04:05"))
}()
}
func main() {
demo1()
defer fmt.Println("first ...")
defer fmt.Println("second ...")
panic("really over")
}
go run main.go
2019-08-24 16:25:06
second ...
first ...
panic: really over
goroutine 1 [running]:
main.main()
E:/Go/src/GoNote/chapter6/demo4/main/main.go:18 +0xfd
8.2.2 recover 捕獲異常
無(wú)論是運(yùn)行時(shí)拋出的panic 還是其他形式的panic,我們都可以配合defer和recover實(shí)現(xiàn)異常的捕獲和恢復(fù),讓程序繼續(xù)運(yùn)行下去
本質(zhì)上Go語(yǔ)言中沒(méi)有異常系統(tǒng),使用panic和recover是模擬了其他編程語(yǔ)言中的應(yīng)對(duì)異常的機(jī)制
package main
import (
"fmt"
"runtime"
)
func divZero(){
num1 := 10
num2 := 0
res := num1 / num2
fmt.Println("res=", res)
}
func pointCite(){
var s *string
*s = "hello world"
}
func Run(f func()){
defer func() {
err := recover()
switch err.(type) {
case runtime.Error:
fmt.Println("runtime error : ",err)
default:
fmt.Println(err)
}
}()
f()
}
func main(){
fmt.Println("start===>")
Run(divZero)
fmt.Println("go on===>")
Run(pointCite)
fmt.Println("end===>")
}
go run main.go
start===>
runtime error : runtime error: integer divide by zero
go on===>
runtime error : runtime error: invalid memory address or nil pointer dereference
end===>
panic 和 recover 的關(guān)系
- 有panic 沒(méi)有recover 程序直接中斷
- 有panic 和recover捕獲,程序不會(huì)中斷,從報(bào)panic的點(diǎn)退出后,程序繼續(xù)執(zhí)行后面的流程