多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
[TOC] >[success]## **一、 函数介绍** >[info]### 1.1 定义 函数:有输入、有输出,用来执行一个指定任务的代码块。 ~~~ func functionname([parametername type]) [return type] { //function body } //其中参数列表和返回值列表都是可选的 ~~~ 解释: ~~~ func 函数名([参数名 类型])[返回值 类型] { 函数体 } ~~~ <br> <br> >[info]### **1.2 特点** golang函数的特点: 1)不支持重载,即一个包不能有两个名字一样的函数 2)函数也是一种类型,一个函数可以赋值给变量 3)匿名函数 4)多返回值 >[info]### **1.3 return函数** :-: ![](https://img.kancloud.cn/b5/7a/b57ad89ff4596829bdcfd2e07cb75d07_415x102.png) <br> >[]#### **多返回值** 返回多个值时要用括号包起来 ~~~ package main import "fmt" func test(num1,num2 int) (int,int){ res1 := num1 + num2 res2 := num1 - num2 return res1,res2 } func main(){ a,b := test(20,10) fmt.Printf("a = %v\nb = %v",a,b) } ~~~ **运行结果** `a = 30` `b = 10` <br> <br> >[]#### **只接受一个返回值(_)来忽略** ~~~ package main import "fmt" func test(num1,num2 int) (int,int){ res1 := num1 + num2 res2 := num1 - num2 return res1,res2 } func main(){ a,_ := test(20,10) fmt.Printf("a = %v\nb = %v",a) } ~~~ **运行结果** a = 30 b = %!v(MISSING) <br> <br> >[info]## **二、函数递归调用** 说名:函数体内又调用了本身,称之为递归调用 ~~~ package main func test(n int){ if n > 2{ n-- test(n) } } func main(){ test(4) } ~~~ ~~~ package main import "fmt" func test(n int){ if n > 2{ n-- test(n) }else{ fmt.Println("n = ",n) } } func main(){ test(4) } ~~~ <br> >[success]### **2.1 递归调用的原则** 1)执行一个函数时,就创建一个新的受保护的独立空间(新函数栈) 2)函数的局部变量是独立的,不会相互影响 3)递归必须向退出递归的条件逼近,否则就是无限递归,死龟了:) 4)当一个函数执行完毕,或者遇到return,就会返回,遵守谁调用,就将结果返回 给谁,同时当函数执行完毕或者返回时,该函数本身也会被系统销毁 >[success]## **三、init函数** >[info] ### **3.1 基本介绍** 每一个包都有一个init函数,该函数在main函数之前执行, >[info] ### **3.2 案例演示** ~~~ package main import "fmt" func init(){ fmt.Println("init") } func main(){ fmt.Println("main") } ~~~ **运行结果** **init** **main** >[warning] ### **注意事项** 1)go中在当前文件中的执行顺序,全局变量  ---> init函数----> main函数 代码如下: ~~~ package main import "fmt" var sum = test() func test() int{ fmt.Println("test()") return 100 } func init(){ fmt.Println("init") } func main(){ fmt.Println("main") } ~~~ **运行结果** **test()** **init** **main** <br> 2)如果引入其他文件的全局变量和init函数,那么执行顺序如下: :-: ![](https://img.kancloud.cn/bf/b0/bfb0e65edf3e26d5b34eb5f9abfd59b7_344x130.png) <br> <br> >[success]## **四、匿名函数** >[info]### **4.1 基本介绍** Go支持匿名函数,匿名函数就是没有名字的函数,如果我们某个函数只是希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用。 >[info] ### **4.2 匿名函数使用方式** 1)匿名函数使用方式1 在定义匿名函数时就直接调用,[案例演示] ~~~ package main import "fmt" func main(){ res := func(n,m int) int{ return n + m }(10,20) // 这里是直接调用 fmt.Println(res) } ~~~ 2)匿名函数使用方式2 将匿名函数赋给一一个变量(函数变量),再通过该变量来调用匿名函数 此时匿名函数`func(n3 int , n4 int) int 赋值给了res` res的数据类型是一个函数类型,通过调用ret来完成调用 【案例演示】 ~~~ package main import "fmt" func main(){ res := func(n,m int) int{ return n + m } sum := res(100,200) fmt.Println("sum = ",sum) sum1 := res(30,20) fmt.Println("sum1 = ",sum1) } ~~~ **运行结果** `sum = 300` `sum1 = 50` <br> <br> 全局匿名函数 如果将匿名函数赋给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在程序有效。[案例演示] ~~~ package main import "fmt" var ( res = func(n,m int) int{ return n + m } ) func main(){ ret := res(200,200) fmt.Println(ret) } ~~~ **运行结果** 400 <br> <br> >[success]## **五、闭包** >[info]### **5.1闭包介绍:** 闭包就是一个函数与其相关的引用环境,组合成一个整体 >[info]### **5.2** **strings 和 strconv 包** >[info]### **5.3案例演练:** ~~~ package main import "fmt" func Addper()func(int)int{ var sum = 10 return func(i int) int { fmt.Printf("匿名函数接收到了%v\n",i) sum = sum + i return sum } } func main(){ f := Addper() fmt.Println(f(1)) fmt.Println(f(10)) fmt.Println(f(100)) } ~~~ **运行结果** 匿名函数接收到了1 11 匿名函数接收到了10 21 匿名函数接收到了100 121 <br> 返回的是一个匿名函数。但是这个匿名函数引用到函数外的i .因此这个匿名函数就和i形成一个整体,构成闭包。(第6行到10行就是闭包) 1)大家可以这样理解:闭包是类.函数是操作,i是字段。函数和它使用到n构成闭包。 2)当我们反复的调用f函数时,因为i是初始化一次,因此每调用一次就进行累计。 3)我们要搞清楚闭包的关键,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和它引用到的变量共同构成闭包。 <br> <br> >[info]### **5.4闭包的最佳实践** 请编写一个程序,具体要求如下 1)编写一个函数makefuffix(suffix string)可以接收一个文件后缀名(比如.jpg),并回一个闭包 2)调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀(比如.jpg) ,则返 3)回文件名.jpg,如果已经有.jpg后缀,则返回原文件名。要求使用闭包的方式完成 4)strings.HasSuffix(判断后缀名) func [HasSuffix](https://github.com/golang/go/blob/master/src/strings/strings.go?name=release#376 "View Source")[](https://studygolang.com/static/pkgdoc/pkg/strings.htm#pkg-index) ~~~ package main import ( "fmt" "strings" ) func MaKeSuffix(suffix string)func(string)string{ return func(name string) string { if !strings.HasSuffix(name,suffix){ return name + suffix } return name } } func main(){ res := MaKeSuffix(".jpg") fmt.Println(res("witer")) fmt.Println(res("f")) fmt.Println(res("image")) } ~~~ **运行结果** witer.jpg f.jpg image.jpg <br> <br> >[success]## **六、函数 defer** >[info]### **6.1 基本介绍** 在函数中,程序员经常需要创建资源(比如:数据库连接、文件句柄、锁等),为了在函数执行完毕后,及时的释放资源,Go的设计者提供defer (延时机制) >[info]### **6.2 案例演示** ~~~ package main import "fmt" func test(n,m int)int{ defer fmt.Println("n",n) defer fmt.Println("m",m) res := n + m fmt.Println("res = ",res) return res } func main(){ f := test(10,20) fmt.Println(f) } ~~~ **运行结果** res = 30 m 20 n 10 30 <br> <br> >[info]### **defer细节** 1)当go执行到一个defer时, 不会立即执行defer后的语句,而是将defer后的语句压入到一个栈中【我为了方便,暂时称该栈为defer栈】然后继续执行函数下一个语句 2)当函数执行完毕后,在从defer栈中,依次从栈顶取出语句执行(注:遵守栈先入后出的机制),所以同学们看到前面案例输出的顺序。 3)在defer 将语句放入到栈时,也会将相关的值拷贝同时入栈。请看一段代码: 4)在golang编程中的通常做法是,创建资源后,比如(打开了文件,获取了数据库的链接,或者是锁资源),可以执行 `defer file.Close() defer connect .CloseO ` 5)在defer后,可以继续使用创建资源 6)当函数完毕后,系统会依次从defer栈中,取出语句,关闭资源三造, 7)这种机制,非常简洁,程序员不用再为在什么时机关闭资源而烦心。 <br> <br> >[success]## **七、函数参数的传递方式** :-: ![](https://img.kancloud.cn/05/7d/057d75729c7e428da2f0918f57a30ffa_500x302.png) >[info]### **7.1 基本介绍** 我们在讲解函数注意事项和使用细节时,已经讲过值类型和引用类型了,这里 我们再系统总结一下,因为这是重难点,**值类型参数默认就是值传递**,而引用类型 **参数默认就是引用传递。** 两种传递方式 1)值传递 2)引用传递 其实,不管是值传递还是引用传递,传递给函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,**地址拷贝效率高**,因为数据量小,而值拷贝诀定拷贝的数据大小,数据越大,效率越低。 >[info]### **7.2 值类型和引用类型** 1)值类型: 基本数据类型int系列, float系列, bool, string、数组和结构体struct 2)引用类型: 指针、slice切片、 map、管道chan、interface 等都是引用类型 &emsp;&emsp;&emsp;&emsp;●函数参数的传递方式 &emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;1)值类型默认是值传递: 变量直接存储值,内存通常在栈中分配[案例:画出示意图] :-: ![](https://img.kancloud.cn/bf/15/bf1528a7d599abf1cabf56a268debdc7_294x95.png) &emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;2)引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何变量引用这个地址时,该地址对应的数据空间就成为一个垃圾,由GC来回收。[案例, 并画出示意图] :-: ![](https://img.kancloud.cn/25/6a/256a998eda076ec5b8d54a77afcf876d_326x92.png) >[success]## **八、变量的作用域** >[info] ### 基本介绍 1)函数内部声明/定义的变量叫局部变量,作用域仅限于函数内部。 2)函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效。 ~~~ package main import "fmt" var( age = 23 Name = "Boy" ) func test(){ age := 18 Name := "jack" fmt.Printf("age = %v\nName = %v",age,Name) } func main(){ fmt.Println(age) fmt.Println(Name) test() } ~~~ **运行结果** 23 Boy age = 18 Name = jack 1)如果变量是在一个代码块,比如for/if中, 那么这个变量的的作用域就在该代码块 **练习题** 如下代码,会报错 :-: ![](https://img.kancloud.cn/51/ea/51ea5598415666d5f99c9cdf6980b2d5_298x74.png) **函数的练习** 1)在终端输入一个整数,打印出相应的金字塔 :-: ![](https://img.kancloud.cn/f9/96/f9962feaa745a59e1899a1a6f2ae3d9a_296x213.png) >[success]## **九、string常用的函数** 说明:字符串在我们程序开发中,使用的是非常多的,常用的函数需要掌握[带看手册或者官方编程指南]: >[info]**9,1 统计字符串的长度** 按字节len(str)-----(len是按照字节遍历,并非按照字符) ~~~ func main(){ c := "hello" fmt.Println(len(c)) } ~~~ >[info]**9.2 字符串遍历** 同时处理有中文的问题r:= **[ ]rune(str)**,;例如遍历 a:=“hallo北京” ~~~ package main import "fmt" func main(){ c := "hello北京" res := []rune(c) for i := 1;i < len(res);i++{ fmt.Printf("hello北京的长度是:%c\n",res[i]) } } ~~~ >[info]**9.3 字符串转整数**: n, err := strconv.Atoi("12") ~~~ package main import ( "fmt" "strconv" ) func main(){ sum := "12345" re,err := strconv.Atoi(sum) if err != nil{ fmt.Println("转换错误",err) }else{ fmt.Println("转换成功",re) } } ~~~ >[info]**9.4 整数转字符串**  str = strconv.Itoa(12345) ~~~ package main import ( "fmt" "strconv" ) func main(){ sum := 123456 str := strconv.Itoa(sum) fmt.Printf("str value is【%v】\nstr type is【%T】",str,str) } ~~~ **运行结果** str value is【123456】 str type is【string】 >[info] **9.5 字符串转[]byte** var bytes = [ ]byte("hello go") ~~~ package main import ( "fmt" ) func main(){ sum := []byte("hello") fmt.Println(sum) } ~~~ **运行结果** `[104 101 108 108 111]` >[info] **9.6 []byte 转字符串:** **`str = string( [ ]byte{97, 98, 99})`** ~~~ package main import ( "fmt" ) func main(){ sum := string([]byte{97, 98, 99}) fmt.Printf("sum value %v\nsum type is %T",sum,sum) } ~~~ **运行结果** **sum value abc** **sum type is string**