函数的基本语法 func 函数名(形参列表) 返回值列表{ 执行语句... return 返回值列表 } 案例: 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 }
函数使用时注意事项: 函数的形参列表可以是多个,返回值列表也可以是多个。 形参列表和返回值列表的数据类型可以是值类型,也可以是引用类型。; 函数的命名遵从标识符命名规则,首字母大写,则该函数可以被本包文件和其他包和文件使用。首字母小写则只能被本包文件使用,其他包文件不能使用。 函数中的变量是局部的。 基本数据类型和数组默认是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。 如果希望函数内的变量能够修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。 函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量,通过该变量可以对函数调用。 函数可以作为形参并且调用。 go支持自定义数据类型,基本语法: type myint int 此时myint就相当于int (注意,这里myInt与int就不是同一个数据类型了) type mySum func(int,int) int 此时mySum就相当于一个函数类型func(int,int) int 支持对函数返回值命名。 func cal(n1 int, n2 int)(sum int ,sub int) { sum = n1 + n2 sub = n1 - n2 return } func main() { n1 := 2 n2 := 1 a1,a2 :=cal(n1,n2) fmt.Println(a1,a2) } 使用_标识符,忽略返回值。 go支持可变参数。如果一个函数的形参列表有可变参数,则可变参数需要放在形参列表最后 // 支持0到多个参数 func sum(args... int) sum int { // args是slice 切片,通过 args[index]可以访问到各个值 } // 支持1到多个参数 func sum(n1 int,args... int) sum int { } init函数介绍:每一个源文件都可以包含一个init函数,该函数会在main函数执行前被Go运行框架调用。 案例: package main import ( "fmt" ) func init() { fmt.Println("init()...") } func main() { fmt.Println("main()...") } 输出的结果: D:gosrc est>go run main.go init()... main()... init函数的注意事项和细节: 如果一个文件同时包含全局变量定义、init函数和main函数,则执行的流程是变量定义 ->init ->main。 init函数的主要作用,就是完成一些初始化工作。 如果main.go 和引入的包 utils.go 都有变量定义、init函数,注意执行的顺序是 (1)引用的utils.go 包里面的变量定义 (2)执行utils.go 包里的init函数 (3)main.go 变量定义 (4)main.go 的init函数 (5)main.go 的main函数 匿名函数,如果我们某个函数只是使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用 匿名函数的使用方式: (1)在定义匿名函数的时候就直接调用,这种方式匿名函数只调用一次; func main() { res1 := func (n1 int,n2 int) int { return n1 + n2 }(10,20) fmt.Println("res1=",res1) } (2)将匿名函数赋予给一个变量,再通过变量来调用匿名函数; func main() { a := func (n1 int,n2 int) int { return n1 + n2 } res2 :=a(10,20) fmt.Println("res2=",res2) res3 :=a(30,40) fmt.Println("res3=",res3) } (3)全局匿名函数:将匿名函数赋值给一个全局变量; package main import ( "fmt" ) var ( Fun1 = func (n1 int,n2 int) int { return n1 * n2 } ) func main() { res4 :=Fun1(5,6) fmt.Println("res4=",res4) } 所谓闭包:就是一个函数和其相关的引用环境组合的一个整体。 案例: package main import ( "fmt" ) // 累加器 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)) // 11 fmt.Println(f(2)) // 13 fmt.Println(f(3)) // 16 } 对上述代码说明: (1)AddUpper是一个函数,返回的是func(int) int。 (2)func(int) int引用函数外的n,因此这个匿名函数和n形成了一个闭包。 (3)可以这么理解,闭包是类,函数是操作,n是字段。 (4)当我们反复调用f函数时,因为n是初始化一次,因此每调用一次就进行累计。 (5)搞清楚闭包的关键,就是分析出返回的函数和它引用到哪些变量形成闭包。 案例: package main import ( "fmt" ) 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)) // 11 fmt.Println(f(2)) // 13 fmt.Println(f(3)) // 16 } 输出结果为: str= hello$ 11 str= hello$$ 13 str= hello$$$ 16 闭包最佳实践案例: 判断文件名后缀是否是jpg(txt),如果是直接返回文件名,否则给文件加上.jpg(txt) func makeSuffixFunc(suffix string) func(string) string { return func(name string) string { if !strings.HasSuffix(name, suffix) { return name + suffix } return name } } func main() { jpgFunc := makeSuffixFunc(".jpg") txtFunc := makeSuffixFunc(".txt") fmt.Println(jpgFunc("test")) //test.jpg fmt.Println(txtFunc("test")) //test.txt } 闭包的优点: (1)能够读取函数内部的变量; (2)让变量长期贮存在内存中,不会在调用结束后被垃圾回收机制回收,因为该变量一直被其它函数引用; 缺点额也很明显:内存消耗很大。 defer语句 Go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行,最后被defer的语句,最先被执行。 由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。 defer语句案例: func main() { fmt.Println("start") defer fmt.Println(1) defer fmt.Println(2) defer fmt.Println(3) fmt.Println("end") } 输出结果: start end 3 2 1 案例: func f1() int { x := 5 defer func() { x++ }() return x } func f2() (x int) { defer func() { x++ }() return 5 } func f3() (y int) { x := 5 defer func() { x++ }() return x } func f4() (x int) { defer func(x int) { x++ }(x) return 5 } func main() { fmt.Println(f1()) // 5 fmt.Println(f2()) // 6 fmt.Println(f3()) // 5 fmt.Println(f4()) // 5 } 案例: func calc(index string, a, b int) int { ret := a + b fmt.Println(index, a, b, ret) return ret } func main() { x := 1 y := 2 defer calc("AA", x, calc("A", x, y)) x = 10 defer calc("BB", x, calc("B", x, y)) y = 20 } 输出结果: A 1 2 3 B 10 2 12 BB 10 12 22 AA 1 3 4 函数参数的传递方式 两种传递方式: 值传递:值类型参数默认。 引用传递:引用类型参数默认。 一般来说,地址传递效率高,因为数据量小。 值类型:int、float、bool、string、数组、结构体。 引用类型:指针、切片、map、管道、接口。 变量的作用域 (1)函数内部申明/定义的变量叫局部变量,作用域仅限于函数的内部。 (2)函数外部申明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域是整个程序。 (3)如果变量是在一个代码块内,比如在if/for中,那么这个变量的作用域就该代码块内。 字符串中的函数 golang中ascii对应的字符占一个字节,而汉字占三个字节。 package main import ( "fmt" "strconv" "strings" ) func main() { //(1)统计字符串的长度,按字节 len(str) str := "hello北京" fmt.Println("str len=",len(str)) // 11 //(2)字符串遍历,同时处理有中文的问题r:=[]rune(str) str2 := "hello北京" r2 := []rune(str2) for i := 0;i<len(r2);i++ { fmt.Printf("字符=%c ",r2[i]) } // 或者 for index,value := range r2{ fmt.Printf("%v:%c ",index,value) } //(3)字符串转整数:n3,err:= strconv.Atoi("12") n3,err := strconv.Atoi("123") if err != nil { fmt.Println("转换错误",err) } else { fmt.Println("转换的结果是",n3) } //(4)整数转字符串:str = strconv.ltoa(12345) str4 := strconv.Itoa(12345) fmt.Printf("str4=%v,str4=%T ",str4,str4) //(5)字符串转[]bytes:var bytes = []byte("hello") var bytes5 = []byte("hello go") fmt.Printf("bytes5=%v ",bytes5) //(6)[]byte转字符串:str = string([]byte{97,98,99}) str6 := string([]byte{97,98,99}) fmt.Printf("str6=%v ",str6) //(7)10进制转2,8,16进制:str = strconv.FormatInt(123,2) 二进制,第二个参数是2;八进制,第二个参数是8;十六进制,第二个参数是16 str71 := strconv.FormatInt(123,2) fmt.Printf("123对应的二进制是=%v ",str71) str72 := strconv.FormatInt(123,8) fmt.Printf("123对应的八进制是=%v ",str72) str73 := strconv.FormatInt(123,16) fmt.Printf("123对应的十六进制是=%v ",str73) //(8)查找子串是否在指定字符串中:strings.Contains("swafood","sea") str8 := strings.Contains("swafoodsea","sea") fmt.Printf("str8=%v ",str8) //(9)统计一个字符串有几个指定子串:strings.Count("cardd","d") num9 := strings.Count("cdardd","d") fmt.Printf("num9=%v ",num9) //(10)不区分大小写的字符串比较(==是区分大小写的):fmt.Println(strings.EqualFold("abc,"ABC")) str10 := strings.EqualFold("abc", "ABC") fmt.Println("str10=%v ",str10) fmt.Println("结果","abc" == "Abc ") //(11)返回子串在字符串中第一次出现的index,如果没有就返回-1:strings.Index("abc","b") str11 := strings.Index("abc","b") fmt.Printf("str11=%v ",str11) //(12)返回子串最后一次出现的index,如果没有则返回-1,strings.LastIndex("abc","b") str12 := strings.LastIndex("abcbxdfb","b") fmt.Printf("str12=%v ",str12) //(13)将指定字符串替换为另一个子串:strings.Replace("go go hello","go","go 语言",n) n可以指定你想替换几个,n=-1为替换全部 str131 := "go go hello" str132 := strings.Replace(str131,"go","go 语言",-1) fmt.Printf("str131=%v str132=%v ",str131,str132) //(14)按照某个指定字符进行分割strings.Split("hello,word,ok",",") str14 := strings.Split("hello,word,ok",",") for i:=0;i<len(str14);i++{ fmt.Printf("str[%v]=%v ",i,str14[i]) } fmt.Printf("str14=%v ",str14) //(15)大小写转换strings.ToLower("Go") ,strings.ToUpper("Go") str151 := "GoLang Hello Word" str152 := strings.ToLower(str151) fmt.Printf("str152=%v ",str152) str153 := strings.ToUpper(str151) fmt.Printf("str153=%v ",str153) //(16)去掉两边空格strings.TrimSpace(" abc 123 xxx nolang ") str16 := strings.TrimSpace(" abc 123 xxx nolang ") fmt.Printf("str16=%q ",str16) //(17)去掉两边指定字符strings.Trim("123abc 123 xxx nolang123","123") str17 := strings.Trim("123abc 123 xxx nolang123","123") fmt.Printf("str17=%q ",str17) //(18)去掉左边指定字符strings.TrimLeft("123abc 123 xxx nolang123","123") str18 := strings.TrimLeft("123abc 123 xxx nolang123","123") fmt.Printf("str18=%q ",str18) //(19)去掉右边指定字符strings.TrimRight("123abc 123 xxx nolang123","123") str19 := strings.TrimRight("123abc 123 xxx nolang123","123") fmt.Printf("str19=%q ",str19) //(20)判断字符串是否以某个字符串开头strings.HasPrefix("http://www.baidu.com","http") str20 := strings.HasPrefix("http://www.baidu.com","http") fmt.Printf("str20=%v ",str20) //(21)判断字符串是否以某个字符串结尾strings.HasSuffix("abc.txt","txt") str21 := strings.HasSuffix("abc.txt","txt") fmt.Printf("str21=%v ",str21) } 时间和日期相关函数 package main import ( "fmt" "time" ) func main() { // 获取当前时间 now := time.Now() fmt.Printf("now = %v now type = %T ",now,now) // 通过now可以获取到年月日、时分秒 fmt.Printf("年 = %v ",now.Year()) fmt.Printf("月 = %v ",now.Month()) fmt.Printf("月 = %v ",int(now.Month())) fmt.Printf("日 = %v ",now.Day()) fmt.Printf("时 = %v ",now.Hour()) fmt.Printf("分 = %v ",now.Minute()) fmt.Printf("秒 = %v ",now.Second()) // 格式化日期时间 fmt.Printf("当前年月日 %d-%d-%d %d-%d-%d ",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second()) dateStr := fmt.Sprintf("当前年月日 %d-%d-%d %d-%d-%d ",now.Year(),now.Month(),now.Day(),now.Hour(),now.Minute(),now.Second()) fmt.Printf("dateStr=%v ",dateStr) // 使用time.Format() 格式化时间 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() } 时间常量 const ( Nanosecond Duration = 1 // 纳秒 Microsecond = 1000 * Nanosecond // 微秒 Millisecond = 1000 * Microsecond // 毫秒 Second = 1000 * Millisecond // 秒 Minute = 60 * Second // 分钟 Hour = 60 * Minute //小时 ) 每隔1秒中打印一个数字,打印到100时就退出 i := 0 for { i++ fmt.Println(i) //休眠 time.Sleep(time.Second) if i == 100 { break } } 每隔0.1秒中打印一个数字,打印到100时就退出 j := 0 for { j++ fmt.Println(j) time.Sleep(time.Millisecond * 100) if j == 100 { break } } time 的 Unix 和 UnixNano 的方法 package main import ( "fmt" "time" ) func main() { // Unix将t表示为Unix时间,即从时间点January 1, 1970 UTC到时间点t所经过的时间(单位秒)。 // UnixNano将t表示为Unix时间,即从时间点January 1, 1970 UTC到时间点t所经过的时间(单位纳秒)。如果纳秒为单位的unix时间超出了int64能表示的范围,结果是未定义的。注意这就意味着Time零值调用UnixNano方法的话,结果是未定义的。 now := time.Now() fmt.Printf("unix时间戳=%v unixnano时间戳=%v ", now.Unix(), now.UnixNano()) } golang内置函数 len:用来求长度,比如 string、array、slice、map、channel new:用来分配内存,主要用来分配值类型,比如 int、float32,struct...返回的是指针 内置函数make:用来分配内存,主要用来分配引用类型,比如map、切片、管道等。 make:用来分配内存,主要用来分配引用类型,比如 channel、map、slice。