在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
一.函数的调用机制1.函数的调用过程函数的局部变量与全局变量 举例: package main import "fmt" // 一个test函数 func test(n1 int){ n1 += 1 fmt.Println("n1=",n1) } // 一个get函数 func getSum(n1 int, n2 int) int { // sum := n1 + n2 return sum } func main(){ // 调用 函数 n1 := 10 test(n1) get_sum := getSum(1, 2) fmt.Println("n1=",n1) fmt.Println("get_sum",get_sum) }
程序在运行时,在调用函数时,会在栈空间中分配给函数一个空间。当调用完毕后,空间被进行回收。 return 语句: 注意go 语言return支持多个返回值 基本语法: func 函数名(形参列表) (返回值类型列表){ 语句.... return 返回值列表 }
1)如果返回多个值时,在接收时,希望忽略某个返回值,则使用’_’符号表示占位忽略 2)如果返回值只有一个 (返回值类型列表)可以不写 ()
多个返回值与 _ 占位举例:
package main import "fmt" // 返回两个值的和和查 func getSumAndSub(a int, b int) (int, int){ sum := a + b sub := a - b return sum,sub } func main(){ // 调用 函数 n1 := 10 test(n1) get_sum := getSum(1, 2) fmt.Println("n1=",n1) fmt.Println("get_sum",get_sum) // 返回多个值 re1,re2 := getSumAndSub(3,2) fmt.Println(re1) fmt.Println(re2) // 希望忽略某个返回值,用 _ 符号表示占位符 _,re3 := getSumAndSub(5,4) fmt.Println(re3) }
2.函数的递归调用基本介绍 一个函数在函体内又调用了本身,我们称为递归调用。 举例: package main import ( "fmt" ) func test(n int) { if n > 2 { n-- test(n) // 把test()函数全部带入 } fmt.Println("n=", n) } func main() { test(4) } 举例2:
package main import ( "fmt" ) func test2(n int) { if n > 2 { // 注意执行过if 不在执行else n-- test(n) // 把test()函数全部带入 } else { fmt.Println("n=", n) // n = 2 } } func main() { test2(4) }
函数递归需要遵守的重要原则: 1_执行一个函数时,就创建一个新的受保护的独立空间(新函数栈) 2_函数的局部变量时独立的,不受相互影响 3_递归必须向退出递归的条件逼近,有穷性 4_当一个函数执行完毕,或遇到return,就会返回,遵守谁调用,就将结果返回给谁。 同时当函数执行完毕或者返回时,该函数本身也会被销毁。
递归经典练习题: 练习01:斐波那契数
package main import "fmt" func fb(n int ) int { if (n == 2 || n == 1) { return 1 } else { return fb(n-1) + fb(n-2) } } func main(){ // num := fb(5) fmt.Println("num:",num) // 输出第几个的斐波那契数 }
练习02:求函数值 已知f(1) = 3;f(n) = 2*f(n-1) + 1; 请使用递归的思想编程,求出f(n)的值
package main import "fmt" func test(n int) int { if n == 1 { return 3 } else { return 2 * test(n-1) + 1 } } func main(){ fmt.Println("f(1)=",test(1)) // 3 fmt.Println("f(5)=",test(5)) // 63 }
练习03:猴子吃桃吃一半多一个,10天剩一个,原来桃子多少
package main import "fmt" func test(n int) int { if n == 10 { return 1 } else { return 2 * (test(n+1) + 1) } } func main(){ fmt.Println(test(5)) }
3.函数的主要事项和细节1_函数的形参可以是多个,返回列表也可以是多个 2_形参列表和返回列表的数据类型可以是值类型和引用类型 3_函数的命名遵循标识符命名规范,首字母不能是数字。首字母大写包能被本包问号和其他问号使用,类似pubilc,首字母小写只能在本包使用,其他文件不能使用,类似private。 4_函数中的变量是局部的,函数外不生效 5_基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改不会影响到原来的值。 6_如果希望函数内的变量能修改函数外的变量,可以传入变的地址&,函数内以指针的方式操作变量。从效果看类似引用。 7_GO函数不能重载,函数名相同不支持 8_在GO中,函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。
package main import "fmt" func getSum(n1 int,n2 int) int { return n1 + n2 } func main(){ // 在GO中,函数也是一种数据类型, // 可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。 a := getSum fmt.Printf("a的类型%T,getSum类似是%T\n",a,getSum) res := getSum(10,40) fmt.Println("res=",res) }
9_函数既然是一种数据类型,因此在GO中,函数可以作为形参,并且调用。
package main import "fmt" func getSum(n1 int,n2 int) int { return n1 + n2 } // 函数既然是一种数据类型,因此在GO中,函数可以作为形参,并且调用。 func myFun(funvar func(int, int) int, num1 int , num2 int) int { return funvar(num1, num2) } func main(){ // 在GO中,函数也是一种数据类型, // 可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。 a := getSum fmt.Printf("a的类型%T,getSum类似是%T\n",a,getSum) res := getSum(10,40) fmt.Println("res=",res) res2 := myFun(getSum, 50, 60) fmt.Println("res2=",res2) }
11_为了简化数据类型定义,GO支持自定义数据类型。 基本语法:type自定义数据类型名 数据类型 // 理解:相当于一个别名 举例:type myint int // 这时myint就等价于int来使用
package main import "fmt" func main(){ type myInt int // 给int取别名 ,在go中myInt和int虽然都是int类型,但是go认为myInt和int还是不同的类型 var num1 myInt var num2 int fmt.Printf("num1类型%T,num2类型%T",num1,num2) // 打印:num1类型main.myInt,num2类似int }
举例02:type mySum func(int, int) int // 这时mySum就等价一个函数类型 func(int,int) int
package main import "fmt" // 案例2 type myFunType func(int, int) int // 这时myFun就是func(int,int) int 类型 func getSum(n1 int, n2 int) int { return n1 + n2 } func myFun(funvar myFunType, num1 int, num2 int) int { return funvar(num1, num2) } func main() { type myInt int // 给int取别名 ,在go中myInt和int虽然都是int类型,但是go认为myInt和int还是不同的类型 var num1 myInt var num2 int fmt.Printf("num1类型%T,num2类型%T\n", num1, num2) // 打印:num1类型main.myInt,num2类似int res := myFun(getSum, 500, 600) fmt.Println(res) \\ 1100 }
12_支持对返回值命名
package main import "fmt" func getSumAndSub(n1 int, n2 int) (sum int, sub int) { sum = n1 + n2 sub = n1 - n2 return } func main() { // a, b := getSumAndSub(3, 2) fmt.Println(a, b) }
13.使用_标识符,忽略返回值
14.在GO中支持可变参数 语法格式: // 支持0到多个参数 func sum(args...int) sum int { } // 支持1到多个参数 func sum(n1 int,args...int) sum int {
}
说明: (1)args 是slice 切片(类似数组),通过args[index] 可以访问到各个值。 举例:编写一个函数sum ,可以求出1到多个int的和
package main import "fmt" func sum(n1 int,args...int) int { sum := n1 for i := 0; i < len(args); i ++ { sum += args[i] // args[0] 表示第一个元素的值 其他以此类推 } return sum } func main(){ // 编写一个函数sum ,可以求出1到多个int的和 res := sum(5,6,7,8,1,2,3) fmt.Println(res) }
练习:使用函数对两个值进行交换 package main import "fmt" func swap(n1 *int, n2 *int) { // 指针接收地址进行值的相应交换 // 定义一个临时变量 t := *n1 *n1 = *n2 *n2 = t } func main() { a := 10 b := 20 swap(&a, &b) // 传入的是地址 fmt.Printf("a=%v,b=%v", a, b) } 二.常用函数的介绍1.init函数基本介绍: 每一个源文件都可以包含一个init函数,该函数在main函数执行前,被GO运行框架调用,也就是说init会在main函数前被调用。
package main import "fmt" func init() { fmt.Println("run init") } func main(){ fmt.Println("run main") } // 打印结果: // run init // run main
init函数的注意事项和细节 (1)如果一个文件同时包含全局变量定义,init函数和main函数,则执行的流程全局变量定义--init函数-->main函数
package main import "fmt" var age = test() func test() int { fmt.Println("run test") return 90 } func init() { fmt.Println("run init") } func main(){ fmt.Println("run main") fmt.Println("age=",age) } // 得出结论:先执行变量定义,再执行test-->init -- > main // 打印结果: // run test // run init // run main // age= 90 (2)init 函数最主要的作用就是完成一些初始化的工作。(就是定义一个初始化的变量) (3)当一个main函数从另外文件引入包的时候,优先执行包中的init函数
2.匿名函数GO支持匿名函数,如果我们某个函数只希望使用一次,可以考虑使用匿名函数,匿名函数也可以实现多次调用。
匿名函数使用方式1: 在定义匿名函数时就直接调用(这种方式只能调用一次) 举例01: package main import "fmt" func main() { // 在定义匿名函数时就直接调用 // 使用匿名函数求两个数和 res := func (n1 int,n2 int) int { return n1 + n2 }(10,20) fmt.Println(res) } 匿名函数使用方式2: 将匿名函数赋值给一个变量(函数变量),再通过该变量来调用匿名函数 举例02:
package main import "fmt" func main() { // 将匿名函数func (n1 int n2 int) int 赋给 a 变量 // 则 a 的数据类型就是函数类型 此时 我们可以通过a完成调用 a := func (n1 int,n2 int) int { return n1 - n2 } // 可以多次调用 多态??? res2 := a(30,10) fmt.Println(res2) res3 := a(40,10) fmt.Println(res3) }
全局匿名函数 如果将匿名函数赋值给一个全局变量,那么这个匿名函数,就成为一个全局匿名函数,可以在整个程序中有效。 举例: package main import "fmt" var ( // Func1 为全局匿名函数 Func1 = func(n1 int,n2 int) int { return n1 * n2 } ) func main() { // 全局匿名函数的使用 res4 := Func1(4,5) fmt.Println(res4) } 3.闭包基本介绍: 闭包就是一个函数和与其相关的引用环境组合的一个整体(实体)。 举例: package main import "fmt" // 累加器 func Addupper() func(int) int { // 匿名函数与外面的n构成一个闭包 var n int = 10 return func(x int) int { // 返回是一个函数 匿名函数引用到外面变量n n = n + x return n } } func main() { // f := Addupper() fmt.Println(f(1)) // 11 fmt.Println(f(2)) // 13 } 返回的是一个匿名函数,但是这个匿名函数引用到函数外的n,因此这个匿名函数就和n形成一个整体,构成闭包。 可以理解为:闭包是一个类 ,函数是操作,n是字段。
当我们反复的调用f函数时,因为n只初始化一次 搞清楚闭包的关机,就是要分析出返回的函数它使用(引用)到哪些变量,因为函数和引用的变量共同构成闭包。
一个闭包的例子: package main import "fmt" import "strings" func makeSuffix(suffix string) func(string) string { // (1)编写一个函数 makeSuffix (suffix string) 可以接收一个文件后缀名比如后缀名(.jpg) // 并返回一个闭包 // (2)调用闭包,可以传入一个文件名,如果该文件名没有指定的后缀比如后缀名(.jpg),则返回 // 文件名.jpg,如果已经有.jpg后缀,则返回原文件名 // (3) 要求使用闭包的方式完成 // (4)strings.HasSuffix 这个函数可以判断是否存有指定的后缀 return func(name string) string { if !strings.HasSuffix(name, suffix) { return name + suffix } return name } } func main() { // .jpg的后缀 f := makeSuffix(".jpg") // 实例引用 fmt.Println("new_name:", f("today")) fmt.Println("new_name:", f("today.jpg")) } // 以上代码说明:返回的匿名函数和maksSuffix(suffix string)的suffix 变量组成一个闭包 // 返回的函数引用到suffix变量 4.函数中defer为什么需要defer 在函数中,需要创建资源(比如:数据库连接,文件句柄,锁等),为了在函数执行完毕后,及时的释放资源,GO的设计提供defer(延时机制)。
defer的基本介绍 (1)当go执行到一个defer时,不会立即执行defer后的语句,而是将defer后的语句压入到一个defer栈中(一个独立的栈),然后继续执行函数下一个语句。 (2)当函数执行完毕后,在defer栈中,依次从栈取出语句执行(注:遵循栈,先入后出的机制),所以同学们看到前面案例输出的顺序。 (3)在defer将语句放入到栈时,也会将相关的值拷贝同时入栈。
举例: package main import "fmt" func sum(n1 int, n2 int) int { // defer 的作用会让其本来要执行的语句暂时不执行,将要执行的语句压入到一个defer栈(独立栈) // 暂时不执行 // 当函数执行完毕后,再执行defer栈中语句:先入后出执行 defer fmt.Println("n1=", n1) // defer defer fmt.Println("n2=", n2) // defer res := n1 + n2 fmt.Println("in_res=", res) return res } func main() { res := sum(5, 7) fmt.Println("out_res=", res) // 结果: // in_res= 12 // n2= 7 // n1= 5 // out_res= 12 } defer的最佳实践在于,当函数执行完毕后,可以及时的释放创建的资源(比如:打开文件,立即defer就不用考虑什么时候去close,数据库连接也是如此)。在defer以后可以继续使用创建的资源,当函数执行完毕后可以自动从defer中取出并进行关闭资源。
比如: 例子01: func test() { // 关闭文件资源 file = openfile(文件名) def file.close() // 使用文件句柄相关操作 }
例子02: func test() { // 释放数据库资源 connect = openDatabse() defer connect.close() // 操作数据库
}
三.函数参数的传递方式基本介绍: 值类型参数默认就是值传递,而引用类型参数默认就是引用传递。
1.两种传递方式1)值传递 基本数据类型(int float,bool,string,数组和结构体struct) 2)引用传递(指针传递) 指针,slice切片,map,管道chan,interface等都是引用类型
不管是值传递还是引用传递,传递函数的都是变量的副本,不同的是,值传递的是值的拷贝,引用传递的是地址的拷贝,一般来说,地址拷贝效率高,因为数据量小,而值拷贝决定的数据大小,数据越大效率越低。 2.值传递和引用传递使用特点
1)值类型默认是值传递:变量直接存储值,内存通常在栈中分配。 2)引用类型默认是引用传递:变量存储的是一个地址,这个地址对应的空间才真正存储数据(值),内存通常在堆上分配,当没有任何引用这个地址时,该地址对应的数据空间就成为一个垃圾,有GC来回收。
3)如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果上看类似引用。
四.变量的作用域 说明: 1) 函数内声明/定义的变量叫局部变量,作用域仅限于函数内部。 2)函数外部声明/定义的变量叫全局变量,作用域在整个包都有效,如果其首字母为大写,则作用域在整个程序有效。 3)如果变量是在一个代码块,比如for /if 中,那么这个变量的作用域就在该代码块中。 例子:局部变量与全局变量 package main import "fmt" var age int = 50 var Name string = "zero" // 函数 func test() { // age 和 Name 的作用域只在test函数内 age := 10 Name := "hsz" fmt.Println("age=", age) fmt.Println("Name=", Name) } func main() { test() fmt.Println("age=", age) // 打印是test() 函数外的age变量 fmt.Println("Name=", Name) // 打印是test()函数外的Name变量 // 最后输出结果为: // age= 10 // Name= hsz // age= 50 // Name= zero } 在GO语言中:赋值语句不能在函数体外,如:Name := “hsz” 是错误的。
四.函数的练习
1.金字塔// 打印金字塔案例 使用函数的方式 // 1 2 3 4 5 package main import "fmt" func Jinzita(totalLevel int){ // 表示层数 for i :=1;i <= totalLevel; i++ { // 在打印 *前先打印空格 for k:=1; k <= totalLevel - i; k++ { fmt.Print(" ") } // j 表示每层打印多少 for j :=1; j <= 2 * i - 1; j++ { fmt.Print("*") } fmt.Println() } } func main() { var n int fmt.Println("请输入金字塔层数:") fmt.Scanln(&n) Jinzita(n) } 2.九九乘法表用函数的方式打印九九乘法表 package main import "fmt" func NightNight(){ // 打印九九乘法表 for i:=1;i<=9;i++{ for j := 1; j <= i; j++{ fmt.Printf("%v*%v=%v",i,j,i*j) } fmt.Println() } } func main(){ NightNight() }
|
请发表评论